npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ridwan-retainer/auth-lite

v0.1.1

Published

Lightweight identity layer with anonymous mode and Auth0 integration

Downloads

168

Readme

@ridwan-retainer/auth-lite

A lightweight, production-ready authentication library for React Native applications with support for anonymous users and Auth0 integration. Built with TypeScript for type safety and developer experience.

npm version License: MIT

📋 Table of Contents

✨ Features

  • 🆔 Anonymous Authentication: Automatically generate and persist anonymous user IDs for instant onboarding
  • 🔐 Auth0 Integration: Full OAuth 2.0 / OIDC support with social login (Google, Apple, Facebook)
  • 🔒 Secure Storage: Credentials stored securely using Expo SecureStore
  • ♻️ Token Management: Automatic token refresh and validation
  • 👤 User Profiles: Built-in user profile management and retrieval
  • 🗑️ Account Deletion: Complete account deletion flow with confirmations
  • 🔄 Migration Support: Seamless anonymous-to-authenticated account linking
  • 📱 React Hooks: Easy-to-use hooks for all authentication flows
  • 📝 TypeScript: Full type definitions for excellent IDE support
  • ✅ Well-tested: Comprehensive test coverage

📦 Installation

npm install @ridwan-retainer/auth-lite

Peer Dependencies

This package requires the following peer dependencies:

npm install @react-native-async-storage/async-storage \
            expo-auth-session \
            expo-crypto \
            expo-secure-store \
            expo-web-browser \
            react \
            react-native

Platform-Specific Setup

iOS (Expo)

Add to your app.json:

{
  "expo": {
    "ios": {
      "bundleIdentifier": "com.yourapp.id"
    },
    "scheme": "yourappscheme"
  }
}

Android (Expo)

Add to your app.json:

{
  "expo": {
    "android": {
      "package": "com.yourapp.id"
    },
    "scheme": "yourappscheme"
  }
}

🚀 Quick Start

1. Configure Auth0

import { initializeAuth0 } from '@ridwan-retainer/auth-lite';

// Initialize Auth0 configuration
initializeAuth0({
  domain: 'your-tenant.auth0.com',
  clientId: 'your-client-id',
  redirectUri: 'yourappscheme://auth',
});

2. Set Up Anonymous Authentication

import { initializeAnonymousId } from '@ridwan-retainer/auth-lite';
import { useEffect } from 'react';

function App() {
  useEffect(() => {
    // Initialize anonymous ID on app start
    initializeAnonymousId();
  }, []);

  return <YourApp />;
}

3. Implement Sign In

import { useSignIn, useIsAnonymous } from '@ridwan-retainer/auth-lite';
import { Button, Text, View } from 'react-native';

