react-native-auth0

repository·master·Indexed 19 days ago

https://github.com/auth0/react-native-auth0

An SDK for integrating Auth0 authentication into React Native and Expo applications. It provides secure login flows, identity management, and a toolkit for the Auth0 API, including the Auth0Provider for React Hooks and a CredentialsManager for secure storage of user credentials.

Tokens
82.4K
Snippets
174
Records
235
Agent score
68%

What's inside react-native-auth0

  1. What is Multi-Resource Refresh Tokens (MRRT)?

    master

    Multi-Resource Refresh Tokens (MRRT) allow your application to obtain access tokens for multiple APIs (each with a different audience) using a single refresh token. This eliminates the need to perform a new login when switching between different backend services.

    Prerequisites

    1. Enable MRRT on your Auth0 tenant (via Dashboard or Support).
    2. Request offline_access scope during the initial login to ensure a refresh token is issued.
    3. Register all target APIs in the Auth0 Dashboard with their respective audience identifiers.

    Implementation with Hooks

    Use getApiCredentials to fetch tokens for specific audiences and clearApiCredentials to manage the cache.

    import { useAuth0 } from 'react-native-auth0';
    
    function MyComponent() {
      const { authorize, getApiCredentials, clearApiCredentials } = useAuth0();
    
      const login = async () => {
        // 1. Login with offline_access to get the refresh token
        await authorize({
          scope: 'openid profile email offline_access',
          audience: 'https://primary-api.example.com',
        });
      };
    
      const getFirstApiToken = async () => {
        try {
          // 2. Get credentials for a specific API
          const credentials = await getApiCredentials(
            'https://first-api.example.com',
            'read:data write:data'
          );
          console.log('First API authenticated successfully');
        } catch (error) {
          console.error('Error:', error);
        }
      };
    
      const clearFirstApiCache = async () => {
        // 3. Clear cached credentials for a specific API or scope
        await clearApiCredentials('https://first-api.example.com');
        // Or specific scope:
        await clearApiCredentials('https://first-api.example.com', 'read:data');
      };
    
      return <></>;
    }
    import { useAuth0 } from 'react-native-auth0';
    
    function MyComponent() {
      const { authorize, getApiCredentials, clearApiCredentials } = useAuth0();
    
      const login = async () => {
        // Login with offline_access to get a refresh token
        await authorize({
          scope: 'openid profile email offline_access',
          audience: 'https://primary-api.example.com',
        });
      };
    
      const getFirstApiToken = async () => {
        try {
          // Get credentials for the first API
          const credentials = await getApiCredentials(
            'https://first-api.example.com',
            'read:data write:data'
          );
          console.log('First API authenticated successfully');
          console.log('Expires At:', new Date(credentials.expiresAt * 1000));
        } catch (error) {
          console.error('Error:', error);
        }
      };
    
      const getSecondApiToken = async () => {
        try {
          // Get credentials for a different API using the same refresh token
          const credentials = await getApiCredentials(
            'https://second-api.example.com',
            'read:reports'
          );
          console.log('Second API authenticated successfully');
        } catch (error) {
          console.error('Error:', error);
        }
      };
    
      const clearFirstApiCache = async () => {
        // Clear cached credentials for a specific API
        await clearApiCredentials('https://first-api.example.com');
    
        // Or clear with specific scope
        await clearApiCredentials('https://first-api.example.com', 'read:data');
      };
    
      return (
        // Your UI components
      );
    }
  2. Overview of the My Account API

    master

    The My Account API allows authenticated users to manage their own authentication methods, such as passkeys, phone, email, TOTP, push notifications, and recovery codes. It provides endpoints for enrolling new factors, confirming enrollments with OTP, listing, updating, or deleting existing methods, and querying available factors.

    Access the My Account client via the myAccount property from the useAuth0() hook or the Auth0 class instance.

    Prerequisites

    • A custom domain must be configured on your Auth0 tenant.
    • iOS: Configure the Associated Domains entitlement with webcredentials:<your-custom-domain> for passkey support.
    • Android: Set up App Links with your custom domain via an assetlinks.json file for passkey support.
    • Authentication: The user must be authenticated.
    • Scopes: An access token with the appropriate My Account API scopes is required (e.g., read:me:authentication_methods, create:me:authentication_methods, etc.).
    // Access via useAuth0
    const { myAccount } = useAuth0();
    
    // Or via Auth0 instance
    // const myAccount = auth0Instance.myAccount;
  3. Implement Delegation and Impersonation with Actor Tokens

    master

    For scenarios where an actor (like an AI agent or support rep) acts on behalf of a subject, you can pass an actorToken alongside the subjectToken. This follows RFC 8693 Section 2.1.

    Requirements & Constraints:

    • actorToken and actorTokenType must be provided together. Supplying only one throws an error with code invalid_actor_token_parameters.
    • The actorToken must be a valid Auth0 ID token (JWT).
    • Refresh Token Suppression: When an actorToken is present, Auth0 will not issue a refresh token. credentials.refreshToken will be undefined.
    • To act again, a new token exchange must be performed.

    Accessing the act claim: After exchange, use parseIdToken to read the act claim from the ID token, which describes the acting party.

    import { parseIdToken } from 'react-native-auth0';
    
    const credentials = await customTokenExchange({
      subjectToken: 'subject-provider-token',
      subjectTokenType: 'urn:acme:legacy-system-token',
      actorToken: 'actor-id-token',
      actorTokenType: 'http://corporate-idp/id-token',
    });
    
    const user = parseIdToken(credentials.idToken);
    console.log(user.act); // The acting party claim
  4. Switch Auth0 tenants at runtime

    master

    Switching tenants at runtime is a build-time configuration (registering redirect callbacks) combined with a runtime JavaScript change.

    Using Hooks: When using Auth0Provider, changing identity-defining props like domain or clientId causes the provider to rebuild its underlying client. Keep these values in your component state to trigger the switch.

    Using the Auth0 Class: If using the Auth0 class directly, maintain an instance (or a map of instances) per tenant and call the methods on the instance corresponding to the active tenant.

    Important Considerations:

    1. State Lag: Switching tenants does not immediately clear the UI auth state. The user object may briefly show the previous tenant's user until the new client initializes.
    2. Credential Sharing: By default, all clients share the same native credentials store (Android SharedPreferences / iOS Keychain). A login on a new tenant will overwrite the previous tenant's session unless you use credentialsManagerStorageKey to isolate them.
    import React, { useState } from 'react';
    import { Auth0Provider, useAuth0 } from 'react-native-auth0';
    
    const TENANTS = [
      { domain: 'tenant-a.us.auth0.com', clientId: 'CLIENT_ID_A' },
      { domain: 'tenant-b.us.auth0.com', clientId: 'CLIENT_ID_B' },
    ];
    
    const App = () => {
      const [index, setIndex] = useState(0);
      const tenant = TENANTS[index];
    
      return (
        <Auth0Provider domain={tenant.domain} clientId={tenant.clientId}>
          <Button
            title="Switch Tenant"
            onPress={() => setIndex((i) => (i + 1) % TENANTS.length)}
          />
          <LoginScreen />
        </Auth0Provider>
      );
    };
  5. Implement MFA Flexible Factors Grant

    master

    The MFA Flexible Factors Grant allows you to build custom Multi-Factor Authentication (MFA) experiences directly in your React Native UI instead of using Universal Login. You can list enrolled authenticators, enroll new factors (like TOTP, SMS, or Email), trigger challenges, and verify codes.

    This feature is available via the useAuth0 hook or the Auth0 class and works across iOS, Android, and Web. All MFA operations require an mfaToken obtained during the initial login flow.

    // Example of the MFA flow using the useAuth0 hook
    function MfaScreen({ mfaToken }: { mfaToken: string }) {
      const { mfa } = useAuth0();
      const [otp, setOtp] = useState('');
    
      const listAuthenticators = async () => {
        const authenticators = await mfa.getAuthenticators({ mfaToken });
      };
    
      const enrollTotp = async () => {
        const challenge = await mfa.enroll({ mfaToken, factorType: 'otp' });
        // handle challenge.barcodeUri or challenge.secret
      };
    
      const verifyOtp = async () => {
        const credentials = await mfa.verify({ mfaToken, otp });
        // User is now logged in
      };
    }
  6. Configure Biometric Authentication Policies

    master

    The SDK supports four biometric policies to control when biometric prompts are shown to protect credential access. This is configured via localAuthenticationOptions in either the Auth0Provider or the Auth0 class.

    Biometric Policy Types

    • BiometricPolicy.default: System-managed behavior. On iOS, it reuses the same LAContext to optimize prompt frequency. On Android, it maps to the "Always" policy.
    • BiometricPolicy.always: Always requires biometric authentication on every credential access. Creates a fresh LAContext on iOS and uses the "Always" policy on Android.
    • BiometricPolicy.session: Requires authentication once per session. Credentials can be accessed without prompting for a specified biometricTimeout duration.
    • BiometricPolicy.appLifecycle: Persists for the app's lifecycle. The session remains valid until the app restarts or clearCredentials() is called. The default timeout is 1 hour (3600 seconds).

    Platform Differences

    • Android: Uses BiometricPrompt. default and always both map to the Android SDK's "Always" policy. Session state is stored in memory and cleared on app restart.
    • iOS: default reuses LAContext. always, session, and appLifecycle create a fresh LAContext. Uses Face ID or Touch ID.
    import {
      Auth0Provider,
      BiometricPolicy,
      LocalAuthenticationStrategy,
      LocalAuthenticationLevel,
    } from 'react-native-auth0';
    
    function App() {
      return (
        <Auth0Provider
          domain="YOUR_AUTH0_DOMAIN"
          clientId="YOUR_CLIENT_ID"
          localAuthenticationOptions={{
            title: 'Authenticate to access credentials',
            subtitle: 'Please authenticate to continue',
            description: 'We need to authenticate you to retrieve your credentials',
            cancelTitle: 'Cancel',
            evaluationPolicy: LocalAuthenticationStrategy.deviceOwnerWithBiometrics,
            fallbackTitle: 'Use Passcode',
            authenticationLevel: LocalAuthenticationLevel.strong,
            deviceCredentialFallback: true,
            biometricPolicy: BiometricPolicy.session,
            biometricTimeout: 300,
          }}
        >
          <YourApp />
        </Auth0Provider>
      );
    }
  7. How Passkeys work in react-native-auth0

    master

    Passkeys provide a passwordless authentication experience using platform biometrics. The SDK follows a three-step flow that is consistent across native and web platforms, though the implementation of the 'WebAuthn Ceremony' step differs:

    1. Challenge: Request a WebAuthn challenge from Auth0 using passkeySignupChallenge (for new users) or passkeyLoginChallenge (for existing users).
    2. WebAuthn Ceremony: Perform the biometric/platform credential creation or assertion.
      • Native: Use a native module or library (e.g., react-native-passkey).
      • Web: Call the browser's built-in navigator.credentials.create() or .get() API.
    3. Exchange: Send the resulting credential back to Auth0 via getTokenByPasskey to receive your Auth0 tokens.

    Platform Support:

    • iOS: 16.6+ (Requires Associated Domains with webcredentials service).
    • Android: API 28+ (Requires Digital Asset Links configuration).
    • Web: Modern browsers with WebAuthn support (Must be triggered by a user gesture).
    // Conceptual flow
    const challenge = await passkeySignupChallenge({ ... });
    const credential = await yourCredentialManagerCreate(challenge.authParamsPublicKey);
    const credentials = await getTokenByPasskey({ authSession: challenge.authSession, authResponse: credential, ... });
  8. Disable DPoP after enabling it

    master

    If you disable DPoP by setting useDPoP: false in the Auth0 constructor, the SDK handles the transition automatically:

    1. New logins will use standard Bearer tokens.
    2. Existing DPoP tokens remain valid until they expire.
    3. getDPoPHeaders() remains compatible with both token types, returning appropriate headers for either DPoP or Bearer tokens.

    Security Warning: Disabling DPoP is a security downgrade. If you must disable it, it is recommended to force users to re-authenticate to migrate them from DPoP to Bearer tokens.

    // Disable DPoP
    const auth0 = new Auth0({
      domain: 'YOUR_AUTH0_DOMAIN',
      clientId: 'YOUR_AUTH0_CLIENT_ID',
      useDPoP: false, // Disabled
    });
    
    // Force migration from DPoP to Bearer
    async function migrateFromDPoP() {
      try {
        const credentials = await auth0.credentialsManager.getCredentials();
    
        if (credentials.tokenType === 'DPoP') {
          console.log('Migrating from DPoP to Bearer...');
    
          // Clear DPoP credentials
          await auth0.credentialsManager.clearCredentials();
    
          // Re-authenticate to get Bearer tokens
          await auth0.webAuth.authorize();
        }
      } catch (error) {
        console.error('Migration from DPoP failed:', error);
      }
    }
  9. Compare feature availability across Native and Web platforms

    master

    The react-native-auth0 SDK provides a unified API for Native (iOS/Android) and Web (Browser) platforms, but feature availability varies due to security models and underlying technology.

    Key Platform Differences

    • Web Authentication: webAuth.authorize() and webAuth.clearSession() are the primary methods for both platforms. However, webAuth.handleRedirectCallback() is Web-only and is used to manually process callbacks (though it is handled automatically if using the Auth0Provider hook).
    • Credential Management: On Native, you must manually call credentialsManager.saveCredentials() to persist tokens to the secure Keychain/Keystore. On Web, this is handled automatically by the underlying SPA SDK and is a no-op.
    • Direct Authentication Grants: Methods like auth.passwordRealm(), auth.passwordless...(), and auth.loginWith...() (OTP/SMS) are Native-only. They are not supported on Web because exposing credentials directly in a browser is insecure for Single Page Applications. Web users should use Universal Login via webAuth.authorize().
    • Token Refresh: auth.refreshToken() is Native-only. On Web, token refresh is managed automatically by getCredentials() via getTokenSilently().
    • DPoP (Web): When DPoP is enabled (the default), myAccount API calls on Web will only succeed if the access token was issued by the same client instance, as the DPoP proof is signed with that specific client's keypair.
  10. Understand the architectural patterns of the SDK

    master

    The SDK is built using several key architectural patterns:

    • Interface-driven design: Platform backends implement shared contracts defined in core/interfaces/. New public methods are first defined on these interfaces.
    • Bundler-selected factory: The SDK uses file extensions to select the correct implementation at build time rather than using runtime branching. For example, Auth0ClientFactory.ts is used for native platforms (picked by Metro), while Auth0ClientFactory.web.ts is used for web platforms.
    • Orchestrators: Components like AuthenticationOrchestrator and ManagementApiOrchestrator manage the lifecycle of requests through an HttpClient and centralize response parsing.
    • Native bridge: The flow for native calls is Adapter $\rightarrow$ NativeBridgeManager $\rightarrow$ NativeA0Auth0 (TurboModule) $\rightarrow$ Swift/Kotlin.
    • React integration: The SDK provides an Auth0Provider and a useAuth0() hook, which manage state (including user, isLoading, and error) via a reducer.
  11. Handle IPSIE Session Expiry

    master

    The SDK supports the IPSIE session_expiry claim, which allows an upstream identity provider to set a hard ceiling on session lifetime. This ceiling is enforced transparently by the SDK on every credential retrieval.

    How it works

    When the session_expiry timestamp is reached, getCredentials() will clear stored credentials and reject the request. This is layered on top of your existing Auth0 idle and absolute timeouts.

    Detecting Expiry

    You can detect this specific event by catching a CredentialsManagerError with the type SESSION_EXPIRED.

    Accessing the Expiry Timestamp

    You can read the absolute Unix timestamp (in seconds) of when the session will expire via the sessionExpiresAt property on the returned Credentials object.

    Important Notes:

    • Timestamp Format: The upstream provider must emit the timestamp in seconds. If milliseconds are emitted, the SDK will treat it as a malformed value and disable enforcement.
    • Android Behavior: On Android, the session_expiry ceiling is pinned at the initial login and is not raised by subsequent refresh-token grants. On iOS and Web, it is derived from the current ID token.
    • Clock Skew: Enforcement applies a small negative leeway (approx. 30 seconds) to account for clock skew.

    Implementation Example

    import { useAuth0, CredentialsManagerError } from 'react-native-auth0';
    
    function MyComponent() {
      const { getCredentials, authorize } = useAuth0();
    
      const fetchCredentials = async () => {
        try {
          const credentials = await getCredentials();
          
          if (credentials.sessionExpiresAt) {
            const endsAt = new Date(credentials.sessionExpiresAt * 1000);
            console.log(`Upstream IdP session ends at: ${endsAt.toISOString()}`);
          }
          
          return credentials;
        } catch (error) {
          if (error instanceof CredentialsManagerError && error.type === 'SESSION_EXPIRED') {
            // Upstream IdP session has ended — send the user back to login.
            await authorize({ scope: 'openid profile offline_access' });
          } else {
            throw error;
          }
        }
      };
    }
    import { useAuth0, CredentialsManagerError } from 'react-native-auth0';
    
    function MyComponent() {
      const { getCredentials, authorize } = useAuth0();
    
      const fetchCredentials = async () => {
        try {
          const credentials = await getCredentials();
          if (credentials.sessionExpiresAt) {
            const endsAt = new Date(credentials.sessionExpiresAt * 1000);
            console.log(`Upstream IdP session ends at: ${endsAt.toISOString()}`);
          }
          return credentials;
        } catch (error) {
          if (
            error instanceof CredentialsManagerError &&
            error.type === 'SESSION_EXPIRED'
          ) {
            // Upstream IdP session has ended — send the user back to login.
            await authorize({ scope: 'openid profile offline_access' });
          } else {
            throw error;
          }
        }
      };
    
      // ...
    }