function AuthScreen() {
  const { signIn, isLoading, error } = useSignIn();
  const isAnonymous = useIsAnonymous();

  const handleSignIn = async () => {
    try {
      const credentials = await signIn();
      console.log('Signed in:', credentials);
    } catch (err) {
      console.error('Sign in failed:', err);
    }
  };

  return (
    <View>
      {isAnonymous && <Text>You're browsing anonymously</Text>}
      <Button
        title={isLoading ? 'Signing in...' : 'Sign In with Auth0'}
        onPress={handleSignIn}
        disabled={isLoading}
      />
      {error && <Text>Error: {error.message}</Text>}
    </View>
  );
}

4. Implement Social Login

import {
  useSignInWithGoogle,
  useSignInWithApple,
  useSignInWithFacebook,
} from '@ridwan-retainer/auth-lite';

function SocialLoginButtons() {
  const { signIn: signInWithGoogle, isLoading: googleLoading } = useSignInWithGoogle();
  const { signIn: signInWithApple, isLoading: appleLoading } = useSignInWithApple();
  const { signIn: signInWithFacebook, isLoading: facebookLoading } = useSignInWithFacebook();

  return (
    <View>
      <Button
        title="Continue with Google"
        onPress={() => signInWithGoogle()}
        disabled={googleLoading}
      />
      <Button
        title="Continue with Apple"
        onPress={() => signInWithApple()}
        disabled={appleLoading}
      />
      <Button
        title="Continue with Facebook"
        onPress={() => signInWithFacebook()}
        disabled={facebookLoading}
      />
    </View>
  );
}

🧠 Core Concepts

Anonymous Users

The library automatically generates and persists an anonymous ID for users who haven't signed in. This allows you to:

  • Track user behavior before authentication
  • Provide immediate access to app features
  • Seamlessly migrate to authenticated accounts

Authentication Flow

  1. Anonymous State: User opens app → Anonymous ID generated
  2. Sign In: User signs in → OAuth flow with Auth0
  3. Token Storage: Access/refresh tokens stored securely
  4. Migration: Anonymous data linked to authenticated account

Credential Management

Credentials are automatically managed:

  • Access Tokens: Short-lived, used for API requests
  • Refresh Tokens: Long-lived, used to obtain new access tokens
  • Automatic Refresh: Tokens refreshed before expiration
  • Secure Storage: All tokens stored in platform-secure storage

📚 API Reference

Anonymous Authentication

generateAnonymousId(): AnonymousId

Generates a new anonymous ID using cryptographic random values.

import { generateAnonymousId } from '@ridwan-retainer/auth-lite';

const anonymousId = generateAnonymousId();
console.log(anonymousId); // "anon_1a2b3c4d..."

getOrCreateAnonymousId(): Promise<AnonymousId>

Gets existing anonymous ID or creates a new one if none exists.

import { getOrCreateAnonymousId } from '@ridwan-retainer/auth-lite';

const id = await getOrCreateAnonymousId();

useAnonymousId(): AnonymousId | null

React hook to get the current anonymous ID.

import { useAnonymousId } from '@ridwan-retainer/auth-lite';

function Profile() {
  const anonymousId = useAnonymousId();
  
  return <Text>Your ID: {anonymousId}</Text>;
}

useIsAnonymous(): boolean

React hook to check if user is anonymous.

import { useIsAnonymous } from '@ridwan-retainer/auth-lite';

function WelcomeBanner() {
  const isAnonymous = useIsAnonymous();
  
  if (isAnonymous) {
    return <Text>Sign in to save your progress!</Text>;
  }
  
  return <Text>Welcome back!</Text>;
}

clearAnonymousId(): Promise<void>

Clears the stored anonymous ID (useful during sign out).

import { clearAnonymousId } from '@ridwan-retainer/auth-lite';

await clearAnonymousId();

Auth0 Integration

initializeAuth0(config: Auth0Config): void

Initialize Auth0 configuration. Call this once at app startup.

import { initializeAuth0 } from '@ridwan-retainer/auth-lite';

initializeAuth0({
  domain: 'your-tenant.auth0.com',
  clientId: 'your-client-id',
  redirectUri: 'yourapp://auth',
  // Optional: additional scopes
  scopes: ['openid', 'profile', 'email', 'offline_access'],
});

useSignIn(): UseSignInReturn

Hook for general Auth0 sign in (shows Auth0 Universal Login).

import { useSignIn } from '@ridwan-retainer/auth-lite';

function SignInButton() {
  const { signIn, isLoading, error } = useSignIn();

  const handleSignIn = async () => {
    try {
      const credentials = await signIn();
      // Navigate to authenticated screen
    } catch (err) {
      console.error('Sign in error:', err);
    }
  };

  return (
    <>
      <Button onPress={handleSignIn} disabled={isLoading}>
        {isLoading ? 'Signing in...' : 'Sign In'}
      </Button>
      {error && <Text style={{color: 'red'}}>{error.message}</Text>}
    </>
  );
}

Return Type:

{
  signIn: () => Promise<Credentials>;
  isLoading: boolean;
  error: Error | null;
}

useSignInWithGoogle(): UseSignInReturn

Hook for Google OAuth sign in.

import { useSignInWithGoogle } from '@ridwan-retainer/auth-lite';

function GoogleSignInButton() {
  const { signIn, isLoading } = useSignInWithGoogle();

  return (
    <Button onPress={() => signIn()} disabled={isLoading}>
      Continue with Google
    </Button>
  );
}

useSignInWithApple(): UseSignInReturn

Hook for Apple Sign In.

import { useSignInWithApple } from '@ridwan-retainer/auth-lite';

function AppleSignInButton() {
  const { signIn, isLoading } = useSignInWithApple();

  return (
    <Button onPress={() => signIn()} disabled={isLoading}>
      Continue with Apple
    </Button>
  );
}

useSignInWithFacebook(): UseSignInReturn

Hook for Facebook OAuth sign in.

import { useSignInWithFacebook } from '@ridwan-retainer/auth-lite';

function FacebookSignInButton() {
  const { signIn, isLoading } = useSignInWithFacebook();

  return (
    <Button onPress={() => signIn()} disabled={isLoading}>
      Continue with Facebook
    </Button>
  );
}

signOut(): Promise<void>

Signs out the current user and clears all stored credentials.

import { signOut } from '@ridwan-retainer/auth-lite';

async function handleSignOut() {
  await signOut();
  // Navigate to login screen
}

Credential Management

getCredentials(options?: GetCredentialsOptions): Promise<Credentials | null>

Retrieves stored credentials with optional auto-refresh.

import { getCredentials } from '@ridwan-retainer/auth-lite';

// Get credentials and auto-refresh if expired
const credentials = await getCredentials({ autoRefresh: true });

if (credentials) {
  // Make API call with credentials.accessToken
}

Options:

{
  autoRefresh?: boolean; // Default: true
}

getAccessToken(): Promise<string | null>

Get the current access token (refreshes if expired).

import { getAccessToken } from '@ridwan-retainer/auth-lite';

const token = await getAccessToken();
if (token) {
  // Use token for API calls
  fetch('https://api.example.com/data', {
    headers: {
      Authorization: `Bearer ${token}`,
    },
  });
}

hasValidCredentials(): Promise<boolean>

Check if user has valid (non-expired) credentials.

import { hasValidCredentials } from '@ridwan-retainer/auth-lite';

const isAuthenticated = await hasValidCredentials();
if (isAuthenticated) {
  // Show authenticated content
} else {
  // Show login prompt
}

refreshCredentials(): Promise<Credentials>

Manually refresh the access token using the refresh token.

import { refreshCredentials } from '@ridwan-retainer/auth-lite';

try {
  const newCredentials = await refreshCredentials();
  console.log('Token refreshed:', newCredentials.accessToken);
} catch (error) {
  // Refresh failed, user needs to sign in again
  console.error('Refresh failed:', error);
}

clearCredentials(): Promise<void>

Clear all stored credentials (use during sign out).

import { clearCredentials } from '@ridwan-retainer/auth-lite';

await clearCredentials();

User Profile

getUserProfile(): Promise<UserProfile | null>

Get the current user's profile information.

import { getUserProfile } from '@ridwan-retainer/auth-lite';

const profile = await getUserProfile();
if (profile) {
  console.log('Name:', profile.name);
  console.log('Email:', profile.email);
  console.log('Picture:', profile.picture);
}

UserProfile Type:

{
  sub: string;           // User ID
  name?: string;         // Full name
  given_name?: string;   // First name
  family_name?: string;  // Last name
  email?: string;        // Email address
  email_verified?: boolean;
  picture?: string;      // Profile picture URL
  [key: string]: any;    // Additional claims
}

getUserId(): Promise<string | null>

Get just the user ID.

import { getUserId } from '@ridwan-retainer/auth-lite';

const userId = await getUserId();

getUserEmail(): Promise<string | null>

Get just the user's email.

import { getUserEmail } from '@ridwan-retainer/auth-lite';

const email = await getUserEmail();

Account Deletion

useDeletion(callback: DeletionCallback): UseDeletionResult

Hook for handling account deletion with confirmation flow.

import { useDeletion } from '@ridwan-retainer/auth-lite';

function DeleteAccountScreen() {
  const { requestDeletion, isDeleting } = useDeletion(async (userId) => {
    // Call your API to delete the account
    await fetch(`https://api.example.com/users/${userId}`, {
      method: 'DELETE',
    });
  });

  const handleDelete = async () => {
    try {
      await requestDeletion();
      // Account deleted, navigate away
    } catch (error) {
      console.error('Deletion failed:', error);
    }
  };

  return (
    <Button onPress={handleDelete} disabled={isDeleting}>
      {isDeleting ? 'Deleting...' : 'Delete Account'}
    </Button>
  );
}

useConfirmation(warning?: DeletionWarning): UseConfirmationResult

Hook for account deletion confirmation dialog.

import { useConfirmation } from '@ridwan-retainer/auth-lite';

function DeleteAccountButton() {
  const { showConfirmation } = useConfirmation({
    title: 'Delete Account?',
    message: 'This action cannot be undone. All your data will be permanently deleted.',
    confirmText: 'Delete',
    cancelText: 'Cancel',
  });

  const handleDelete = async () => {
    const confirmed = await showConfirmation();
    if (confirmed) {
      // Proceed with deletion
    }
  };

  return <Button onPress={handleDelete}>Delete Account</Button>;
}

Migration

checkAndMigrate(): Promise<boolean>

Check if anonymous-to-authenticated migration is needed and perform it.

import { checkAndMigrate, setMigrationCallback } from '@ridwan-retainer/auth-lite';

// Set up migration callback
setMigrationCallback(async (anonymousId, authenticatedId) => {
  // Call your API to migrate data
  await fetch('https://api.example.com/migrate', {
    method: 'POST',
    body: JSON.stringify({ from: anonymousId, to: authenticatedId }),
  });
});

// After sign in, check for migration
const migrated = await checkAndMigrate();
if (migrated) {
  console.log('User data migrated successfully');
}

🔧 Advanced Usage

Custom Authentication Guard

import { hasValidCredentials } from '@ridwan-retainer/auth-lite';
import { useEffect, useState } from 'react';
import { useNavigation } from '@react-navigation/native';

function useAuthGuard() {
  const [isChecking, setIsChecking] = useState(true);
  const navigation = useNavigation();

  useEffect(() => {
    checkAuth();
  }, []);

  const checkAuth = async () => {
    const isAuthenticated = await hasValidCredentials();
    if (!isAuthenticated) {
      navigation.replace('Login');
    }
    setIsChecking(false);
  };

  return { isChecking };
}

// Usage in protected screens
function ProtectedScreen() {
  const { isChecking } = useAuthGuard();
  
  if (isChecking) {
    return <LoadingScreen />;
  }
  
  return <YourContent />;
}

Automatic Token Refresh with Axios

import axios from 'axios';
import { getAccessToken, refreshCredentials } from '@ridwan-retainer/auth-lite';

const api = axios.create({
  baseURL: 'https://api.example.com',
});

// Request interceptor to add token
api.interceptors.request.use(async (config) => {
  const token = await getAccessToken();
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

// Response interceptor to handle 401 errors
api.interceptors.response.use(
  (response) => response,
  async (error) => {
    const originalRequest = error.config;

    if (error.response?.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true;

      try {
        await refreshCredentials();
        const token = await getAccessToken();
        originalRequest.headers.Authorization = `Bearer ${token}`;
        return api(originalRequest);
      } catch (refreshError) {
        // Refresh failed, redirect to login
        return Promise.reject(refreshError);
      }
    }

    return Promise.reject(error);
  }
);

export default api;

Context Provider Pattern

import React, { createContext, useContext, useEffect, useState } from 'react';
import {
  hasValidCredentials,
  getUserProfile,
  signOut,
  type UserProfile,
} from '@ridwan-retainer/auth-lite';

interface AuthContextValue {
  user: UserProfile | null;
  isAuthenticated: boolean;
  isLoading: boolean;
  signOut: () => Promise<void>;
  refreshUser: () => Promise<void>;
}

const AuthContext = createContext<AuthContextValue | undefined>(undefined);

export function AuthProvider({ children }: { children: React.ReactNode }) {
  const [user, setUser] = useState<UserProfile | null>(null);
  const [isAuthenticated, setIsAuthenticated] = useState(false);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    loadUser();
  }, []);

  const loadUser = async () => {
    try {
      const authenticated = await hasValidCredentials();
      setIsAuthenticated(authenticated);

      if (authenticated) {
        const profile = await getUserProfile();
        setUser(profile);
      }
    } finally {
      setIsLoading(false);
    }
  };

  const handleSignOut = async () => {
    await signOut();
    setUser(null);
    setIsAuthenticated(false);
  };

  return (
    <AuthContext.Provider
      value={{
        user,
        isAuthenticated,
        isLoading,
        signOut: handleSignOut,
        refreshUser: loadUser,
      }}
    >
      {children}
    </AuthContext.Provider>
  );
}

export function useAuth() {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth must be used within AuthProvider');
  }
  return context;
}

📖 Migration Guide

Migrating from v0.0.x to v0.1.0

No breaking changes. This is the initial release.

Migrating from Other Auth Libraries

If you're migrating from another authentication library:

  1. Remove old auth dependencies
  2. Install @ridwan-retainer/auth-lite and peer dependencies
  3. Replace initialization code
  4. Update sign in/out flows
  5. Migrate token storage (if needed)

Example migration from Firebase Auth:

// Before (Firebase Auth)
import auth from '@react-native-firebase/auth';

async function signIn() {
  const result = await auth().signInWithEmailAndPassword(email, password);
  return result.user;
}

// After (@ridwan-retainer/auth-lite)
import { useSignIn } from '@ridwan-retainer/auth-lite';

function SignInComponent() {
  const { signIn } = useSignIn();
  
  const handleSignIn = async () => {
    const credentials = await signIn();
    return credentials;
  };
}

✅ Best Practices

1. Initialize Early

Initialize Auth0 configuration as early as possible in your app lifecycle:

// App.tsx
import { initializeAuth0, initializeAnonymousId } from '@ridwan-retainer/auth-lite';

initializeAuth0({
  domain: process.env.AUTH0_DOMAIN,
  clientId: process.env.AUTH0_CLIENT_ID,
  redirectUri: 'myapp://auth',
});

initializeAnonymousId();

2. Handle Errors Gracefully

Always handle authentication errors:

const { signIn, error } = useSignIn();

useEffect(() => {
  if (error) {
    // Show user-friendly error message
    Alert.alert('Sign In Failed', error.message);
  }
}, [error]);

3. Use Auto-Refresh

Always enable auto-refresh when getting credentials:

const credentials = await getCredentials({ autoRefresh: true });

4. Implement Migration

Set up anonymous-to-authenticated migration:

setMigrationCallback(async (anonId, authId) => {
  // Migrate user data on your backend
  await api.post('/migrate', { from: anonId, to: authId });
});

5. Secure Storage Only

Never store tokens in AsyncStorage - the library uses SecureStore automatically.

6. Check Authentication State

Check authentication on app startup and navigation:

useEffect(() => {
  hasValidCredentials().then(setIsAuthenticated);
}, []);

🐛 Troubleshooting

Issue: "No credentials found"

Solution: User hasn't signed in yet. Show login screen.

const credentials = await getCredentials();
if (!credentials) {
  navigation.navigate('Login');
}

Issue: "Refresh token expired"

Solution: User needs to sign in again. Clear credentials and show login.

try {
  await refreshCredentials();
} catch (error) {
  await clearCredentials();
  navigation.navigate('Login');
}

Issue: "Invalid redirect URI"

Solution: Ensure redirect URI matches Auth0 configuration:

  1. Check Auth0 dashboard → Application → Allowed Callback URLs
  2. Verify format: yourscheme://auth
  3. Ensure scheme matches app.json

Issue: Sign in opens browser but doesn't return

Solution: Check deep linking configuration:

// app.json
{
  "expo": {
    "scheme": "yourappscheme"
  }
}

Issue: "Cannot read property 'sub' of null"

Solution: Check if user is authenticated before accessing profile:

const profile = await getUserProfile();
if (profile) {
  console.log(profile.sub);
}

🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

📄 License

MIT © Ridwan Hamid

🔗 Links

📞 Support

For issues and questions: