├── .npmignore
├── .nvmrc
├── .version
├── .watchmanconfig
├── example
├── .watchmanconfig
├── tsconfig.json
├── app.json
├── android
│ ├── app
│ │ ├── src
│ │ │ └── main
│ │ │ │ ├── res
│ │ │ │ ├── values
│ │ │ │ │ ├── strings.xml
│ │ │ │ │ └── styles.xml
│ │ │ │ ├── mipmap-hdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-mdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-xhdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── ic_launcher_round.png
│ │ │ │ └── drawable
│ │ │ │ │ └── rn_edit_text_material.xml
│ │ │ │ ├── java
│ │ │ │ └── com
│ │ │ │ │ └── auth0example
│ │ │ │ │ ├── MainApplication.kt
│ │ │ │ │ └── MainActivity.kt
│ │ │ │ └── AndroidManifest.xml
│ │ └── proguard-rules.pro
│ ├── gradle
│ │ └── wrapper
│ │ │ ├── gradle-wrapper.jar
│ │ │ └── gradle-wrapper.properties
│ ├── settings.gradle
│ ├── build.gradle
│ ├── gradle.properties
│ └── gradlew.bat
├── ios
│ ├── Auth0Example
│ │ ├── Images.xcassets
│ │ │ ├── Contents.json
│ │ │ └── AppIcon.appiconset
│ │ │ │ └── Contents.json
│ │ ├── main.m
│ │ ├── PrivacyInfo.xcprivacy
│ │ ├── AppDelegate.swift
│ │ ├── Info.plist
│ │ └── LaunchScreen.storyboard
│ ├── Auth0Example.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ ├── .xcode.env
│ ├── AppDelegate.swift
│ ├── Podfile
│ └── Auth0Example.xcodeproj
│ │ └── xcshareddata
│ │ └── xcschemes
│ │ └── Auth0Example.xcscheme
├── src
│ ├── auth0-configuration.js
│ ├── auth0-configuration.d.ts
│ ├── api
│ │ └── auth0.ts
│ ├── components
│ │ ├── Header.tsx
│ │ ├── LabeledInput.tsx
│ │ ├── Button.tsx
│ │ ├── Result.tsx
│ │ └── UserInfo.tsx
│ ├── App.tsx
│ ├── navigation
│ │ ├── AuthStackNavigator.tsx
│ │ ├── MainTabNavigator.tsx
│ │ ├── RootNavigator.tsx
│ │ ├── HooksDemoNavigator.tsx
│ │ └── ClassDemoNavigator.tsx
│ └── screens
│ │ ├── hooks
│ │ └── Api.tsx
│ │ ├── class-based
│ │ └── ClassLogin.tsx
│ │ └── SelectionScreen.tsx
├── babel.config.js
├── index.js
├── metro.config.js
├── react-native.config.js
├── Gemfile
├── public
│ └── index.html
├── README.md
├── .gitignore
├── package.json
├── webpack.config.js
└── Gemfile.lock
├── .husky
├── pre-push
└── pre-commit
├── .github
├── CODEOWNERS
├── ISSUE_TEMPLATE
│ ├── config.yml
│ ├── Feature Request.yml
│ └── Bug Report.yml
├── workflows
│ ├── sca_scan.yml
│ ├── claude-code-review.yml
│ ├── main.yml
│ ├── release.yml
│ ├── publish-docs.yml
│ └── npm-release.yml
├── dependabot.yml
├── actions
│ ├── get-version
│ │ └── action.yml
│ ├── get-prerelease
│ │ └── action.yml
│ ├── npm-publish
│ │ └── action.yml
│ ├── tag-exists
│ │ └── action.yml
│ ├── release-create
│ │ └── action.yml
│ ├── setup
│ │ └── action.yml
│ └── get-release-notes
│ │ └── action.yml
├── stale.yml
└── PULL_REQUEST_TEMPLATE.md
├── app.plugin.js
├── src
├── exports
│ ├── hooks.ts
│ ├── index.ts
│ ├── enums.ts
│ ├── interface.ts
│ └── classes.ts
├── platforms
│ ├── native
│ │ ├── index.ts
│ │ ├── bridge
│ │ │ └── index.ts
│ │ └── adapters
│ │ │ ├── index.ts
│ │ │ └── NativeCredentialsManager.ts
│ ├── web
│ │ ├── adapters
│ │ │ ├── index.ts
│ │ │ ├── __tests__
│ │ │ │ └── WebCredentialsManager.errors.spec.ts
│ │ │ └── WebAuthenticationProvider.ts
│ │ └── index.ts
│ └── index.ts
├── core
│ ├── interfaces
│ │ ├── common.ts
│ │ ├── index.ts
│ │ ├── IUsersClient.ts
│ │ ├── IAuthenticationProvider.ts
│ │ ├── IAuth0Client.ts
│ │ └── IWebAuthProvider.ts
│ ├── services
│ │ ├── index.ts
│ │ └── ManagementApiOrchestrator.ts
│ ├── utils
│ │ ├── index.ts
│ │ ├── telemetry.ts
│ │ ├── deepEqual.ts
│ │ ├── scope.ts
│ │ ├── fetchWithTimeout.ts
│ │ ├── conversion.ts
│ │ ├── validation.ts
│ │ └── __tests__
│ │ │ ├── scope.spec.ts
│ │ │ ├── validation.spec.ts
│ │ │ └── conversion.spec.ts
│ └── models
│ │ ├── index.ts
│ │ ├── ApiCredentials.ts
│ │ ├── Credentials.ts
│ │ ├── AuthError.ts
│ │ ├── DPoPError.ts
│ │ └── WebAuthError.ts
├── hooks
│ ├── index.ts
│ ├── useAuth0.ts
│ └── reducer.ts
├── factory
│ ├── index.ts
│ ├── Auth0ClientFactory.web.ts
│ ├── Auth0ClientFactory.ts
│ └── __tests__
│ │ └── Auth0ClientFactory.spec.ts
├── types
│ └── index.ts
├── index.ts
├── plugin
│ └── __tests__
│ │ └── fixtures
│ │ ├── buildgradle.ts
│ │ ├── swiftappdelegate-withoutlinking.ts
│ │ └── swiftappdelegate-withlinking.ts
└── Auth0.ts
├── opslevel.yml
├── .gitattributes
├── assets
├── ios-sso-alert.png
└── android-app-link.png
├── .shiprc
├── babel.config.js
├── .prettierrc.js
├── android
├── gradle.properties
├── src
│ └── main
│ │ ├── newarch
│ │ └── com
│ │ │ └── auth0
│ │ │ └── react
│ │ │ └── A0Auth0Spec.kt
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ └── com
│ │ │ └── auth0
│ │ │ └── react
│ │ │ ├── ApiCredentialsParser.kt
│ │ │ ├── A0Auth0Package.kt
│ │ │ ├── CredentialsParser.kt
│ │ │ └── LocalAuthenticationOptionsParser.kt
│ │ └── oldarch
│ │ └── com
│ │ └── auth0
│ │ └── react
│ │ └── A0Auth0Spec.kt
└── build.gradle
├── ios
├── A0Auth0-Bridging-Header.h
└── A0Auth0.h
├── .semgrepignore
├── tsconfig.build.json
├── .yarnrc.yml
├── scripts
├── replace-telemetry-version.js
└── ensure-yarn.js
├── codecov.yml
├── A0Auth0.podspec
├── jest.environment.js
├── tsconfig.json
├── LICENSE
├── .gitignore
├── eslint.config.mjs
├── __mocks__
└── react-native.js
├── typedoc.json
├── REACT_NATIVE_WEB_SETUP.md
├── .snyk
└── CONTRIBUTING.md
/.npmignore:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | v22.15.0
--------------------------------------------------------------------------------
/.version:
--------------------------------------------------------------------------------
1 | v5.2.1
2 |
--------------------------------------------------------------------------------
/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/example/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/.husky/pre-push:
--------------------------------------------------------------------------------
1 | npm run prepush
2 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | npm run precommit
2 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @auth0/project-dx-sdks-engineer-codeowner
2 |
--------------------------------------------------------------------------------
/app.plugin.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./lib/commonjs/plugin/withAuth0');
2 |
--------------------------------------------------------------------------------
/src/exports/hooks.ts:
--------------------------------------------------------------------------------
1 | export { Auth0Provider, useAuth0 } from '../hooks';
2 |
--------------------------------------------------------------------------------
/example/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@react-native/typescript-config"
3 | }
4 |
--------------------------------------------------------------------------------
/opslevel.yml:
--------------------------------------------------------------------------------
1 | ---
2 | version: 1
3 | repository:
4 | owner: dx_sdks
5 | tags:
6 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.pbxproj -text
2 | # specific for windows script files
3 | *.bat text eol=crlf
--------------------------------------------------------------------------------
/example/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Auth0Example",
3 | "displayName": "Auth0Example"
4 | }
5 |
--------------------------------------------------------------------------------
/src/platforms/native/index.ts:
--------------------------------------------------------------------------------
1 | export { NativeAuth0Client } from './adapters/NativeAuth0Client';
2 |
--------------------------------------------------------------------------------
/assets/ios-sso-alert.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/auth0/react-native-auth0/HEAD/assets/ios-sso-alert.png
--------------------------------------------------------------------------------
/assets/android-app-link.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/auth0/react-native-auth0/HEAD/assets/android-app-link.png
--------------------------------------------------------------------------------
/src/core/interfaces/common.ts:
--------------------------------------------------------------------------------
1 | export type NativeModuleError = {
2 | code: string;
3 | message: string;
4 | };
5 |
--------------------------------------------------------------------------------
/src/platforms/native/bridge/index.ts:
--------------------------------------------------------------------------------
1 | export * from './INativeBridge';
2 | export * from './NativeBridgeManager';
3 |
--------------------------------------------------------------------------------
/.shiprc:
--------------------------------------------------------------------------------
1 | {
2 | "files": {
3 | "src/core/utils/telemetry.ts": [],
4 | ".version": []
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ['module:@react-native/babel-preset'],
3 | sourceMaps: true,
4 | };
5 |
--------------------------------------------------------------------------------
/src/core/services/index.ts:
--------------------------------------------------------------------------------
1 | export * from './AuthenticationOrchestrator';
2 | export * from './ManagementApiOrchestrator';
3 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Auth0Example
3 |
4 |
--------------------------------------------------------------------------------
/example/ios/Auth0Example/Images.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info": {
3 | "version": 1,
4 | "author": "xcode"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | bracketSpacing: false,
3 | jsxBracketSameLine: true,
4 | singleQuote: true,
5 | trailingComma: 'all',
6 | };
7 |
--------------------------------------------------------------------------------
/example/android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/auth0/react-native-auth0/HEAD/example/android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/src/core/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from './validation';
2 | export * from './conversion';
3 | export * from './fetchWithTimeout';
4 | export * from './scope';
5 |
--------------------------------------------------------------------------------
/src/platforms/web/adapters/index.ts:
--------------------------------------------------------------------------------
1 | export * from './WebAuth0Client';
2 | export * from './WebWebAuthProvider';
3 | export * from './WebCredentialsManager';
4 |
--------------------------------------------------------------------------------
/src/platforms/native/adapters/index.ts:
--------------------------------------------------------------------------------
1 | export * from './NativeAuth0Client';
2 | export * from './NativeWebAuthProvider';
3 | export * from './NativeCredentialsManager';
4 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/auth0/react-native-auth0/HEAD/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/auth0/react-native-auth0/HEAD/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/auth0/react-native-auth0/HEAD/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/src/hooks/index.ts:
--------------------------------------------------------------------------------
1 | export { Auth0Provider } from './Auth0Provider';
2 | export { useAuth0 } from './useAuth0';
3 | export type { Auth0ContextInterface } from './Auth0Context';
4 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | A0Auth0_kotlinVersion=2.0.21
2 | A0Auth0_minSdkVersion=24
3 | A0Auth0_targetSdkVersion=34
4 | A0Auth0_compileSdkVersion=35
5 | A0Auth0_ndkVersion=27.1.12297006
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/auth0/react-native-auth0/HEAD/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/auth0/react-native-auth0/HEAD/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/ios/A0Auth0-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | //
2 | // Use this file to import your target's public headers that you would like to expose to Swift.
3 | //
4 |
5 | #import
6 |
--------------------------------------------------------------------------------
/src/exports/index.ts:
--------------------------------------------------------------------------------
1 | export * as Classes from './classes';
2 | export * as Hooks from './hooks';
3 | export * as Interface from './interface';
4 | export * as Enums from './enums';
5 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/auth0/react-native-auth0/HEAD/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/auth0/react-native-auth0/HEAD/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/auth0/react-native-auth0/HEAD/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/auth0/react-native-auth0/HEAD/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/auth0/react-native-auth0/HEAD/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/src/exports/enums.ts:
--------------------------------------------------------------------------------
1 | export { SafariViewControllerPresentationStyle } from '../index';
2 | export {
3 | LocalAuthenticationLevel,
4 | LocalAuthenticationStrategy,
5 | } from '../types/platform-specific';
6 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: Auth0 Community
4 | url: https://community.auth0.com
5 | about: Discuss this SDK in the Auth0 Community forums
6 |
--------------------------------------------------------------------------------
/src/exports/interface.ts:
--------------------------------------------------------------------------------
1 | export { type Auth0ContextInterface } from '../hooks/Auth0Context';
2 | export * from '../core/interfaces';
3 | export { type AuthState } from '../hooks/reducer';
4 | export * from '../types';
5 |
--------------------------------------------------------------------------------
/src/core/utils/telemetry.ts:
--------------------------------------------------------------------------------
1 | export const telemetry = {
2 | name: 'react-native-auth0',
3 | version: '5.2.1',
4 | };
5 |
6 | export type Telemetry = {
7 | name?: string;
8 | version?: string;
9 | env?: any;
10 | };
11 |
--------------------------------------------------------------------------------
/android/src/main/newarch/com/auth0/react/A0Auth0Spec.kt:
--------------------------------------------------------------------------------
1 | package com.auth0.react
2 |
3 | import com.facebook.react.bridge.ReactApplicationContext
4 |
5 | abstract class A0Auth0Spec(context: ReactApplicationContext) : NativeA0Auth0Spec(context)
--------------------------------------------------------------------------------
/example/src/auth0-configuration.js:
--------------------------------------------------------------------------------
1 | const config = {
2 | clientId: 'your-client-id',
3 | domain: 'your-domain',
4 | clientIdSecondScreen: 'your-client-id',
5 | domainSecondScreen: 'your-domain',
6 | };
7 |
8 | export default config;
9 |
--------------------------------------------------------------------------------
/src/core/interfaces/index.ts:
--------------------------------------------------------------------------------
1 | export * from './common';
2 | export * from './IAuth0Client';
3 | export * from './IAuthenticationProvider';
4 | export * from './ICredentialsManager';
5 | export * from './IWebAuthProvider';
6 | export * from './IUsersClient';
7 |
--------------------------------------------------------------------------------
/src/exports/classes.ts:
--------------------------------------------------------------------------------
1 | export {
2 | AuthError,
3 | CredentialsManagerError,
4 | WebAuthError,
5 | DPoPError,
6 | } from '../core/models';
7 | export { default as Auth0 } from '../Auth0';
8 | export { TimeoutError } from '../core/utils/fetchWithTimeout';
9 |
--------------------------------------------------------------------------------
/.semgrepignore:
--------------------------------------------------------------------------------
1 | docs/
2 | src/auth/__tests__
3 | src/credentials-manager/__tests__
4 | src/hooks/__tests__
5 | src/jwt/__tests__
6 | src/management/__tests__
7 | src/networking/__tests__
8 | src/utils/__tests__
9 | src/webauth/__mocks__
10 | src/webauth/__tests__
11 |
--------------------------------------------------------------------------------
/example/ios/Auth0Example/main.m:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | #import "AppDelegate.h"
4 |
5 | int main(int argc, char *argv[])
6 | {
7 | @autoreleasepool {
8 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
9 | }
10 | }
--------------------------------------------------------------------------------
/example/src/auth0-configuration.d.ts:
--------------------------------------------------------------------------------
1 | interface Auth0Configuration {
2 | domain: string;
3 | clientId: string;
4 | domainSecondScreen: string;
5 | clientIdSecondScreen: string;
6 | }
7 |
8 | declare const config: Auth0Configuration;
9 | export default config;
10 |
--------------------------------------------------------------------------------
/ios/A0Auth0.h:
--------------------------------------------------------------------------------
1 | #ifdef RCT_NEW_ARCH_ENABLED
2 | #import "RNAuth0Spec.h"
3 |
4 | @interface A0Auth0 : NSObject
5 | #else
6 | #import
7 |
8 | @interface A0Auth0 : NSObject
9 | #endif
10 |
11 | @end
12 |
13 | @class NativeBridge;
14 |
--------------------------------------------------------------------------------
/.github/workflows/sca_scan.yml:
--------------------------------------------------------------------------------
1 | name: SCA
2 |
3 | on:
4 | push:
5 | branches: ['master', 'main', '**']
6 |
7 | jobs:
8 | snyk-cli:
9 | uses: auth0/devsecops-tooling/.github/workflows/sca-scan.yml@main
10 | with:
11 | additional-arguments: '--exclude=README.md'
12 | secrets: inherit
13 |
--------------------------------------------------------------------------------
/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig",
3 | "compilerOptions": {
4 | "outDir": "lib",
5 | "declaration": true
6 | },
7 | "exclude": [
8 | "example",
9 | "lib",
10 | "docs",
11 | "scripts",
12 | "babel.config.js",
13 | "jest.environment.js"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/example/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/example/ios/Auth0Example.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/example/ios/Auth0Example.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/factory/index.ts:
--------------------------------------------------------------------------------
1 | // The React Native bundler (Metro) will automatically resolve this import
2 | // to 'Auth0ClientFactory.native.ts' for iOS/Android builds, and bundlers
3 | // like Webpack will resolve it to 'Auth0ClientFactory.web.ts' for web builds
4 | // (with the appropriate resolver configuration).
5 | export { Auth0ClientFactory } from './Auth0ClientFactory';
6 |
--------------------------------------------------------------------------------
/.yarnrc.yml:
--------------------------------------------------------------------------------
1 | nodeLinker: node-modules
2 | nmHoistingLimits: workspaces
3 |
4 | plugins:
5 | - path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
6 | spec: '@yarnpkg/plugin-interactive-tools'
7 | - path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs
8 | spec: '@yarnpkg/plugin-workspace-tools'
9 |
10 | yarnPath: .yarn/releases/yarn-3.6.1.cjs
11 |
--------------------------------------------------------------------------------
/example/babel.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const { getConfig } = require('react-native-builder-bob/babel-config');
3 | const pkg = require('../package.json');
4 |
5 | const root = path.resolve(__dirname, '..');
6 |
7 | module.exports = getConfig(
8 | {
9 | presets: ['module:@react-native/babel-preset'],
10 | },
11 | { root, pkg }
12 | );
13 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 |
3 | updates:
4 | - package-ecosystem: github-actions
5 | directory: /
6 | schedule:
7 | interval: daily
8 | - package-ecosystem: 'npm'
9 | directory: '/'
10 | schedule:
11 | interval: 'daily'
12 | ignore:
13 | - dependency-name: '*'
14 | update-types: ['version-update:semver-major']
15 |
--------------------------------------------------------------------------------
/src/core/models/index.ts:
--------------------------------------------------------------------------------
1 | export { AuthError } from './AuthError';
2 | export { Credentials } from './Credentials';
3 | export { Auth0User } from './Auth0User';
4 | export { ApiCredentials } from './ApiCredentials';
5 | export { CredentialsManagerError } from './CredentialsManagerError';
6 | export { WebAuthError } from './WebAuthError';
7 | export { DPoPError } from './DPoPError';
8 |
--------------------------------------------------------------------------------
/example/android/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement { includeBuild("../node_modules/@react-native/gradle-plugin") }
2 | plugins { id("com.facebook.react.settings") }
3 | extensions.configure(com.facebook.react.ReactSettingsExtension){ ex -> ex.autolinkLibrariesFromCommand() }
4 | rootProject.name = 'Auth0Example'
5 | include ':app'
6 | includeBuild('../node_modules/@react-native/gradle-plugin')
7 |
--------------------------------------------------------------------------------
/android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/platforms/web/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This is the primary entry point for the Web platform module.
3 | *
4 | * It exports the main client class, `WebAuth0Client`, which aggregates all
5 | * web-specific functionality and adapters built on top of `auth0-spa-js`.
6 | * This class is intended to be consumed by the `Auth0ClientFactory`.
7 | */
8 |
9 | export { WebAuth0Client } from './adapters/WebAuth0Client';
10 |
--------------------------------------------------------------------------------
/example/index.js:
--------------------------------------------------------------------------------
1 | import { AppRegistry } from 'react-native';
2 | import App from './src/App';
3 | import { name as appName } from './app.json';
4 |
5 | AppRegistry.registerComponent(appName, () => App);
6 |
7 | // This is the entry point for the React Native web application.
8 | if (typeof document !== 'undefined') {
9 | const rootTag = document.getElementById('root');
10 | AppRegistry.runApplication(appName, { rootTag });
11 | }
12 |
--------------------------------------------------------------------------------
/src/types/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This is the main entry point for all public-facing types used in the library.
3 | * It re-exports types from other modules for a clean and consolidated API surface.
4 | *
5 | * @example
6 | * ```
7 | * import { Credentials, User, WebAuthorizeParameters } from 'react-native-auth0';
8 | * ```
9 | */
10 |
11 | export * from './common';
12 | export * from './parameters';
13 | export * from './platform-specific';
14 |
--------------------------------------------------------------------------------
/scripts/replace-telemetry-version.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 |
4 | const version = require('../package.json').version;
5 | const targetFile = path.resolve(__dirname, '../src/core/utils/telemetry.ts');
6 |
7 | let content = fs.readFileSync(targetFile, 'utf8');
8 | content = content.replace(/__SDK_VERSION__/g, version);
9 |
10 | fs.writeFileSync(targetFile, content);
11 | console.log(`SDK version set to ${version}`);
12 |
--------------------------------------------------------------------------------
/.github/workflows/claude-code-review.yml:
--------------------------------------------------------------------------------
1 | name: Claude Code PR Review
2 |
3 | on:
4 | issue_comment:
5 | types: [created]
6 | pull_request_review_comment:
7 | types: [created]
8 | pull_request_review:
9 | types: [submitted]
10 |
11 | jobs:
12 | claude-review:
13 | permissions:
14 | contents: write
15 | issues: write
16 | pull-requests: write
17 | id-token: write
18 | uses: auth0/auth0-ai-pr-analyzer-gh-action/.github/workflows/claude-code-review.yml@main
19 |
--------------------------------------------------------------------------------
/example/android/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
--------------------------------------------------------------------------------
/src/platforms/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This module serves as a central registry for all platform-specific client implementations.
3 | *
4 | * The Auth0ClientFactory uses this file to dynamically import the correct client
5 | * based on the runtime environment. Adding a new platform (e.g., for desktop)
6 | * would simply involve creating a new platform module and exporting its client
7 | * class from this file.
8 | */
9 |
10 | export { NativeAuth0Client } from './native';
11 | export { WebAuth0Client } from './web';
12 |
--------------------------------------------------------------------------------
/example/metro.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const { getDefaultConfig } = require('@react-native/metro-config');
3 | const { getConfig } = require('react-native-builder-bob/metro-config');
4 |
5 | const root = path.resolve(__dirname, '..');
6 |
7 | /**
8 | * Metro configuration
9 | * https://facebook.github.io/metro/docs/configuration
10 | *
11 | * @type {import('metro-config').MetroConfig}
12 | */
13 | module.exports = getConfig(getDefaultConfig(__dirname), {
14 | root,
15 | project: __dirname,
16 | });
17 |
--------------------------------------------------------------------------------
/.github/actions/get-version/action.yml:
--------------------------------------------------------------------------------
1 | name: Return the version extracted from the branch name
2 |
3 | #
4 | # Returns the version from the .version file.
5 | #
6 | # TODO: Remove once the common repo is public.
7 | #
8 |
9 | outputs:
10 | version:
11 | value: ${{ steps.get_version.outputs.VERSION }}
12 |
13 | runs:
14 | using: composite
15 |
16 | steps:
17 | - id: get_version
18 | shell: bash
19 | run: |
20 | VERSION=$(head -1 .version)
21 | echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT
22 |
--------------------------------------------------------------------------------
/example/ios/.xcode.env:
--------------------------------------------------------------------------------
1 | # This `.xcode.env` file is versioned and is used to source the environment
2 | # used when running script phases inside Xcode.
3 | # To customize your local environment, you can create an `.xcode.env.local`
4 | # file that is not versioned.
5 |
6 | # NODE_BINARY variable contains the PATH to the node executable.
7 | #
8 | # Customize the NODE_BINARY variable here.
9 | # For example, to use nvm with brew, add the following line
10 | # . "$(brew --prefix nvm)/nvm.sh" --no-use
11 | export NODE_BINARY=$(command -v node)
12 |
--------------------------------------------------------------------------------
/example/src/api/auth0.ts:
--------------------------------------------------------------------------------
1 | import Auth0 from 'react-native-auth0';
2 | import config from '../auth0-configuration';
3 |
4 | const AUTH0_DOMAIN = config.domain;
5 | const AUTH0_CLIENT_ID = config.clientId;
6 |
7 | if (!AUTH0_DOMAIN || !AUTH0_CLIENT_ID) {
8 | throw new Error(
9 | 'Missing Auth0 credentials. Please add AUTH0_DOMAIN and AUTH0_CLIENT_ID to your environment variables.'
10 | );
11 | }
12 |
13 | const auth0 = new Auth0({
14 | domain: AUTH0_DOMAIN,
15 | clientId: AUTH0_CLIENT_ID,
16 | });
17 |
18 | export default auth0;
19 |
--------------------------------------------------------------------------------
/codecov.yml:
--------------------------------------------------------------------------------
1 | coverage:
2 | precision: 2
3 | round: down
4 | range: '70...100'
5 | ignore:
6 | - 'src/networking/telemetry.ts'
7 | status:
8 | project:
9 | default:
10 | enabled: true
11 | target: auto
12 | threshold: 1%
13 | if_no_uploads: error
14 | patch:
15 | default:
16 | enabled: true
17 | target: 80%
18 | threshold: 30%
19 | if_no_uploads: error
20 | changes:
21 | default:
22 | enabled: true
23 | if_no_uploads: error
24 | comment: false
25 |
--------------------------------------------------------------------------------
/example/react-native.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const pkg = require('../package.json');
3 |
4 | module.exports = {
5 | project: {
6 | ios: {
7 | automaticPodsInstallation: true,
8 | },
9 | },
10 | dependencies: {
11 | [pkg.name]: {
12 | root: path.join(__dirname, '..'),
13 | platforms: {
14 | // Codegen script incorrectly fails without this
15 | // So we explicitly specify the platforms with empty object
16 | ios: {},
17 | android: {},
18 | },
19 | },
20 | },
21 | };
22 |
--------------------------------------------------------------------------------
/example/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | # You may use http://rbenv.org/ or https://rvm.io/ to install and use this version
4 | ruby ">= 2.6.10"
5 |
6 | # Exclude problematic versions of cocoapods and activesupport that causes build failures.
7 | gem 'cocoapods', '>= 1.13', '!= 1.15.0', '!= 1.15.1'
8 | gem 'activesupport', '>= 6.1.7.5', '!= 7.1.0'
9 | gem 'xcodeproj', '< 1.26.0'
10 | gem 'concurrent-ruby', '< 1.3.4'
11 |
12 | # Ruby 3.4.0 has removed some libraries from the standard library.
13 | gem 'bigdecimal'
14 | gem 'logger'
15 | gem 'benchmark'
16 | gem 'mutex_m'
17 |
--------------------------------------------------------------------------------
/src/core/utils/deepEqual.ts:
--------------------------------------------------------------------------------
1 | export function deepEqual(x: T, y: T): boolean {
2 | if (x === y) {
3 | return true;
4 | } else if (
5 | typeof x == 'object' &&
6 | x != null &&
7 | typeof y == 'object' &&
8 | y != null
9 | ) {
10 | if (Object.keys(x).length != Object.keys(y).length) return false;
11 |
12 | for (const prop in x) {
13 | if (Object.prototype.hasOwnProperty.call(y, prop)) {
14 | if (!deepEqual(x[prop], y[prop])) return false;
15 | } else return false;
16 | }
17 | return true;
18 | } else {
19 | return false;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | AuthError,
3 | CredentialsManagerError,
4 | WebAuthError,
5 | DPoPError,
6 | } from './core/models';
7 | export { TimeoutError } from './core/utils/fetchWithTimeout';
8 | export { TokenType } from './types/common';
9 | export { Auth0Provider } from './hooks/Auth0Provider';
10 | export { useAuth0 } from './hooks/useAuth0';
11 | export * from './types';
12 | export type {
13 | LocalAuthenticationLevel,
14 | LocalAuthenticationOptions,
15 | LocalAuthenticationStrategy,
16 | } from './types/platform-specific';
17 |
18 | // Re-export Auth0 as default
19 | export { default } from './Auth0';
20 |
--------------------------------------------------------------------------------
/example/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext {
3 | buildToolsVersion = "36.0.0"
4 | minSdkVersion = 24
5 | compileSdkVersion = 36
6 | targetSdkVersion = 36
7 | ndkVersion = "27.1.12297006"
8 | kotlinVersion = "2.1.20"
9 | }
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | dependencies {
15 | classpath("com.android.tools.build:gradle")
16 | classpath("com.facebook.react:react-native-gradle-plugin")
17 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin")
18 | }
19 | }
20 |
21 | apply plugin: "com.facebook.react.rootproject"
22 |
--------------------------------------------------------------------------------
/example/src/components/Header.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View, Text, StyleSheet } from 'react-native';
3 |
4 | type Props = {
5 | title: string;
6 | };
7 |
8 | const Header = ({ title }: Props) => {
9 | return (
10 |
11 | {title}
12 |
13 | );
14 | };
15 |
16 | const styles = StyleSheet.create({
17 | container: {
18 | padding: 20,
19 | backgroundColor: '#F5F5F5',
20 | borderBottomWidth: 1,
21 | borderBottomColor: '#E0E0E0',
22 | alignItems: 'center',
23 | },
24 | title: {
25 | fontSize: 22,
26 | fontWeight: 'bold',
27 | color: '#212121',
28 | },
29 | });
30 |
31 | export default Header;
32 |
--------------------------------------------------------------------------------
/src/hooks/useAuth0.ts:
--------------------------------------------------------------------------------
1 | import { useContext } from 'react';
2 | import { Auth0Context, type Auth0ContextInterface } from './Auth0Context';
3 |
4 | /**
5 | * The primary hook for interacting with the Auth0 SDK in a React component.
6 | *
7 | * It provides access to the authentication state (`user`, `error`, `isLoading`)
8 | * and methods for performing authentication (`authorize`, `clearSession`, etc.).
9 | *
10 | * @example
11 | * ```
12 | * const { user, authorize, clearSession, isLoading } = useAuth0();
13 | * ```
14 | *
15 | * @returns The current authentication state and methods.
16 | */
17 | export const useAuth0 = (): Auth0ContextInterface => {
18 | const context = useContext(Auth0Context);
19 | return context;
20 | };
21 |
--------------------------------------------------------------------------------
/A0Auth0.podspec:
--------------------------------------------------------------------------------
1 | require 'json'
2 |
3 | package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
4 |
5 | Pod::Spec.new do |s|
6 | s.name = 'A0Auth0'
7 | s.version = package['version']
8 | s.summary = package['description']
9 | s.homepage = package['repository']['baseUrl']
10 | s.license = package['license']
11 | s.authors = package['author']
12 | s.platforms = { :ios => min_ios_version_supported }
13 | s.swift_version = '5.0'
14 | s.source = { :git => 'https://github.com/auth0/react-native-auth0.git', :tag => "v#{s.version}" }
15 |
16 | s.source_files = 'ios/**/*.{h,m,mm,swift}'
17 | s.requires_arc = true
18 |
19 | s.dependency 'Auth0', '2.14'
20 |
21 | install_modules_dependencies(s)
22 | end
23 |
--------------------------------------------------------------------------------
/example/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StatusBar } from 'react-native';
3 | import { NavigationContainer } from '@react-navigation/native';
4 | import RootNavigator from './navigation/RootNavigator';
5 |
6 | /**
7 | * The absolute root component of the example application.
8 | *
9 | * It sets up the main navigation container and renders the RootNavigator,
10 | * which then decides which demo flow (Hooks or Class-based) to display.
11 | * The Auth0Provider is now scoped within the Hooks demo flow itself.
12 | */
13 | function App(): React.JSX.Element {
14 | return (
15 |
16 |
17 |
18 |
19 | );
20 | }
21 |
22 | export default App;
23 |
--------------------------------------------------------------------------------
/example/src/navigation/AuthStackNavigator.tsx:
--------------------------------------------------------------------------------
1 | // example/src/navigation/AuthStackNavigator.tsx
2 |
3 | import React from 'react';
4 | import { createStackNavigator } from '@react-navigation/stack';
5 | import HomeScreen from '../screens/hooks/Home';
6 |
7 | export type AuthStackParamList = {
8 | Home: undefined;
9 | };
10 |
11 | const Stack = createStackNavigator();
12 |
13 | /**
14 | * Navigator for the unauthenticated part of the Hooks-based demo.
15 | * It displays the main login screen.
16 | */
17 | const AuthStackNavigator = () => {
18 | return (
19 |
20 |
25 |
26 | );
27 | };
28 |
29 | export default AuthStackNavigator;
30 |
--------------------------------------------------------------------------------
/src/core/utils/scope.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Ensures that the 'openid' scope is always present, which is required
3 | * for receiving an ID token. It also adds 'profile' and 'email' as
4 | * sensible defaults if no other scopes are requested.
5 | *
6 | * @param inputScopes A space-separated string of scopes provided by the user.
7 | * @returns A finalized, space-separated string of scopes.
8 | */
9 | export function finalizeScope(inputScopes?: string): string {
10 | const defaultScopes = ['openid', 'profile', 'email'];
11 |
12 | if (!inputScopes?.trim()) {
13 | return defaultScopes.join(' ');
14 | }
15 |
16 | const providedScopes = new Set(inputScopes.split(' ').filter((s) => s));
17 |
18 | // Ensure 'openid' is always included.
19 | providedScopes.add('openid');
20 |
21 | return Array.from(providedScopes).join(' ');
22 | }
23 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | pull_request:
5 | types:
6 | - opened
7 | - synchronize
8 |
9 | permissions: {}
10 |
11 | concurrency:
12 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
13 | cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
14 |
15 | jobs:
16 | test:
17 | name: Run JS tests
18 | runs-on: ubuntu-latest
19 |
20 | steps:
21 | - name: Checkout
22 | uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3
23 |
24 | - name: Setup
25 | uses: ./.github/actions/setup
26 |
27 | - name: Run tests
28 | run: yarn test:ci
29 |
30 | - name: Upload coverage report
31 | uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de
32 | with:
33 | directory: coverage
34 |
--------------------------------------------------------------------------------
/.github/actions/get-prerelease/action.yml:
--------------------------------------------------------------------------------
1 | name: Return a boolean indicating if the version contains prerelease identifiers
2 |
3 | #
4 | # Returns a simple true/false boolean indicating whether the version indicates it's a prerelease or not.
5 | #
6 | # TODO: Remove once the common repo is public.
7 | #
8 |
9 | inputs:
10 | version:
11 | required: true
12 |
13 | outputs:
14 | prerelease:
15 | value: ${{ steps.get_prerelease.outputs.PRERELEASE }}
16 |
17 | runs:
18 | using: composite
19 |
20 | steps:
21 | - id: get_prerelease
22 | shell: bash
23 | run: |
24 | if [[ "${VERSION}" == *"beta"* || "${VERSION}" == *"alpha"* ]]; then
25 | echo "PRERELEASE=true" >> $GITHUB_OUTPUT
26 | else
27 | echo "PRERELEASE=false" >> $GITHUB_OUTPUT
28 | fi
29 | env:
30 | VERSION: ${{ inputs.version }}
31 |
--------------------------------------------------------------------------------
/src/core/utils/fetchWithTimeout.ts:
--------------------------------------------------------------------------------
1 | import { AuthError } from '../models';
2 |
3 | export class TimeoutError extends AuthError {
4 | constructor(message: string) {
5 | super('TimeoutError', message, { code: 'timeout' });
6 | }
7 | }
8 |
9 | export function fetchWithTimeout(
10 | url: RequestInfo,
11 | options: RequestInit,
12 | timeoutMs: number
13 | ): Promise {
14 | const controller = new AbortController();
15 | const signal = controller.signal;
16 | options.signal = signal;
17 |
18 | const timeout = setTimeout(() => controller.abort(), timeoutMs);
19 |
20 | return fetch(url, options)
21 | .catch((err) => {
22 | if (err.name === 'AbortError') {
23 | throw new TimeoutError(`Request timed out after ${timeoutMs}ms`);
24 | }
25 | throw err;
26 | })
27 | .finally(() => {
28 | clearTimeout(timeout);
29 | });
30 | }
31 |
--------------------------------------------------------------------------------
/jest.environment.js:
--------------------------------------------------------------------------------
1 | import JSDOMEnvironment from 'jest-environment-jsdom';
2 | import fetch, { Headers, Request, Response } from 'node-fetch';
3 | import { TextEncoder, TextDecoder } from 'util';
4 |
5 | /**
6 | * Custom Jest Environment based on JSDOMEnvironment to support TextEncoder and TextDecoder.
7 | *
8 | * ref: https://github.com/jsdom/jsdom/issues/2524
9 | */
10 | export default class CustomJSDOMEnvironment extends JSDOMEnvironment {
11 | constructor(config, context) {
12 | super(config, context);
13 | }
14 |
15 | async setup() {
16 | await super.setup();
17 |
18 | this.global.fetch = fetch;
19 | this.global.Headers = Headers;
20 | this.global.Request = Request;
21 | this.global.Response = Response;
22 | this.global.TextEncoder = TextEncoder;
23 | this.global.TextDecoder = TextDecoder;
24 | }
25 | }
26 |
27 | module.exports = CustomJSDOMEnvironment;
28 |
--------------------------------------------------------------------------------
/android/src/main/java/com/auth0/react/ApiCredentialsParser.kt:
--------------------------------------------------------------------------------
1 | package com.auth0.react
2 |
3 | import com.auth0.android.result.APICredentials
4 | import com.facebook.react.bridge.Arguments
5 | import com.facebook.react.bridge.ReadableMap
6 |
7 | object ApiCredentialsParser {
8 |
9 | private const val ACCESS_TOKEN_KEY = "accessToken"
10 | private const val EXPIRES_AT_KEY = "expiresAt"
11 | private const val SCOPE_KEY = "scope"
12 | private const val TOKEN_TYPE_KEY = "tokenType"
13 |
14 | fun toMap(credentials: APICredentials): ReadableMap {
15 | val map = Arguments.createMap()
16 | map.putString(ACCESS_TOKEN_KEY, credentials.accessToken)
17 | map.putDouble(EXPIRES_AT_KEY, credentials.expiresAt.time / 1000.0)
18 | map.putString(SCOPE_KEY, credentials.scope)
19 | map.putString(TOKEN_TYPE_KEY, credentials.type)
20 | return map
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/.github/actions/npm-publish/action.yml:
--------------------------------------------------------------------------------
1 | name: Publish release to npm
2 |
3 | inputs:
4 | version:
5 | required: true
6 | release-directory:
7 | default: './'
8 |
9 | runs:
10 | using: composite
11 |
12 | steps:
13 | - name: Checkout code
14 | uses: actions/checkout@v4
15 |
16 | - name: Setup
17 | uses: ./.github/actions/setup
18 |
19 | - name: Build package
20 | shell: bash
21 | run: yarn ci
22 |
23 | - name: Publish release to NPM
24 | shell: bash
25 | working-directory: ${{ inputs.release-directory }}
26 | run: |
27 | if [[ "${VERSION}" == *"beta"* ]]; then
28 | TAG="beta"
29 | elif [[ "${VERSION}" == *"alpha"* ]]; then
30 | TAG="alpha"
31 | else
32 | TAG="latest"
33 | fi
34 | npm publish --provenance --tag $TAG
35 | env:
36 | VERSION: ${{ inputs.version }}
37 |
--------------------------------------------------------------------------------
/example/android/app/src/main/java/com/auth0example/MainApplication.kt:
--------------------------------------------------------------------------------
1 | package com.auth0example
2 |
3 | import android.app.Application
4 | import com.facebook.react.PackageList
5 | import com.facebook.react.ReactApplication
6 | import com.facebook.react.ReactHost
7 | import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative
8 | import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
9 |
10 | class MainApplication : Application(), ReactApplication {
11 |
12 | override val reactHost: ReactHost by lazy {
13 | getDefaultReactHost(
14 | context = applicationContext,
15 | packageList =
16 | PackageList(this).packages.apply {
17 | // Packages that cannot be autolinked yet can be added manually here, for example:
18 | // add(MyReactNativePackage())
19 | },
20 | )
21 | }
22 |
23 | override fun onCreate() {
24 | super.onCreate()
25 | loadReactNative(this)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "rootDir": ".",
4 | "paths": {
5 | "react-native-auth0": ["./src/index"]
6 | },
7 | "allowUnreachableCode": false,
8 | "allowUnusedLabels": false,
9 | "esModuleInterop": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "jsx": "react-jsx",
12 | "lib": ["ESNext"],
13 | "module": "ESNext",
14 | "moduleResolution": "bundler",
15 | "noEmit": true,
16 | "noFallthroughCasesInSwitch": true,
17 | "noImplicitReturns": true,
18 | "noImplicitUseStrict": false,
19 | "noStrictGenericChecks": false,
20 | "noUncheckedIndexedAccess": true,
21 | "noUnusedLocals": true,
22 | "noUnusedParameters": true,
23 | "resolveJsonModule": true,
24 | "skipLibCheck": true,
25 | "strict": true,
26 | "target": "ESNext",
27 | "verbatimModuleSyntax": true
28 | },
29 | "exclude": ["example", "node_modules", "lib", "docs", "**/__tests__/**"]
30 | }
31 |
--------------------------------------------------------------------------------
/example/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Auth0 React Native Example
9 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/src/core/interfaces/IUsersClient.ts:
--------------------------------------------------------------------------------
1 | import type { User, GetUserParameters, PatchUserParameters } from '../../types';
2 |
3 | /**
4 | * Defines the contract for a client that interacts with the Auth0 Management API's
5 | * user endpoints. An instance of this client is typically created with a
6 | * user-specific management token.
7 | */
8 | export interface IUsersClient {
9 | /**
10 | * Retrieves the full profile of a user from the Management API.
11 | *
12 | * @param parameters The parameters containing the user's ID.
13 | * @returns A promise that resolves with the user's full profile.
14 | */
15 | getUser(parameters: GetUserParameters): Promise;
16 |
17 | /**
18 | * Updates a user's `user_metadata`.
19 | *
20 | * @param parameters The parameters containing the user's ID and the metadata to update.
21 | * @returns A promise that resolves with the updated user profile.
22 | */
23 | patchUser(parameters: PatchUserParameters): Promise;
24 | }
25 |
--------------------------------------------------------------------------------
/example/android/app/src/main/java/com/auth0example/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.auth0example
2 |
3 | import com.facebook.react.ReactActivity
4 | import com.facebook.react.ReactActivityDelegate
5 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
6 | import com.facebook.react.defaults.DefaultReactActivityDelegate
7 |
8 | class MainActivity : ReactActivity() {
9 |
10 | /**
11 | * Returns the name of the main component registered from JavaScript. This is used to schedule
12 | * rendering of the component.
13 | */
14 | override fun getMainComponentName(): String = "Auth0Example"
15 |
16 | /**
17 | * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate]
18 | * which allows you to enable New Architecture with a single boolean flags [fabricEnabled]
19 | */
20 | override fun createReactActivityDelegate(): ReactActivityDelegate =
21 | DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled)
22 | }
23 |
--------------------------------------------------------------------------------
/.github/stale.yml:
--------------------------------------------------------------------------------
1 | # Configuration for probot-stale - https://github.com/probot/stale
2 |
3 | # Number of days of inactivity before an Issue or Pull Request becomes stale
4 | daysUntilStale: 90
5 |
6 | # Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
7 | daysUntilClose: 7
8 |
9 | # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
10 | exemptLabels: []
11 |
12 | # Set to true to ignore issues with an assignee (defaults to false)
13 | exemptAssignees: true
14 |
15 | # Label to use when marking as stale
16 | staleLabel: closed:stale
17 |
18 | # Comment to post when marking as stale. Set to `false` to disable
19 | markComment: >
20 | This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. If you have not received a response for our team (apologies for the delay) and this is still a blocker, please reply with additional information or just a ping. Thank you for your contribution! 🙇♂️
21 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | ## Running the Example Application 🏃♂️
2 |
3 | The Example application can be used for development purpose of the SDK. To integrate with Auth0, it is better to use the [Quickstart](https://auth0.com/docs/quickstart/native/react-native/interactive) and [Sample App](https://github.com/auth0-samples/auth0-react-native-sample/tree/master/00-Login-Hooks) applications
4 |
5 | To run the example application inside the repository, follow these steps:
6 |
7 | 1. Open a terminal or command prompt.
8 | 2. Run `yarn bootstrap` to set up the project.
9 | 3. Run `yarn ci` to build the project.
10 | 4. To run the application:
11 | For Android, run `yarn example:android`.
12 | For iOS, run `yarn example:ios`.
13 |
14 | The application will be built and launched on the specified platform, allowing you to interact with it.
15 |
16 | ### To run on different Auth0 Application
17 |
18 | 1. Change the `clientId` and `domain` value in `example/src/auth0-configuration.js`
19 | 2. For Android, Change the `android:host` values in `example/android/app/src/main/AndroidManifest.xml`
20 |
--------------------------------------------------------------------------------
/src/factory/Auth0ClientFactory.web.ts:
--------------------------------------------------------------------------------
1 | import type { IAuth0Client } from '../core/interfaces';
2 | import type { Auth0Options } from '../types';
3 | import type { WebAuth0Options } from '../types/platform-specific';
4 | import { validateAuth0Options } from '../core/utils';
5 |
6 | // This file ONLY imports the Web client.
7 | import { WebAuth0Client } from '../platforms/web';
8 |
9 | /**
10 | * A factory class responsible for creating the Web-specific
11 | * IAuth0Client instance. This file is automatically selected by bundlers
12 | * like Webpack when targeting the web.
13 | */
14 | export class Auth0ClientFactory {
15 | /**
16 | * Creates and returns a WebAuth0Client instance.
17 | *
18 | * @param options The configuration options for the Auth0 client.
19 | * @returns An instance of WebAuth0Client.
20 | */
21 | static createClient(options: Auth0Options): IAuth0Client {
22 | validateAuth0Options(options);
23 |
24 | // No platform detection needed. We know we are on the web.
25 | return new WebAuth0Client(options as WebAuth0Options);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/plugin/__tests__/fixtures/buildgradle.ts:
--------------------------------------------------------------------------------
1 | export default `
2 | android {
3 | compileSdkVersion rootProject.ext.compileSdkVersion
4 |
5 | compileOptions {
6 | sourceCompatibility JavaVersion.VERSION_1_8
7 | targetCompatibility JavaVersion.VERSION_1_8
8 | }
9 |
10 | defaultConfig {
11 | applicationId 'com.example.app'
12 | minSdkVersion rootProject.ext.minSdkVersion
13 | targetSdkVersion rootProject.ext.targetSdkVersion
14 | versionCode 82
15 | versionName "1.0.0"
16 | }
17 |
18 | splits {
19 | abi {
20 | reset()
21 | enable enableSeparateBuildPerCPUArchitecture
22 | universalApk false // If true, also generate a universal APK
23 | include "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
24 | }
25 | }
26 |
27 | signingConfigs {
28 | debug {
29 | storeFile file('debug.keystore')
30 | storePassword 'android'
31 | keyAlias 'androiddebugkey'
32 | keyPassword 'android'
33 | }
34 | }
35 | `;
36 |
--------------------------------------------------------------------------------
/example/src/components/LabeledInput.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | View,
4 | Text,
5 | TextInput,
6 | StyleSheet,
7 | TextInputProps,
8 | } from 'react-native';
9 |
10 | type Props = TextInputProps & {
11 | label: string;
12 | };
13 |
14 | const LabeledInput = ({ label, ...props }: Props) => {
15 | return (
16 |
17 | {label}
18 |
23 |
24 | );
25 | };
26 |
27 | const styles = StyleSheet.create({
28 | container: {
29 | width: '100%',
30 | marginBottom: 16,
31 | },
32 | label: {
33 | marginBottom: 8,
34 | fontSize: 14,
35 | color: '#424242',
36 | fontWeight: '500',
37 | },
38 | input: {
39 | backgroundColor: '#FAFAFA',
40 | borderWidth: 1,
41 | borderColor: '#E0E0E0',
42 | borderRadius: 8,
43 | padding: 12,
44 | fontSize: 16,
45 | color: '#212121',
46 | },
47 | });
48 |
49 | export default LabeledInput;
50 |
--------------------------------------------------------------------------------
/src/factory/Auth0ClientFactory.ts:
--------------------------------------------------------------------------------
1 | import type { IAuth0Client } from '../core/interfaces';
2 | import type { Auth0Options } from '../types';
3 | import type { NativeAuth0Options } from '../types/platform-specific';
4 | import { validateAuth0Options } from '../core/utils';
5 |
6 | // This file ONLY imports the Native client.
7 | import { NativeAuth0Client } from '../platforms/native';
8 |
9 | /**
10 | * A factory class responsible for creating the Native-specific
11 | * IAuth0Client instance. This file is automatically selected by the Metro
12 | * bundler during a build for iOS or Android.
13 | */
14 | export class Auth0ClientFactory {
15 | /**
16 | * Creates and returns a NativeAuth0Client instance.
17 | *
18 | * @param options The configuration options for the Auth0 client.
19 | * @returns An instance of NativeAuth0Client.
20 | */
21 | static createClient(options: Auth0Options): IAuth0Client {
22 | validateAuth0Options(options);
23 |
24 | // No platform detection needed. We know we are on native.
25 | return new NativeAuth0Client(options as NativeAuth0Options);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/example/ios/Auth0Example/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images": [
3 | {
4 | "idiom": "iphone",
5 | "scale": "2x",
6 | "size": "20x20"
7 | },
8 | {
9 | "idiom": "iphone",
10 | "scale": "3x",
11 | "size": "20x20"
12 | },
13 | {
14 | "idiom": "iphone",
15 | "scale": "2x",
16 | "size": "29x29"
17 | },
18 | {
19 | "idiom": "iphone",
20 | "scale": "3x",
21 | "size": "29x29"
22 | },
23 | {
24 | "idiom": "iphone",
25 | "scale": "2x",
26 | "size": "40x40"
27 | },
28 | {
29 | "idiom": "iphone",
30 | "scale": "3x",
31 | "size": "40x40"
32 | },
33 | {
34 | "idiom": "iphone",
35 | "scale": "2x",
36 | "size": "60x60"
37 | },
38 | {
39 | "idiom": "iphone",
40 | "scale": "3x",
41 | "size": "60x60"
42 | },
43 | {
44 | "idiom": "ios-marketing",
45 | "scale": "1x",
46 | "size": "1024x1024"
47 | }
48 | ],
49 | "info": {
50 | "author": "xcode",
51 | "version": 1
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/.github/actions/tag-exists/action.yml:
--------------------------------------------------------------------------------
1 | name: Return a boolean indicating if a tag already exists for the repository
2 |
3 | #
4 | # Returns a simple true/false boolean indicating whether the tag exists or not.
5 | #
6 | # TODO: Remove once the common repo is public.
7 | #
8 |
9 | inputs:
10 | token:
11 | required: true
12 | tag:
13 | required: true
14 |
15 | outputs:
16 | exists:
17 | description: 'Whether the tag exists or not'
18 | value: ${{ steps.tag-exists.outputs.EXISTS }}
19 |
20 | runs:
21 | using: composite
22 |
23 | steps:
24 | - id: tag-exists
25 | shell: bash
26 | run: |
27 | GET_API_URL="https://api.github.com/repos/${GITHUB_REPOSITORY}/git/ref/tags/${TAG_NAME}"
28 | http_status_code=$(curl -LI $GET_API_URL -o /dev/null -w '%{http_code}\n' -s -H "Authorization: token ${GITHUB_TOKEN}")
29 | if [ "$http_status_code" -ne "404" ] ; then
30 | echo "EXISTS=true" >> $GITHUB_OUTPUT
31 | else
32 | echo "EXISTS=false" >> $GITHUB_OUTPUT
33 | fi
34 | env:
35 | TAG_NAME: ${{ inputs.tag }}
36 | GITHUB_TOKEN: ${{ inputs.token }}
37 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Create npm and GitHub Release
2 |
3 | on:
4 | pull_request:
5 | types:
6 | - closed
7 | workflow_dispatch:
8 |
9 | permissions:
10 | contents: write
11 | id-token: write # For publishing to npm using --provenance
12 | pages: write # For publishing documentation
13 |
14 | ### TODO: Replace instances of './.github/workflows/' w/ `auth0/dx-sdk-actions/workflows/` and append `@latest` after the common `dx-sdk-actions` repo is made public.
15 | ### TODO: Also remove `get-prerelease`, `get-release-notes`, `get-version`, `npm-publish`, `release-create`, and `tag-exists` actions from this repo's .github/actions folder once the repo is public.
16 | ### TODO: Also remove `npm-release` workflow from this repo's .github/workflows folder once the repo is public.
17 |
18 | jobs:
19 | release:
20 | uses: ./.github/workflows/npm-release.yml
21 | secrets:
22 | github-token: ${{ secrets.GITHUB_TOKEN }}
23 |
24 | publish-docs:
25 | needs: release
26 | if: success()
27 | uses: ./.github/workflows/publish-docs.yml
28 | permissions:
29 | contents: write
30 | pages: write
31 | id-token: write
32 |
--------------------------------------------------------------------------------
/example/ios/Auth0Example/PrivacyInfo.xcprivacy:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSPrivacyAccessedAPITypes
6 |
7 |
8 | NSPrivacyAccessedAPIType
9 | NSPrivacyAccessedAPICategoryFileTimestamp
10 | NSPrivacyAccessedAPITypeReasons
11 |
12 | C617.1
13 |
14 |
15 |
16 | NSPrivacyAccessedAPIType
17 | NSPrivacyAccessedAPICategoryUserDefaults
18 | NSPrivacyAccessedAPITypeReasons
19 |
20 | CA92.1
21 |
22 |
23 |
24 | NSPrivacyAccessedAPIType
25 | NSPrivacyAccessedAPICategorySystemBootTime
26 | NSPrivacyAccessedAPITypeReasons
27 |
28 | 35F9.1
29 |
30 |
31 |
32 | NSPrivacyCollectedDataTypes
33 |
34 | NSPrivacyTracking
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2019 Auth0, Inc. (http://auth0.com)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | # OSX
2 | #
3 | .DS_Store
4 |
5 | # Xcode
6 | #
7 | build/
8 | *.pbxuser
9 | !default.pbxuser
10 | *.mode1v3
11 | !default.mode1v3
12 | *.mode2v3
13 | !default.mode2v3
14 | *.perspectivev3
15 | !default.perspectivev3
16 | xcuserdata
17 | *.xccheckout
18 | *.moved-aside
19 | DerivedData
20 | *.hmap
21 | *.ipa
22 | *.xcuserstate
23 | project.xcworkspace
24 |
25 | # Android/IntelliJ
26 | #
27 | build/
28 | .idea
29 | .gradle
30 | local.properties
31 | *.iml
32 |
33 | # node.js
34 | #
35 | node_modules/
36 | package-lock.json # include if you are using npm - don't use both yarn and npm
37 | npm-debug.log
38 | yarn-error.log
39 | yarn.lock # include if you are using yarn - don't use both npm and yarn
40 |
41 | # BUCK
42 | buck-out/
43 | \.buckd/
44 | *.keystore
45 |
46 | # Fastlane
47 | #
48 | # It is recommended to not store the screenshots in the git repo. Instead, use Fastlane to re-generate the
49 | # screenshots whenever they are needed.
50 | # For more information about the recommended setup visit:
51 | # https://docs.fastlane.tools/best-practices/source-control/
52 |
53 | */fastlane/report.xml
54 | */fastlane/Preview.html
55 | */fastlane/screenshots
56 |
57 | # Bundle artifact
58 | *.jsbundle
--------------------------------------------------------------------------------
/example/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
14 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/plugin/__tests__/fixtures/swiftappdelegate-withoutlinking.ts:
--------------------------------------------------------------------------------
1 | export default `
2 | // Swift AppDelegate template for Expo 53+
3 |
4 | import UIKit
5 | import React
6 | import React_RCTAppDelegate
7 | import ReactAppDependencyProvider
8 |
9 | @main
10 | class AppDelegate: RCTAppDelegate {
11 | override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
12 | self.moduleName = "Auth0Example"
13 | self.dependencyProvider = RCTAppDependencyProvider()
14 |
15 | // You can add your custom initial props in the dictionary below.
16 | // They will be passed down to the ViewController used by React Native.
17 | self.initialProps = [:]
18 |
19 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
20 | }
21 |
22 | override func sourceURL(for bridge: RCTBridge) -> URL? {
23 | self.bundleURL()
24 | }
25 |
26 | override func bundleURL() -> URL? {
27 | #if DEBUG
28 | RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
29 | #else
30 | Bundle.main.url(forResource: "main", withExtension: "jsbundle")
31 | #endif
32 | }
33 | }
34 | `;
35 |
--------------------------------------------------------------------------------
/.github/actions/release-create/action.yml:
--------------------------------------------------------------------------------
1 | name: Create a GitHub release
2 |
3 | #
4 | # Creates a GitHub release with the given version.
5 | #
6 | # TODO: Remove once the common repo is public.
7 | #
8 |
9 | inputs:
10 | token:
11 | required: true
12 | files:
13 | required: false
14 | name:
15 | required: true
16 | body:
17 | required: true
18 | tag:
19 | required: true
20 | commit:
21 | required: true
22 | draft:
23 | default: false
24 | required: false
25 | prerelease:
26 | default: false
27 | required: false
28 | fail_on_unmatched_files:
29 | default: true
30 | required: false
31 |
32 | runs:
33 | using: composite
34 |
35 | steps:
36 | - uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844
37 | with:
38 | body: ${{ inputs.body }}
39 | name: ${{ inputs.name }}
40 | tag_name: ${{ inputs.tag }}
41 | target_commitish: ${{ inputs.commit }}
42 | draft: ${{ inputs.draft }}
43 | prerelease: ${{ inputs.prerelease }}
44 | fail_on_unmatched_files: ${{ inputs.fail_on_unmatched_files }}
45 | files: ${{ inputs.files }}
46 | env:
47 | GITHUB_TOKEN: ${{ inputs.token }}
48 |
--------------------------------------------------------------------------------
/src/core/models/ApiCredentials.ts:
--------------------------------------------------------------------------------
1 | import type { ApiCredentials as IApiCredentials } from '../../types';
2 |
3 | /**
4 | * A class representation of API-specific user credentials.
5 | * It encapsulates the tokens and provides helper methods for convenience.
6 | */
7 | export class ApiCredentials implements IApiCredentials {
8 | public accessToken: string;
9 | public tokenType: string;
10 | public expiresAt: number;
11 | public scope?: string;
12 |
13 | /**
14 | * Creates an instance of ApiCredentials.
15 | *
16 | * @param params An object conforming to the ApiCredentials type definition.
17 | */
18 | constructor(params: IApiCredentials) {
19 | this.accessToken = params.accessToken;
20 | this.tokenType = params.tokenType;
21 | this.expiresAt = params.expiresAt;
22 | this.scope = params.scope;
23 | }
24 |
25 | /**
26 | * Checks if the access token is expired.
27 | *
28 | * @param [leeway=0] - A buffer in seconds to account for clock skew. The token will be
29 | * considered expired if it expires within this buffer.
30 | * @returns `true` if the token is expired, `false` otherwise.
31 | */
32 | isExpired(leeway: number = 0): boolean {
33 | const nowInSeconds = Math.floor(Date.now() / 1000);
34 | return this.expiresAt <= nowInSeconds + leeway;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/scripts/ensure-yarn.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | // scripts/ensure-yarn.js
3 | const fs = require('fs');
4 | const path = require('path');
5 |
6 | function deleteFile(filePath) {
7 | if (fs.existsSync(filePath)) {
8 | fs.unlinkSync(filePath);
9 | console.log(`Deleted file: ${filePath}`);
10 | }
11 | }
12 |
13 | function deleteDirectory(directoryPath) {
14 | if (fs.existsSync(directoryPath)) {
15 | fs.rmSync(directoryPath, { recursive: true, force: true });
16 | console.log(`Deleted directory: ${directoryPath}`);
17 | }
18 | }
19 |
20 | if (!process.env.npm_config_user_agent?.includes('yarn')) {
21 | // Clean forbidden files and directories
22 | deleteFile(path.join(__dirname, '../package-lock.json'));
23 | deleteFile(path.join(__dirname, '../pnpm-lock.yaml'));
24 | deleteDirectory(path.join(__dirname, '../node_modules'));
25 |
26 | const examplePath = path.join(__dirname, '../example');
27 | deleteFile(path.join(examplePath, 'package-lock.json'));
28 | deleteFile(path.join(examplePath, 'pnpm-lock.yaml'));
29 | deleteDirectory(path.join(examplePath, 'node_modules'));
30 |
31 | console.warn('Warning: We have removed node_modules and lockfiles');
32 | console.error(
33 | 'Error: This project uses Yarn. Please use "yarn install" instead of npm or pnpm to avoid dependency issues.'
34 | );
35 | process.exit(1);
36 | }
37 |
--------------------------------------------------------------------------------
/.github/actions/setup/action.yml:
--------------------------------------------------------------------------------
1 | name: Setup
2 | description: Setup Node.js and install dependencies
3 |
4 | runs:
5 | using: composite
6 | steps:
7 | - name: Setup Node.js
8 | uses: actions/setup-node@v4
9 | with:
10 | node-version-file: .nvmrc
11 | registry-url: 'https://registry.npmjs.org'
12 |
13 | - name: Install npm 11 # required to use npm OIDC
14 | shell: bash
15 | run: npm install -g npm@11
16 |
17 | - name: Restore dependencies
18 | id: yarn-cache
19 | uses: actions/cache/restore@v4
20 | with:
21 | path: |
22 | **/node_modules
23 | .yarn/install-state.gz
24 | key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }}-${{ hashFiles('**/package.json', '!node_modules/**') }}
25 | restore-keys: |
26 | ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }}
27 | ${{ runner.os }}-yarn-
28 |
29 | - name: Install dependencies
30 | if: steps.yarn-cache.outputs.cache-hit != 'true'
31 | run: yarn install --immutable
32 | shell: bash
33 |
34 | - name: Cache dependencies
35 | if: steps.yarn-cache.outputs.cache-hit != 'true'
36 | uses: actions/cache/save@v4
37 | with:
38 | path: |
39 | **/node_modules
40 | .yarn/install-state.gz
41 | key: ${{ steps.yarn-cache.outputs.cache-primary-key }}
42 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OSX
2 | #
3 | .DS_Store
4 |
5 | # XDE
6 | .expo/
7 |
8 | # VSCode
9 | .vscode/
10 | jsconfig.json
11 |
12 | # Xcode
13 | #
14 | build/
15 | *.pbxuser
16 | !default.pbxuser
17 | *.mode1v3
18 | !default.mode1v3
19 | *.mode2v3
20 | !default.mode2v3
21 | *.perspectivev3
22 | !default.perspectivev3
23 | xcuserdata
24 | *.xccheckout
25 | *.moved-aside
26 | DerivedData
27 | *.hmap
28 | *.ipa
29 | *.xcuserstate
30 | project.xcworkspace
31 | **/.xcode.env.local
32 |
33 | # Android/IJ
34 | #
35 | .classpath
36 | .cxx
37 | .gradle
38 | .idea
39 | .project
40 | .settings
41 | local.properties
42 | android.iml
43 |
44 | # Cocoapods
45 | #
46 | example/ios/Pods
47 |
48 | # Ruby
49 | example/vendor/
50 |
51 | # Yarn
52 | .yarn/*
53 | !.yarn/patches
54 | !.yarn/plugins
55 | !.yarn/releases
56 | !.yarn/sdks
57 | !.yarn/versions
58 |
59 | # node.js
60 | #
61 | node_modules/
62 | npm-debug.log
63 | yarn-debug.log
64 | yarn-error.log
65 |
66 | # BUCK
67 | buck-out/
68 | \.buckd/
69 | android/app/libs
70 | android/keystores/debug.keystore
71 | .kotlin/
72 |
73 | # Yarn
74 | .yarn/*
75 | !.yarn/patches
76 | !.yarn/plugins
77 | !.yarn/releases
78 | !.yarn/sdks
79 | !.yarn/versions
80 |
81 | # Expo
82 | .expo/
83 |
84 | # Turborepo
85 | .turbo/
86 |
87 | # generated by bob
88 | lib/
89 |
90 | # React Native Codegen
91 | ios/generated
92 | android/generated
93 |
94 | # Other
95 | dist/
96 | out/
97 | coverage/
98 | docs/
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ### Changes
2 |
3 | Please describe both what is changing and why this is important. Include:
4 |
5 | - Endpoints added, deleted, deprecated, or changed
6 | - Classes and methods added, deleted, deprecated, or changed
7 | - Screenshots of new or changed UI, if applicable
8 | - A summary of usage if this is a new feature or change to a public API (this should also be added to relevant documentation once released)
9 |
10 | ### References
11 |
12 | Please include relevant links supporting this change such as a:
13 |
14 | - support ticket
15 | - community post
16 | - StackOverflow post
17 | - support forum thread
18 |
19 | Please note any links that are not publicly accessible.
20 |
21 | ### Testing
22 |
23 | Please describe how this can be tested by reviewers. Be specific about anything not tested and reasons why. If this library has unit and/or integration testing, tests should be added for new functionality and existing tests should complete without errors.
24 |
25 | - [ ] This change adds unit test coverage
26 | - [ ] This change has been tested on the latest version of the platform/language or why not
27 |
28 | ### Checklist
29 |
30 | - [ ] I have read the [Auth0 general contribution guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md)
31 | - [ ] All existing and new tests complete without errors
32 | - [ ] All active GitHub checks have passed
33 |
--------------------------------------------------------------------------------
/.github/actions/get-release-notes/action.yml:
--------------------------------------------------------------------------------
1 | name: Return the release notes extracted from the body of the PR associated with the release.
2 |
3 | #
4 | # Returns the release notes from the content of a pull request linked to a release branch. It expects the branch name to be in the format release/vX.Y.Z, release/X.Y.Z, release/vX.Y.Z-beta.N. etc.
5 | #
6 | # TODO: Remove once the common repo is public.
7 | #
8 | inputs:
9 | version:
10 | required: true
11 | repo_name:
12 | required: false
13 | repo_owner:
14 | required: true
15 | token:
16 | required: true
17 |
18 | outputs:
19 | release-notes:
20 | value: ${{ steps.get_release_notes.outputs.RELEASE_NOTES }}
21 |
22 | runs:
23 | using: composite
24 |
25 | steps:
26 | - uses: actions/github-script@v7
27 | id: get_release_notes
28 | with:
29 | result-encoding: string
30 | script: |
31 | const { data: pulls } = await github.rest.pulls.list({
32 | owner: process.env.REPO_OWNER,
33 | repo: process.env.REPO_NAME,
34 | state: 'all',
35 | head: `${process.env.REPO_OWNER}:release/${process.env.VERSION}`,
36 | });
37 | core.setOutput('RELEASE_NOTES', pulls[0].body);
38 | env:
39 | GITHUB_TOKEN: ${{ inputs.token }}
40 | REPO_OWNER: ${{ inputs.repo_owner }}
41 | REPO_NAME: ${{ inputs.repo_name }}
42 | VERSION: ${{ inputs.version }}
43 |
--------------------------------------------------------------------------------
/src/factory/__tests__/Auth0ClientFactory.spec.ts:
--------------------------------------------------------------------------------
1 | import { Auth0ClientFactory } from '../Auth0ClientFactory';
2 | import { validateAuth0Options } from '../../core/utils';
3 | import { NativeAuth0Client } from '../../platforms';
4 |
5 | // Now we only need to mock the dependencies of the NATIVE factory
6 | jest.mock('../../core/utils/validation');
7 | jest.mock('../../platforms/native/adapters/NativeAuth0Client');
8 |
9 | const mockValidateAuth0Options = validateAuth0Options as jest.Mock;
10 | const MockNativeAuth0Client = NativeAuth0Client as jest.MockedClass<
11 | typeof NativeAuth0Client
12 | >;
13 |
14 | describe('Auth0ClientFactory (Native)', () => {
15 | const options = {
16 | domain: 'my-tenant.auth0.com',
17 | clientId: 'MyClientId123',
18 | };
19 |
20 | beforeEach(() => {
21 | jest.clearAllMocks();
22 | });
23 |
24 | it('should call validateAuth0Options with the provided options', () => {
25 | Auth0ClientFactory.createClient(options);
26 | expect(mockValidateAuth0Options).toHaveBeenCalledWith(options);
27 | });
28 |
29 | // This test is now specific to the native factory
30 | it('should always create a NativeAuth0Client', () => {
31 | const client = Auth0ClientFactory.createClient(options);
32 | expect(MockNativeAuth0Client).toHaveBeenCalledTimes(1);
33 | expect(MockNativeAuth0Client).toHaveBeenCalledWith(options);
34 | expect(client).toBeInstanceOf(MockNativeAuth0Client);
35 | });
36 | });
37 |
--------------------------------------------------------------------------------
/android/src/main/java/com/auth0/react/A0Auth0Package.kt:
--------------------------------------------------------------------------------
1 | package com.auth0.react
2 |
3 | import com.facebook.react.TurboReactPackage
4 | import com.facebook.react.bridge.ReactApplicationContext
5 | import com.facebook.react.bridge.NativeModule
6 | import com.facebook.react.module.model.ReactModuleInfoProvider
7 | import com.facebook.react.module.model.ReactModuleInfo
8 |
9 | class A0Auth0Package : TurboReactPackage() {
10 |
11 | override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
12 | return if (name == A0Auth0Module.NAME) {
13 | A0Auth0Module(reactContext)
14 | } else {
15 | null
16 | }
17 | }
18 |
19 | override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
20 | return ReactModuleInfoProvider {
21 | val moduleInfos = mutableMapOf()
22 | val isTurboModule = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
23 |
24 | moduleInfos[A0Auth0Module.NAME] = ReactModuleInfo(
25 | A0Auth0Module.NAME,
26 | A0Auth0Module.NAME,
27 | false, // canOverrideExistingModule
28 | false, // needsEagerInit
29 | true, // hasConstants
30 | false, // isCxxModule
31 | isTurboModule // isTurboModule
32 | )
33 |
34 | moduleInfos
35 | }
36 | }
37 | }
--------------------------------------------------------------------------------
/example/src/components/Button.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | TouchableOpacity,
4 | Text,
5 | StyleSheet,
6 | ViewStyle,
7 | TextStyle,
8 | ActivityIndicator,
9 | } from 'react-native';
10 |
11 | type Props = {
12 | onPress: () => void;
13 | title: string;
14 | disabled?: boolean;
15 | loading?: boolean;
16 | style?: ViewStyle;
17 | textStyle?: TextStyle;
18 | };
19 |
20 | const Button = ({
21 | onPress,
22 | title,
23 | disabled,
24 | loading,
25 | style,
26 | textStyle,
27 | }: Props) => {
28 | return (
29 |
34 | {loading ? (
35 |
36 | ) : (
37 | {title}
38 | )}
39 |
40 | );
41 | };
42 |
43 | const styles = StyleSheet.create({
44 | button: {
45 | backgroundColor: '#E53935', // A distinct color for Auth0
46 | paddingVertical: 12,
47 | paddingHorizontal: 24,
48 | borderRadius: 8,
49 | alignItems: 'center',
50 | justifyContent: 'center',
51 | minWidth: 200,
52 | },
53 | text: {
54 | color: '#FFFFFF',
55 | fontSize: 16,
56 | fontWeight: 'bold',
57 | },
58 | disabled: {
59 | backgroundColor: '#BDBDBD',
60 | },
61 | });
62 |
63 | export default Button;
64 |
--------------------------------------------------------------------------------
/example/ios/Auth0Example/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import React
3 | import React_RCTAppDelegate
4 | import ReactAppDependencyProvider
5 |
6 | @main
7 | class AppDelegate: UIResponder, UIApplicationDelegate {
8 | var window: UIWindow?
9 |
10 | var reactNativeDelegate: ReactNativeDelegate?
11 | var reactNativeFactory: RCTReactNativeFactory?
12 |
13 | func application(
14 | _ application: UIApplication,
15 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
16 | ) -> Bool {
17 | let delegate = ReactNativeDelegate()
18 | let factory = RCTReactNativeFactory(delegate: delegate)
19 | delegate.dependencyProvider = RCTAppDependencyProvider()
20 |
21 | reactNativeDelegate = delegate
22 | reactNativeFactory = factory
23 |
24 | window = UIWindow(frame: UIScreen.main.bounds)
25 |
26 | factory.startReactNative(
27 | withModuleName: "Auth0Example",
28 | in: window,
29 | launchOptions: launchOptions
30 | )
31 |
32 | return true
33 | }
34 | }
35 |
36 | class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate {
37 | override func sourceURL(for bridge: RCTBridge) -> URL? {
38 | self.bundleURL()
39 | }
40 |
41 | override func bundleURL() -> URL? {
42 | #if DEBUG
43 | RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
44 | #else
45 | Bundle.main.url(forResource: "main", withExtension: "jsbundle")
46 | #endif
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/example/src/navigation/MainTabNavigator.tsx:
--------------------------------------------------------------------------------
1 | // example/src/navigation/MainTabNavigator.tsx
2 |
3 | import React from 'react';
4 | import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
5 | import ProfileScreen from '../screens/hooks/Profile';
6 | import ApiScreen from '../screens/hooks/Api';
7 | import MoreScreen from '../screens/hooks/More';
8 | import CredentialsScreen from '../screens/hooks/CredentialsScreen';
9 |
10 | export type MainTabParamList = {
11 | Profile: undefined;
12 | Api: undefined;
13 | More: undefined;
14 | Credentials: undefined;
15 | };
16 |
17 | const Tab = createBottomTabNavigator();
18 |
19 | /**
20 | * Navigator for the authenticated part of the Hooks-based demo.
21 | * It provides tab-based navigation to the Profile, API, and other screens.
22 | */
23 | const MainTabNavigator = () => {
24 | return (
25 |
31 |
36 |
37 |
38 |
39 |
40 | );
41 | };
42 |
43 | export default MainTabNavigator;
44 |
--------------------------------------------------------------------------------
/src/plugin/__tests__/fixtures/swiftappdelegate-withlinking.ts:
--------------------------------------------------------------------------------
1 | export default `
2 | // Swift AppDelegate template for Expo 53+
3 |
4 | import UIKit
5 | import React
6 | import React_RCTAppDelegate
7 | import ReactAppDependencyProvider
8 | import React_RCTLinking
9 |
10 | @main
11 | class AppDelegate: RCTAppDelegate {
12 | override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
13 | self.moduleName = "Auth0Example"
14 | self.dependencyProvider = RCTAppDependencyProvider()
15 |
16 | // You can add your custom initial props in the dictionary below.
17 | // They will be passed down to the ViewController used by React Native.
18 | self.initialProps = [:]
19 |
20 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
21 | }
22 |
23 | override func sourceURL(for bridge: RCTBridge) -> URL? {
24 | self.bundleURL()
25 | }
26 |
27 | override func bundleURL() -> URL? {
28 | #if DEBUG
29 | RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
30 | #else
31 | Bundle.main.url(forResource: "main", withExtension: "jsbundle")
32 | #endif
33 | }
34 |
35 | // Handle URL schemes for Auth0 authentication
36 | override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
37 | return RCTLinkingManager.application(app, open: url, options: options)
38 | }
39 | }
40 | `;
41 |
--------------------------------------------------------------------------------
/src/hooks/reducer.ts:
--------------------------------------------------------------------------------
1 | import type { User } from '../types';
2 | import type { AuthError } from '../core/models';
3 | import { deepEqual } from '../core/utils/deepEqual';
4 |
5 | /**
6 | * The shape of the authentication state managed by the Auth0Provider.
7 | */
8 | export interface AuthState {
9 | user: User | null;
10 | error: AuthError | null;
11 | isLoading: boolean;
12 | }
13 |
14 | /**
15 | * The possible actions that can be dispatched to update the authentication state.
16 | * @internal
17 | */
18 | export type AuthAction =
19 | | { type: 'LOGIN_COMPLETE'; user: User }
20 | | { type: 'LOGOUT_COMPLETE' }
21 | | { type: 'ERROR'; error: AuthError }
22 | | { type: 'INITIALIZED'; user: User | null }
23 | | { type: 'SET_USER'; user: User | null };
24 |
25 | /**
26 | * A pure function that calculates the new state based on the previous state and a dispatched action.
27 | * @internal
28 | */
29 | export const reducer = (state: AuthState, action: AuthAction): AuthState => {
30 | switch (action.type) {
31 | case 'LOGIN_COMPLETE':
32 | return { ...state, error: null, isLoading: false, user: action.user };
33 | case 'LOGOUT_COMPLETE':
34 | return { ...state, error: null, user: null };
35 | case 'ERROR':
36 | return { ...state, isLoading: false, error: action.error };
37 | case 'INITIALIZED':
38 | return { ...state, isLoading: false, user: action.user };
39 | case 'SET_USER':
40 | if (deepEqual(state.user, action.user)) {
41 | return state;
42 | }
43 | return { ...state, user: action.user };
44 | }
45 | };
46 |
--------------------------------------------------------------------------------
/example/src/navigation/RootNavigator.tsx:
--------------------------------------------------------------------------------
1 | // example/src/navigation/RootNavigator.tsx
2 |
3 | import React from 'react';
4 | import { createStackNavigator } from '@react-navigation/stack';
5 | import SelectionScreen from '../screens/SelectionScreen';
6 | import HooksDemoNavigator from './HooksDemoNavigator';
7 | import ClassDemoNavigator from './ClassDemoNavigator';
8 |
9 | // Define the parameter list for type safety
10 | export type RootStackParamList = {
11 | Selection: undefined;
12 | HooksDemo: undefined;
13 | ClassDemo: undefined;
14 | };
15 |
16 | const Stack = createStackNavigator();
17 |
18 | /**
19 | * The top-level navigator that allows the user to select which
20 | * demo they want to see: the recommended Hooks-based approach or
21 | * the class-based approach.
22 | */
23 | const RootNavigator = () => {
24 | return (
25 |
29 |
34 |
39 |
44 |
45 | );
46 | };
47 |
48 | export default RootNavigator;
49 |
--------------------------------------------------------------------------------
/example/src/components/Result.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View, Text, StyleSheet, Platform } from 'react-native';
3 |
4 | type Props = {
5 | title: string;
6 | result: object | null | void;
7 | error: Error | null;
8 | };
9 |
10 | const Result = ({ title, result, error }: Props) => {
11 | if (!result && !error) {
12 | return null;
13 | }
14 |
15 | const isError = !!error;
16 | const content = error ? error.message : JSON.stringify(result, null, 2);
17 |
18 | return (
19 |
20 | {title}
21 |
22 |
23 | {content}
24 |
25 |
26 |
27 | );
28 | };
29 |
30 | const styles = StyleSheet.create({
31 | container: {
32 | marginVertical: 10,
33 | width: '100%',
34 | },
35 | title: {
36 | fontSize: 16,
37 | fontWeight: 'bold',
38 | marginBottom: 8,
39 | },
40 | resultBox: {
41 | backgroundColor: '#E8F5E9',
42 | borderColor: '#A5D6A7',
43 | borderWidth: 1,
44 | borderRadius: 8,
45 | padding: 12,
46 | },
47 | errorBox: {
48 | backgroundColor: '#FFEBEE',
49 | borderColor: '#EF9A9A',
50 | },
51 | resultText: {
52 | fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace',
53 | color: '#1B5E20',
54 | },
55 | errorText: {
56 | fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace',
57 | color: '#B71C1C',
58 | },
59 | });
60 |
61 | export default Result;
62 |
--------------------------------------------------------------------------------
/example/ios/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import React
3 | import React_RCTAppDelegate
4 | import ReactAppDependencyProvider
5 |
6 | @main
7 | class AppDelegate: UIResponder, UIApplicationDelegate {
8 | var window: UIWindow?
9 |
10 | var reactNativeDelegate: ReactNativeDelegate?
11 | var reactNativeFactory: RCTReactNativeFactory?
12 |
13 | func application(
14 | _ application: UIApplication,
15 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
16 | ) -> Bool {
17 | let delegate = ReactNativeDelegate()
18 | let factory = RCTReactNativeFactory(delegate: delegate)
19 | delegate.dependencyProvider = RCTAppDependencyProvider()
20 |
21 | reactNativeDelegate = delegate
22 | reactNativeFactory = factory
23 |
24 | window = UIWindow(frame: UIScreen.main.bounds)
25 |
26 | factory.startReactNative(
27 | withModuleName: "Auth0Example",
28 | in: window,
29 | launchOptions: launchOptions
30 | )
31 |
32 | return true
33 | }
34 |
35 | func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
36 | return RCTLinkingManager.application(app, open: url, options: options)
37 | }
38 | }
39 |
40 | class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate {
41 | override func sourceURL(for bridge: RCTBridge) -> URL? {
42 | self.bundleURL()
43 | }
44 |
45 | override func bundleURL() -> URL? {
46 | #if DEBUG
47 | RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
48 | #else
49 | Bundle.main.url(forResource: "main", withExtension: "jsbundle")
50 | #endif
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/example/src/navigation/HooksDemoNavigator.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Auth0Provider, useAuth0 } from 'react-native-auth0';
3 | import AuthStackNavigator from './AuthStackNavigator';
4 | import MainTabNavigator from './MainTabNavigator';
5 | import { ActivityIndicator, View, StyleSheet } from 'react-native';
6 | import config from '../auth0-configuration';
7 |
8 | const AUTH0_DOMAIN = config.domain;
9 | const AUTH0_CLIENT_ID = config.clientId;
10 |
11 | /**
12 | * A helper component that contains the logic to switch between the
13 | * authentication stack and the main application stack based on user state.
14 | * It's rendered inside the Auth0Provider so it can use the useAuth0 hook.
15 | */
16 | const AppContent = () => {
17 | const { user, isLoading } = useAuth0();
18 |
19 | if (isLoading) {
20 | return (
21 |
22 |
23 |
24 | );
25 | }
26 |
27 | // If user is authenticated, show the main app, otherwise show the login screen.
28 | return user ? : ;
29 | };
30 |
31 | /**
32 | * This component wraps the entire Hooks-based demo flow with the Auth0Provider,
33 | * making the authentication context available to all its child screens.
34 | */
35 | const HooksDemoNavigator = () => {
36 | return (
37 |
38 |
39 |
40 | );
41 | };
42 |
43 | const styles = StyleSheet.create({
44 | container: {
45 | flex: 1,
46 | justifyContent: 'center',
47 | alignItems: 'center',
48 | },
49 | });
50 |
51 | export default HooksDemoNavigator;
52 |
--------------------------------------------------------------------------------
/example/src/components/UserInfo.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View, Text, StyleSheet, Image } from 'react-native';
3 | import type { User } from 'react-native-auth0';
4 |
5 | type Props = {
6 | user: User | null;
7 | };
8 |
9 | const UserInfo = ({ user }: Props) => {
10 | if (!user) {
11 | return null;
12 | }
13 |
14 | return (
15 |
16 | {user.picture && (
17 |
18 | )}
19 | {user.name}
20 | {Object.entries(user).map(([key, value]) => (
21 |
22 | {key}
23 | {String(value)}
24 |
25 | ))}
26 |
27 | );
28 | };
29 |
30 | const styles = StyleSheet.create({
31 | container: {
32 | width: '100%',
33 | padding: 16,
34 | },
35 | avatar: {
36 | width: 100,
37 | height: 100,
38 | borderRadius: 50,
39 | alignSelf: 'center',
40 | marginBottom: 16,
41 | },
42 | title: {
43 | fontSize: 24,
44 | fontWeight: 'bold',
45 | textAlign: 'center',
46 | marginBottom: 20,
47 | color: '#212121',
48 | },
49 | row: {
50 | flexDirection: 'row',
51 | justifyContent: 'space-between',
52 | paddingVertical: 8,
53 | borderBottomWidth: 1,
54 | borderBottomColor: '#EEEEEE',
55 | },
56 | label: {
57 | fontSize: 16,
58 | color: '#757575',
59 | flex: 1,
60 | },
61 | value: {
62 | fontSize: 16,
63 | color: '#212121',
64 | fontWeight: '500',
65 | flex: 2,
66 | textAlign: 'right',
67 | },
68 | });
69 |
70 | export default UserInfo;
71 |
--------------------------------------------------------------------------------
/example/src/navigation/ClassDemoNavigator.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createStackNavigator } from '@react-navigation/stack';
3 | import ClassLoginScreen from '../screens/class-based/ClassLogin';
4 | import ClassProfileScreen from '../screens/class-based/ClassProfile';
5 | import ClassApiTestsScreen from '../screens/class-based/ClassApiTests';
6 | import type { Credentials } from 'react-native-auth0';
7 |
8 | /**
9 | * Defines the screens and their parameters for the class-based navigation stack.
10 | * This provides type safety for navigation calls and route props.
11 | */
12 | export type ClassDemoStackParamList = {
13 | ClassLogin: undefined;
14 | ClassProfile: { credentials: Credentials }; // Expects credentials to be passed after login
15 | ClassApiTests: { accessToken: string }; // Expects an access token for API calls
16 | };
17 |
18 | const Stack = createStackNavigator();
19 |
20 | /**
21 | * The navigator for the entire Class-based demo flow.
22 | *
23 | * It does NOT use an Auth0Provider, demonstrating how to use the SDK
24 | * by importing and calling the Auth0 class instance directly.
25 | */
26 | const ClassDemoNavigator = () => {
27 | return (
28 |
29 |
34 |
39 |
44 |
45 | );
46 | };
47 |
48 | export default ClassDemoNavigator;
49 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Auth0Example",
3 | "version": "0.0.1",
4 | "private": true,
5 | "scripts": {
6 | "android": "react-native run-android",
7 | "ios": "react-native run-ios",
8 | "web": "webpack serve --mode development",
9 | "web:build": "webpack --mode production",
10 | "start": "react-native start",
11 | "build:android": "react-native build-android --extra-params \"--no-daemon --console=plain -PreactNativeArchitectures=arm64-v8a\"",
12 | "build:ios": "react-native build-ios --mode Debug"
13 | },
14 | "dependencies": {
15 | "@react-navigation/bottom-tabs": "^7.8.4",
16 | "@react-navigation/native": "^7.1.20",
17 | "@react-navigation/stack": "^7.6.4",
18 | "react": "19.1.1",
19 | "react-native": "0.82.1",
20 | "react-native-gesture-handler": "^2.29.1",
21 | "react-native-safe-area-context": "^5.6.2",
22 | "react-native-screens": "^4.18.0",
23 | "react-native-web": "^0.21.2"
24 | },
25 | "devDependencies": {
26 | "@babel/core": "^7.25.2",
27 | "@babel/preset-env": "^7.25.3",
28 | "@babel/runtime": "^7.25.0",
29 | "@react-native-community/cli": "20.0.2",
30 | "@react-native-community/cli-platform-android": "20.0.2",
31 | "@react-native-community/cli-platform-ios": "20.0.2",
32 | "@react-native/babel-preset": "0.82.1",
33 | "@react-native/metro-config": "0.82.1",
34 | "@react-native/typescript-config": "0.82.1",
35 | "@types/react": "^19.2.2",
36 | "babel-loader": "^10.0.0",
37 | "babel-plugin-react-native-web": "^0.21.2",
38 | "html-webpack-plugin": "^5.6.5",
39 | "react-native-builder-bob": "^0.40.15",
40 | "react-native-monorepo-config": "^0.1.9",
41 | "url-loader": "^4.1.1",
42 | "webpack": "^5.99.9",
43 | "webpack-cli": "^6.0.1",
44 | "webpack-dev-server": "^5.2.2"
45 | },
46 | "engines": {
47 | "node": ">=20"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/android/src/main/java/com/auth0/react/CredentialsParser.kt:
--------------------------------------------------------------------------------
1 | package com.auth0.react
2 |
3 | import com.auth0.android.result.Credentials
4 | import com.facebook.react.bridge.ReadableMap
5 | import com.facebook.react.bridge.WritableNativeMap
6 | import java.util.Date
7 |
8 | object CredentialsParser {
9 |
10 | private const val ACCESS_TOKEN_KEY = "accessToken"
11 | private const val ID_TOKEN_KEY = "idToken"
12 | private const val EXPIRES_AT_KEY = "expiresAt"
13 | private const val SCOPE = "scope"
14 | private const val REFRESH_TOKEN_KEY = "refreshToken"
15 | private const val TOKEN_TYPE_KEY = "tokenType"
16 | private const val DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
17 |
18 | fun toMap(credentials: Credentials): ReadableMap {
19 | val map = WritableNativeMap()
20 | map.putString(ACCESS_TOKEN_KEY, credentials.accessToken)
21 | map.putDouble(EXPIRES_AT_KEY, credentials.expiresAt.time / 1000.0)
22 | map.putString(ID_TOKEN_KEY, credentials.idToken)
23 | map.putString(SCOPE, credentials.scope)
24 | map.putString(REFRESH_TOKEN_KEY, credentials.refreshToken)
25 | map.putString(TOKEN_TYPE_KEY, credentials.type)
26 | return map
27 | }
28 |
29 | fun fromMap(map: ReadableMap): Credentials {
30 | val idToken = map.getString(ID_TOKEN_KEY) ?: ""
31 | val accessToken = map.getString(ACCESS_TOKEN_KEY) ?: ""
32 | val tokenType = map.getString(TOKEN_TYPE_KEY) ?: ""
33 | val refreshToken = map.getString(REFRESH_TOKEN_KEY)
34 | val scope = map.getString(SCOPE)
35 | val expiresAtUnix = map.getDouble(EXPIRES_AT_KEY)
36 | val expiresAt = Date((expiresAtUnix * 1000).toLong())
37 |
38 | return Credentials(
39 | idToken,
40 | accessToken,
41 | tokenType,
42 | refreshToken,
43 | expiresAt,
44 | scope
45 | )
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/example/ios/Podfile:
--------------------------------------------------------------------------------
1 | def find_and_replace_boost_url
2 | pod_spec = "../node_modules/react-native/third-party-podspecs/boost.podspec"
3 | puts "Debug: Starting boost URL replacement"
4 | if File.exist?(pod_spec)
5 | puts "Debug: Found boost.podspec"
6 | spec_content = File.read(pod_spec)
7 | spec_content.gsub!(
8 | 'https://boostorg.jfrog.io/artifactory/main/release/1.76.0/source/boost_1_76_0.tar.bz2',
9 | 'https://archives.boost.io/release/1.76.0/source/boost_1_76_0.tar.bz2'
10 | )
11 | File.write(pod_spec, spec_content)
12 | puts "Debug: Updated boost.podspec"
13 | end
14 | end
15 |
16 | ENV['RCT_NEW_ARCH_ENABLED'] = '1'
17 |
18 |
19 | # Let the magic happen!
20 | find_and_replace_boost_url
21 |
22 | # Resolve react_native_pods.rb with node to allow for hoisting
23 | require Pod::Executable.execute_command('node', ['-p',
24 | 'require.resolve(
25 | "react-native/scripts/react_native_pods.rb",
26 | {paths: [process.argv[1]]},
27 | )', __dir__]).strip
28 |
29 | platform :ios, min_ios_version_supported
30 | prepare_react_native_project!
31 |
32 | linkage = ENV['USE_FRAMEWORKS']
33 | if linkage != nil
34 | Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green
35 | use_frameworks! :linkage => linkage.to_sym
36 | end
37 |
38 | target 'Auth0Example' do
39 | config = use_native_modules!
40 |
41 | use_react_native!(
42 | :path => config[:reactNativePath],
43 | # An absolute path to your application root.
44 | :app_path => "#{Pod::Config.instance.installation_root}/.."
45 | )
46 |
47 | post_install do |installer|
48 | react_native_post_install(
49 | installer,
50 | )
51 |
52 | installer.pods_project.targets.each do |target|
53 | target.build_configurations.each do |config|
54 | config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= ['$(inherited)', '_LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION']
55 | end
56 | end
57 | end
58 | end
59 |
--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import { fixupConfigRules } from '@eslint/compat';
2 | import { FlatCompat } from '@eslint/eslintrc';
3 | import js from '@eslint/js';
4 | import prettier from 'eslint-plugin-prettier';
5 | import tseslint from 'typescript-eslint';
6 | import { defineConfig } from 'eslint/config';
7 | import path from 'node:path';
8 | import { fileURLToPath } from 'node:url';
9 |
10 | const __filename = fileURLToPath(import.meta.url);
11 | const __dirname = path.dirname(__filename);
12 | const compat = new FlatCompat({
13 | baseDirectory: __dirname,
14 | recommendedConfig: js.configs.recommended,
15 | allConfig: js.configs.all,
16 | });
17 |
18 | export default defineConfig([
19 | {
20 | extends: fixupConfigRules(compat.extends('@react-native', 'prettier')),
21 | plugins: { prettier },
22 | rules: {
23 | 'react/react-in-jsx-scope': 'off',
24 | 'prettier/prettier': [
25 | 'error',
26 | {
27 | quoteProps: 'consistent',
28 | singleQuote: true,
29 | tabWidth: 2,
30 | trailingComma: 'es5',
31 | useTabs: false,
32 | },
33 | ],
34 | },
35 | },
36 | // TypeScript-specific configuration for type-checked rules
37 | {
38 | files: ['**/*.ts', '**/*.tsx'],
39 | ignores: [
40 | '**/__tests__/**',
41 | '**/__mocks__/**',
42 | '**/*.spec.ts',
43 | '**/*.spec.tsx',
44 | '**/*.test.ts',
45 | '**/*.test.tsx',
46 | ],
47 | languageOptions: {
48 | parserOptions: {
49 | project: true,
50 | tsconfigRootDir: import.meta.dirname,
51 | },
52 | },
53 | rules: {
54 | // Enable unbound-method rule to catch interface typing issues
55 | // This helps ensure methods in interfaces use arrow function syntax
56 | // instead of method syntax when they don't use 'this'
57 | '@typescript-eslint/unbound-method': 'error',
58 | },
59 | },
60 | {
61 | ignores: ['node_modules/', 'lib/', 'docs'],
62 | },
63 | ]);
64 |
--------------------------------------------------------------------------------
/__mocks__/react-native.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This file provides a project-wide, self-contained mock for the 'react-native' package.
3 | */
4 |
5 | const React = require('react');
6 |
7 | // A fake native module to be returned by TurboModuleRegistry.getEnforcing.
8 | const mockNativeModule = {
9 | // ... (all the mock native methods from before)
10 | initializeAuth0WithConfiguration: jest.fn(() => Promise.resolve()),
11 | webAuth: jest.fn(() => Promise.resolve({})),
12 | webAuthLogout: jest.fn(() => Promise.resolve()),
13 | cancelWebAuth: jest.fn(() => Promise.resolve()),
14 | getBundleIdentifier: jest.fn(() => Promise.resolve('com.my.app')),
15 | resumeWebAuth: jest.fn(() => Promise.resolve(true)),
16 | saveCredentials: jest.fn(() => Promise.resolve()),
17 | getCredentials: jest.fn(() => Promise.resolve({})),
18 | hasValidCredentials: jest.fn(() => Promise.resolve(true)),
19 | clearCredentials: jest.fn(() => Promise.resolve()),
20 | hasValidInstance: jest.fn(() => Promise.resolve(true)),
21 | };
22 |
23 | // Export the mocked module.
24 | module.exports = {
25 | // Mock the Platform module
26 | Platform: {
27 | OS: 'ios',
28 | },
29 | // Mock the TurboModuleRegistry
30 | TurboModuleRegistry: {
31 | getEnforcing: () => mockNativeModule,
32 | },
33 | // Mock the Linking module
34 | Linking: {
35 | addEventListener: jest.fn(() => ({ remove: jest.fn() })),
36 | removeEventListener: jest.fn(),
37 | getInitialURL: jest.fn(() => Promise.resolve(null)),
38 | },
39 |
40 | // FIX: Add mocks for core UI components used in tests.
41 | // We can use a simple functional component that just renders its children
42 | // and passes along any props.
43 | View: (props) => React.createElement('View', props, props.children),
44 | Text: (props) => React.createElement('Text', props, props.children),
45 | Button: (props) => React.createElement('Button', props, props.children),
46 | // Add any other components your tests might use, e.g., StyleSheet
47 | StyleSheet: {
48 | create: (styles) => styles,
49 | },
50 | };
51 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/drawable/rn_edit_text_material.xml:
--------------------------------------------------------------------------------
1 |
2 |
16 |
22 |
23 |
24 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/example/src/screens/hooks/Api.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { SafeAreaView, ScrollView, StyleSheet } from 'react-native';
3 | import Auth0, { useAuth0, User } from 'react-native-auth0';
4 | import Button from '../../components/Button';
5 | import Header from '../../components/Header';
6 | import Result from '../../components/Result';
7 | import config from '../../auth0-configuration';
8 |
9 | const AUTH0_DOMAIN = config.domain;
10 | const AUTH0_CLIENT_ID = config.clientId;
11 |
12 | const auth0 = new Auth0({ domain: AUTH0_DOMAIN, clientId: AUTH0_CLIENT_ID });
13 |
14 | const ApiScreen = () => {
15 | const { user, getCredentials } = useAuth0();
16 | const [apiResult, setApiResult] = useState(null);
17 | const [apiError, setApiError] = useState(null);
18 |
19 | const onCallApi = async () => {
20 | try {
21 | const credentials = await getCredentials(
22 | 'openid profile email read:current_user'
23 | );
24 | if (!credentials || !user?.sub) {
25 | throw new Error('Could not get credentials or user ID.');
26 | }
27 |
28 | const managementClient = auth0.users(credentials.accessToken);
29 | const fullProfile = await managementClient.getUser({ id: user.sub });
30 |
31 | setApiResult(fullProfile);
32 | setApiError(null);
33 | } catch (e) {
34 | setApiError(e as Error);
35 | }
36 | };
37 |
38 | return (
39 |
40 |
41 |
42 |
47 |
48 |
49 |
50 | );
51 | };
52 |
53 | const styles = StyleSheet.create({
54 | container: {
55 | flex: 1,
56 | backgroundColor: '#FFFFFF',
57 | },
58 | content: {
59 | alignItems: 'center',
60 | padding: 16,
61 | },
62 | });
63 |
64 | export default ApiScreen;
65 |
--------------------------------------------------------------------------------
/src/core/interfaces/IAuthenticationProvider.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | Credentials,
3 | User,
4 | MfaChallengeResponse,
5 | PasswordRealmParameters,
6 | RefreshTokenParameters,
7 | UserInfoParameters,
8 | RevokeOptions,
9 | ExchangeParameters,
10 | ExchangeNativeSocialParameters,
11 | PasswordlessEmailParameters,
12 | PasswordlessSmsParameters,
13 | LoginEmailParameters,
14 | LoginSmsParameters,
15 | LoginOtpParameters,
16 | LoginOobParameters,
17 | LoginRecoveryCodeParameters,
18 | MfaChallengeParameters,
19 | ResetPasswordParameters,
20 | CreateUserParameters,
21 | } from '../../types';
22 |
23 | /**
24 | * Defines the contract for direct authentication methods that interact with Auth0's
25 | * Authentication API endpoints without a web-based redirect.
26 | */
27 | export interface IAuthenticationProvider {
28 | passwordRealm(parameters: PasswordRealmParameters): Promise;
29 | refreshToken(parameters: RefreshTokenParameters): Promise;
30 | userInfo(parameters: UserInfoParameters): Promise;
31 | revoke(parameters: RevokeOptions): Promise;
32 | exchange(parameters: ExchangeParameters): Promise;
33 | passwordlessWithEmail(parameters: PasswordlessEmailParameters): Promise;
34 | passwordlessWithSMS(parameters: PasswordlessSmsParameters): Promise;
35 | loginWithEmail(parameters: LoginEmailParameters): Promise;
36 | loginWithSMS(parameters: LoginSmsParameters): Promise;
37 | loginWithOTP(parameters: LoginOtpParameters): Promise;
38 | loginWithOOB(parameters: LoginOobParameters): Promise;
39 | loginWithRecoveryCode(
40 | parameters: LoginRecoveryCodeParameters
41 | ): Promise;
42 | multifactorChallenge(
43 | parameters: MfaChallengeParameters
44 | ): Promise;
45 | resetPassword(parameters: ResetPasswordParameters): Promise;
46 | createUser(parameters: CreateUserParameters): Promise>;
47 |
48 | exchangeNativeSocial(
49 | parameters: ExchangeNativeSocialParameters
50 | ): Promise;
51 | }
52 |
--------------------------------------------------------------------------------
/example/android/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx512m -XX:MaxMetaspaceSize=256m
13 | org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
19 |
20 | # AndroidX package structure to make it clearer which packages are bundled with the
21 | # Android operating system, and which are packaged with your app's APK
22 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
23 | android.useAndroidX=true
24 |
25 | # Use this property to specify which architecture you want to build.
26 | # You can also override it from the CLI using
27 | # ./gradlew -PreactNativeArchitectures=x86_64
28 | reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
29 |
30 | # Use this property to enable support to the new architecture.
31 | # This will allow you to use TurboModules and the Fabric render in
32 | # your application. You should enable this flag either if you want
33 | # to write custom TurboModules/Fabric components OR use libraries that
34 | # are providing them.
35 | newArchEnabled=true
36 |
37 | # Use this property to enable or disable the Hermes JS engine.
38 | # If set to false, you will be using JSC instead.
39 | hermesEnabled=true
40 |
41 | # Use this property to enable edge-to-edge display support.
42 | # This allows your app to draw behind system bars for an immersive UI.
43 | # Note: Only works with ReactActivity and should not be used with custom Activity.
44 | edgeToEdgeEnabled=false
--------------------------------------------------------------------------------
/src/core/utils/conversion.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * A private helper that converts a single snake_case or kebab-case string to camelCase.
3 | * e.g., 'user_profile' -> 'userProfile'
4 | */
5 | export function snakeToCamel(str: string): string {
6 | var parts = str.split('_').filter((part) => part.length > 0);
7 | if (parts.length === 0) return '';
8 |
9 | return parts.reduce(function (p, c, index) {
10 | if (index === 0) {
11 | return c.charAt(0).toLowerCase() + c.slice(1);
12 | }
13 | return p + c.charAt(0).toUpperCase() + c.slice(1);
14 | }, '');
15 | }
16 |
17 | /**
18 | * Recursively traverses an object or an array and converts all keys from
19 | * snake_case to camelCase.
20 | *
21 | * @param data The object or array to be transformed.
22 | * @returns A new object or array with all keys in camelCase.
23 | */
24 | export function deepCamelCase(data: any): T {
25 | if (Array.isArray(data)) {
26 | return data.map((v) => deepCamelCase(v)) as T;
27 | }
28 | if (data && typeof data === 'object' && data.constructor === Object) {
29 | const newObj: { [key: string]: any } = {};
30 | for (const key in data) {
31 | if (Object.prototype.hasOwnProperty.call(data, key)) {
32 | const camelKey = snakeToCamel(key);
33 | newObj[camelKey] = deepCamelCase(data[key]);
34 | }
35 | }
36 | return newObj as T;
37 | }
38 | return data as T;
39 | }
40 |
41 | /**
42 | * Converts a JavaScript object into a URL-encoded query string.
43 | *
44 | * @remarks
45 | * Keys with `null` or `undefined` values are ignored.
46 | *
47 | * @example
48 | * ```
49 | * toUrlQueryParams({ a: 1, b: 'hello world' })
50 | * // returns "a=1&b=hello%20world"
51 | * ```
52 | *
53 | * @param params The object of parameters to convert.
54 | * @returns A URL-encoded query string.
55 | */
56 | export function toUrlQueryParams(params: Record): string {
57 | const searchParams = new URLSearchParams();
58 | for (const key in params) {
59 | if (Object.prototype.hasOwnProperty.call(params, key)) {
60 | const value = params[key];
61 | if (value !== null && value !== undefined) {
62 | searchParams.append(key, String(value));
63 | }
64 | }
65 | }
66 | return searchParams.toString();
67 | }
68 |
--------------------------------------------------------------------------------
/android/src/main/java/com/auth0/react/LocalAuthenticationOptionsParser.kt:
--------------------------------------------------------------------------------
1 | package com.auth0.react
2 |
3 | import com.auth0.android.authentication.storage.AuthenticationLevel
4 | import com.auth0.android.authentication.storage.LocalAuthenticationOptions
5 | import com.facebook.react.bridge.ReadableMap
6 |
7 | object LocalAuthenticationOptionsParser {
8 | private const val TITLE_KEY = "title"
9 | private const val SUBTITLE_KEY = "subtitle"
10 | private const val DESCRIPTION_KEY = "description"
11 | private const val CANCEL_TITLE_KEY = "cancel"
12 | private const val AUTHENTICATION_LEVEL_KEY = "authenticationLevel"
13 | private const val DEVICE_CREDENTIAL_FALLBACK_KEY = "deviceCredentialFallback"
14 |
15 | fun fromMap(map: ReadableMap): LocalAuthenticationOptions {
16 | val title = map.getString(TITLE_KEY)
17 | ?: throw IllegalArgumentException("LocalAuthenticationOptionsParser: fromMap: The 'title' field is required")
18 |
19 | val subtitle = map.getString(SUBTITLE_KEY)
20 | val description = map.getString(DESCRIPTION_KEY)
21 | val cancelTitle = map.getString(CANCEL_TITLE_KEY)
22 | val deviceCredentialFallback = map.getBoolean(DEVICE_CREDENTIAL_FALLBACK_KEY)
23 |
24 | val builder = LocalAuthenticationOptions.Builder()
25 | .setTitle(title)
26 | .setSubTitle(subtitle)
27 | .setDescription(description)
28 | .setDeviceCredentialFallback(deviceCredentialFallback)
29 |
30 | if (!map.hasKey(AUTHENTICATION_LEVEL_KEY)) {
31 | builder.setAuthenticationLevel(AuthenticationLevel.STRONG)
32 | } else {
33 | val level = getAuthenticationLevelFromInt(map.getInt(AUTHENTICATION_LEVEL_KEY))
34 | builder.setAuthenticationLevel(level)
35 | }
36 |
37 | cancelTitle?.let { builder.setNegativeButtonText(it) }
38 |
39 | return builder.build()
40 | }
41 |
42 | private fun getAuthenticationLevelFromInt(level: Int): AuthenticationLevel {
43 | return when (level) {
44 | 0 -> AuthenticationLevel.STRONG
45 | 1 -> AuthenticationLevel.WEAK
46 | else -> AuthenticationLevel.DEVICE_CREDENTIAL
47 | }
48 | }
49 | }
50 |
51 |
--------------------------------------------------------------------------------
/src/core/models/Credentials.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | Credentials as ICredentials,
3 | NativeCredentialsResponse,
4 | } from '../../types';
5 |
6 | /**
7 | * A class representation of user credentials.
8 | * It encapsulates the tokens and provides helper methods for convenience.
9 | */
10 | export class Credentials implements ICredentials {
11 | public idToken: string;
12 | public accessToken: string;
13 | public tokenType: string;
14 | public expiresAt: number;
15 | public refreshToken?: string;
16 | public scope?: string;
17 |
18 | /**
19 | * Creates an instance of Credentials.
20 | *
21 | * @param params An object conforming to the Credentials type definition.
22 | */
23 | constructor(params: ICredentials) {
24 | this.idToken = params.idToken;
25 | this.accessToken = params.accessToken;
26 | this.tokenType = params.tokenType;
27 | this.expiresAt = params.expiresAt;
28 | this.refreshToken = params.refreshToken;
29 | this.scope = params.scope;
30 | }
31 |
32 | /**
33 | * Checks if the access token is expired.
34 | *
35 | * @param [leeway=0] - A buffer in seconds to account for clock skew. The token will be
36 | * considered expired if it expires within this buffer.
37 | * @returns `true` if the token is expired, `false` otherwise.
38 | */
39 | isExpired(leeway: number = 0): boolean {
40 | const nowInSeconds = Math.floor(Date.now() / 1000);
41 | return this.expiresAt <= nowInSeconds + leeway;
42 | }
43 |
44 | /**
45 | * A static factory method to create a Credentials instance from a raw OAuth2
46 | * token response, which typically includes `expires_in` instead of `expiresAt`.
47 | *
48 | * @param response The raw token response from the server.
49 | * @returns A new Credentials instance.
50 | */
51 | static fromResponse(response: NativeCredentialsResponse): Credentials {
52 | const nowInSeconds = Math.floor(Date.now() / 1000);
53 | const expiresAt = nowInSeconds + response.expires_in;
54 |
55 | return new Credentials({
56 | idToken: response.id_token,
57 | accessToken: response.access_token,
58 | tokenType: response.token_type,
59 | expiresAt: expiresAt,
60 | refreshToken: response.refresh_token,
61 | scope: response.scope,
62 | });
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/example/ios/Auth0Example/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CADisableMinimumFrameDurationOnPhone
6 |
7 | CFBundleDevelopmentRegion
8 | en
9 | CFBundleDisplayName
10 | Auth0Example
11 | CFBundleExecutable
12 | $(EXECUTABLE_NAME)
13 | CFBundleIdentifier
14 | $(PRODUCT_BUNDLE_IDENTIFIER)
15 | CFBundleInfoDictionaryVersion
16 | 6.0
17 | CFBundleName
18 | $(PRODUCT_NAME)
19 | CFBundlePackageType
20 | APPL
21 | CFBundleShortVersionString
22 | $(MARKETING_VERSION)
23 | CFBundleSignature
24 | ????
25 | CFBundleURLTypes
26 |
27 |
28 | CFBundleTypeRole
29 | None
30 | CFBundleURLName
31 | auth0
32 | CFBundleURLSchemes
33 |
34 | $(PRODUCT_BUNDLE_IDENTIFIER).auth0
35 |
36 |
37 |
38 | CFBundleVersion
39 | $(CURRENT_PROJECT_VERSION)
40 | LSRequiresIPhoneOS
41 |
42 | NSAppTransportSecurity
43 |
44 | NSAllowsArbitraryLoads
45 |
46 | NSAllowsLocalNetworking
47 |
48 |
49 | NSFaceIDUsageDescription
50 | Authenticate to retrieve Credentials
51 | NSLocationWhenInUseUsageDescription
52 |
53 | RCTNewArchEnabled
54 |
55 | UILaunchStoryboardName
56 | LaunchScreen
57 | UIRequiredDeviceCapabilities
58 |
59 | armv7
60 |
61 | UISupportedInterfaceOrientations
62 |
63 | UIInterfaceOrientationPortrait
64 | UIInterfaceOrientationLandscapeLeft
65 | UIInterfaceOrientationLandscapeRight
66 |
67 | UIViewControllerBasedStatusBarAppearance
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/.github/workflows/publish-docs.yml:
--------------------------------------------------------------------------------
1 | name: PUBLISH DOCS
2 | on:
3 | workflow_dispatch:
4 | workflow_call:
5 | # or set up your own custom triggers
6 | permissions:
7 | contents: write # allows the 'Commit' step without tokens
8 |
9 | jobs:
10 | get_history: # create an artifact from the existing documentation builds
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: get the gh-pages repo
14 | uses: actions/checkout@v6
15 | with:
16 | ref: gh-pages
17 |
18 | - name: remove all symbolic links from root if present
19 | run: |
20 | find . -maxdepth 1 -type l -delete
21 |
22 | - name: tar the existing docs from root
23 | run: |
24 | tar -cvf documentation.tar ./
25 |
26 | - name: create a document artifact
27 | uses: actions/upload-artifact@v6
28 | with:
29 | name: documentation
30 | path: documentation.tar
31 | retention-days: 1
32 |
33 | build_and_deploy: # builds the distribution and then the documentation
34 | needs: get_history
35 | runs-on: ubuntu-latest
36 | permissions:
37 | contents: write
38 | steps:
39 | - name: Checkout src
40 | uses: actions/checkout@v6
41 | with:
42 | token: ${{ github.token }}
43 |
44 | - name: Download the existing documents artifact
45 | uses: actions/download-artifact@v7
46 | with:
47 | name: documentation
48 | - run: rm -rf ./docs # delete previous docs folder present
49 | - run: mkdir ./docs # create an empty docs folder
50 | - run: tar -xf documentation.tar -C ./docs
51 | - run: rm -f documentation.tar
52 |
53 | - name: Setup
54 | uses: ./.github/actions/setup
55 |
56 | - name: Build documents
57 | run: yarn docs #set up 'docs' build script in your package.json
58 |
59 | - name: Remove all the symbolic links from docs folder
60 | run: find ./docs -type l -delete
61 |
62 | - name: Run cleanup and manage document versions
63 | run: node scripts/manage-doc-versions.js
64 |
65 | - name: Deploy to GitHub Pages
66 | uses: peaceiris/actions-gh-pages@v4
67 | with:
68 | github_token: ${{ github.token }}
69 | publish_dir: ./docs
70 | keep_files: false
71 |
--------------------------------------------------------------------------------
/example/src/screens/class-based/ClassLogin.tsx:
--------------------------------------------------------------------------------
1 | // example/src/screens/class-based/ClassLogin.tsx
2 |
3 | import React, { useState } from 'react';
4 | import { SafeAreaView, View, StyleSheet } from 'react-native';
5 | import { useNavigation } from '@react-navigation/native';
6 | import type { StackNavigationProp } from '@react-navigation/stack';
7 | import auth0 from '../../api/auth0'; // Import our singleton instance
8 | import Button from '../../components/Button';
9 | import Header from '../../components/Header';
10 | import Result from '../../components/Result';
11 | import type { ClassDemoStackParamList } from '../../navigation/ClassDemoNavigator';
12 | import config from '../../auth0-configuration';
13 |
14 | type NavigationProp = StackNavigationProp<
15 | ClassDemoStackParamList,
16 | 'ClassLogin'
17 | >;
18 |
19 | const ClassLoginScreen = () => {
20 | const [error, setError] = useState(null);
21 | const [loading, setLoading] = useState(false);
22 | const navigation = useNavigation();
23 |
24 | const onLogin = async () => {
25 | setLoading(true);
26 | setError(null);
27 | try {
28 | const credentials = await auth0.webAuth.authorize({
29 | scope: 'openid profile email offline_access',
30 | audience: `https://${config.domain}/api/v2/`,
31 | });
32 | // On success, we save the credentials and navigate to the profile screen.
33 | await auth0.credentialsManager.saveCredentials(credentials);
34 | navigation.replace('ClassProfile', { credentials });
35 | } catch (e) {
36 | setError(e as Error);
37 | } finally {
38 | setLoading(false);
39 | }
40 | };
41 |
42 | return (
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | );
51 | };
52 |
53 | const styles = StyleSheet.create({
54 | container: {
55 | flex: 1,
56 | backgroundColor: '#FFFFFF',
57 | },
58 | content: {
59 | flex: 1,
60 | justifyContent: 'center',
61 | alignItems: 'center',
62 | padding: 16,
63 | },
64 | });
65 |
66 | export default ClassLoginScreen;
67 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/Feature Request.yml:
--------------------------------------------------------------------------------
1 | name: 🧩 Feature request
2 | description: Suggest an idea or a feature for this library
3 | labels: ['feature request']
4 |
5 | body:
6 | - type: checkboxes
7 | id: checklist
8 | attributes:
9 | label: Checklist
10 | options:
11 | - label: I have looked into the [Readme](https://github.com/auth0/react-native-auth0#readme), [Examples](https://github.com/auth0/react-native-auth0/blob/master/EXAMPLES.md), and [FAQ](https://github.com/auth0/react-native-auth0/blob/master/FAQ.md) and have not found a suitable solution or answer.
12 | required: true
13 | - label: I have looked into the [API documentation](https://auth0.github.io/react-native-auth0/) and have not found a suitable solution or answer.
14 | required: true
15 | - label: I have searched the [issues](https://github.com/auth0/react-native-auth0/issues) and have not found a suitable solution or answer.
16 | required: true
17 | - label: I have searched the [Auth0 Community](https://community.auth0.com) forums and have not found a suitable solution or answer.
18 | required: true
19 | - label: I agree to the terms within the [Auth0 Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md).
20 | required: true
21 |
22 | - type: textarea
23 | id: description
24 | attributes:
25 | label: Describe the problem you'd like to have solved
26 | description: A clear and concise description of what the problem is.
27 | placeholder: I'm always frustrated when...
28 | validations:
29 | required: true
30 |
31 | - type: textarea
32 | id: ideal-solution
33 | attributes:
34 | label: Describe the ideal solution
35 | description: A clear and concise description of what you want to happen.
36 | validations:
37 | required: true
38 |
39 | - type: textarea
40 | id: alternatives-and-workarounds
41 | attributes:
42 | label: Alternatives and current workarounds
43 | description: A clear and concise description of any alternatives you've considered or any workarounds that are currently in place.
44 | validations:
45 | required: false
46 |
47 | - type: textarea
48 | id: additional-context
49 | attributes:
50 | label: Additional context
51 | description: Add any other context or screenshots about the feature request here.
52 | validations:
53 | required: false
54 |
--------------------------------------------------------------------------------
/typedoc.json:
--------------------------------------------------------------------------------
1 | {
2 | "entryPoints": ["src/exports/index.ts"],
3 | "out": "docs",
4 | "name": "React Native Auth0",
5 | "tsconfig": "./tsconfig.json",
6 | "readme": "README.md",
7 | "theme": "default",
8 | "includeVersion": true,
9 | "excludePrivate": true,
10 | "excludeProtected": true,
11 | "excludeExternals": true,
12 | "json": "docs/index.json",
13 | "sort": ["source-order"],
14 | "kindSortOrder": [
15 | "Project",
16 | "Module",
17 | "Namespace",
18 | "Enum",
19 | "Class",
20 | "Interface",
21 | "TypeAlias",
22 | "Constructor",
23 | "Property",
24 | "Method",
25 | "Function",
26 | "Accessor",
27 | "Variable"
28 | ],
29 | "plugin": ["typedoc-plugin-missing-exports", "typedoc-plugin-replace-text", "@shipgirl/typedoc-plugin-versions"],
30 | "replaceText": {
31 | "replacements": [
32 | { "pattern": "```objective-c", "replace": "```text" },
33 | { "pattern": "```objc", "replace": "```text" },
34 | { "pattern": "```groovy", "replace": "```text" },
35 | { "pattern": "```xml", "replace": "```text" },
36 | { "pattern": "```swift", "replace": "```text" },
37 | { "pattern": "```ruby", "replace": "```text" },
38 | { "pattern": "```javascript", "replace": "```text" }
39 | ]
40 | },
41 | "githubPages": false,
42 | "hideGenerator": true,
43 | "searchInComments": true,
44 | "validation": {
45 | "notExported": true,
46 | "invalidLink": true,
47 | "notDocumented": false
48 | },
49 | "treatWarningsAsErrors": false,
50 | "blockTags": [
51 | "@example",
52 | "@deprecated",
53 | "@throws",
54 | "@since",
55 | "@see",
56 | "@remarks",
57 | "@param",
58 | "@returns",
59 | "@default"
60 | ],
61 | "modifierTags": [
62 | "@public",
63 | "@private",
64 | "@protected",
65 | "@internal",
66 | "@hidden",
67 | "@override",
68 | "@readonly"
69 | ],
70 | "categorizeByGroup": true,
71 | "groupOrder": [
72 | "Core Interfaces",
73 | "Platform-Specific Types",
74 | "Factory Functions",
75 | "Utility Functions",
76 | "React Hooks",
77 | "*"
78 | ],
79 | "navigationLinks": {
80 | "GitHub": "https://github.com/auth0/react-native-auth0",
81 | "NPM": "https://npmjs.com/package/react-native-auth0",
82 | "Auth0 Docs": "https://auth0.com/docs"
83 | },
84 | "placeInternalsInOwningModule": true,
85 | "versions": {
86 | "domLocation": "top",
87 | "makeRelativeLinks": true
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/platforms/web/adapters/__tests__/WebCredentialsManager.errors.spec.ts:
--------------------------------------------------------------------------------
1 | import { Auth0Client } from '@auth0/auth0-spa-js';
2 | import { WebCredentialsManager } from '../WebCredentialsManager';
3 | import { CredentialsManagerError } from '../../../../core/models';
4 |
5 | jest.mock('@auth0/auth0-spa-js');
6 |
7 | describe('WebCredentialsManager Error Handling', () => {
8 | let mockSpaClient: jest.Mocked;
9 | let manager: WebCredentialsManager;
10 |
11 | beforeEach(() => {
12 | jest.clearAllMocks();
13 | mockSpaClient = new (Auth0Client as jest.Mock)({
14 | domain: 'test.auth0.com',
15 | clientId: 'test-client-id',
16 | });
17 | manager = new WebCredentialsManager(mockSpaClient);
18 | });
19 |
20 | describe('Web Error Mappings', () => {
21 | const webErrorTestCases = [
22 | {
23 | code: 'login_required',
24 | message: 'Login is required.',
25 | expectedType: 'NO_CREDENTIALS',
26 | },
27 | {
28 | code: 'consent_required',
29 | message: 'Consent is required.',
30 | expectedType: 'RENEW_FAILED',
31 | },
32 | {
33 | code: 'mfa_required',
34 | message: 'Multi-factor authentication is required.',
35 | expectedType: 'RENEW_FAILED',
36 | },
37 | {
38 | code: 'invalid_grant',
39 | message: 'Invalid grant provided.',
40 | expectedType: 'RENEW_FAILED',
41 | },
42 | {
43 | code: 'invalid_refresh_token',
44 | message: 'Invalid refresh token.',
45 | expectedType: 'RENEW_FAILED',
46 | },
47 | {
48 | code: 'missing_refresh_token',
49 | message: 'Missing refresh token.',
50 | expectedType: 'NO_REFRESH_TOKEN',
51 | },
52 | ];
53 |
54 | webErrorTestCases.forEach(({ code, message, expectedType }) => {
55 | it(`should map ${code} to ${expectedType}`, async () => {
56 | const spaJsError = { error: code, error_description: message };
57 | mockSpaClient.getTokenSilently.mockRejectedValue(spaJsError);
58 |
59 | await expect(manager.getCredentials()).rejects.toThrow(
60 | CredentialsManagerError
61 | );
62 |
63 | try {
64 | await manager.getCredentials();
65 | } catch (e) {
66 | const err = e as CredentialsManagerError;
67 | expect(err.type).toBe(expectedType);
68 | expect(err.message).toBe(message);
69 | }
70 | });
71 | });
72 | });
73 | });
74 |
--------------------------------------------------------------------------------
/.github/workflows/npm-release.yml:
--------------------------------------------------------------------------------
1 | name: Create npm and GitHub Release
2 |
3 | on:
4 | workflow_call:
5 | inputs:
6 | release-directory:
7 | default: './'
8 | type: string
9 | secrets:
10 | github-token:
11 | required: true
12 |
13 | jobs:
14 | release:
15 | if: github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.event.pull_request.merged && startsWith(github.event.pull_request.head.ref, 'release/'))
16 | runs-on: ubuntu-latest
17 | environment: release
18 | permissions:
19 | contents: write
20 | id-token: write
21 |
22 | steps:
23 | # Checkout the code
24 | - uses: actions/checkout@v6
25 | with:
26 | fetch-depth: 0
27 |
28 | # Get the version from the branch name
29 | - id: get_version
30 | uses: ./.github/actions/get-version
31 |
32 | # Get the prerelease flag from the branch name
33 | - id: get_prerelease
34 | uses: ./.github/actions/get-prerelease
35 | with:
36 | version: ${{ steps.get_version.outputs.version }}
37 |
38 | # Get the release notes
39 | - id: get_release_notes
40 | uses: ./.github/actions/get-release-notes
41 | with:
42 | token: ${{ secrets.github-token }}
43 | version: ${{ steps.get_version.outputs.version }}
44 | repo_owner: ${{ github.repository_owner }}
45 | repo_name: ${{ github.event.repository.name }}
46 |
47 | # Check if the tag already exists
48 | - id: tag_exists
49 | uses: ./.github/actions/tag-exists
50 | with:
51 | tag: ${{ steps.get_version.outputs.version }}
52 | token: ${{ secrets.github-token }}
53 |
54 | # If the tag already exists, exit with an error
55 | - if: steps.tag_exists.outputs.exists == 'true'
56 | run: exit 1
57 |
58 | # Publish the release to our package manager
59 | - uses: ./.github/actions/npm-publish
60 | with:
61 | version: ${{ steps.get_version.outputs.version }}
62 | release-directory: ${{ inputs.release-directory }}
63 |
64 | # Create a release for the tag
65 | - uses: ./.github/actions/release-create
66 | with:
67 | token: ${{ secrets.github-token }}
68 | name: ${{ steps.get_version.outputs.version }}
69 | body: ${{ steps.get_release_notes.outputs.release-notes }}
70 | tag: ${{ steps.get_version.outputs.version }}
71 | commit: ${{ github.sha }}
72 | prerelease: ${{ steps.get_prerelease.outputs.prerelease }}
73 |
--------------------------------------------------------------------------------
/src/core/interfaces/IAuth0Client.ts:
--------------------------------------------------------------------------------
1 | import type { IWebAuthProvider } from './IWebAuthProvider';
2 | import type { ICredentialsManager } from './ICredentialsManager';
3 | import type { IAuthenticationProvider } from './IAuthenticationProvider';
4 | import type { IUsersClient } from './IUsersClient';
5 | import type { DPoPHeadersParams, TokenType } from '../../types';
6 |
7 | /**
8 | * The primary interface for the Auth0 client.
9 | *
10 | * It aggregates all core functionalities (web auth, credential management, etc.)
11 | * into a single, cohesive contract. Platform-specific factories will produce an
12 | * object that conforms to this interface.
13 | */
14 | export interface IAuth0Client {
15 | /**
16 | * Provides access to methods for handling web-based authentication flows.
17 | */
18 | readonly webAuth: IWebAuthProvider;
19 |
20 | /**
21 | * Provides access to methods for securely managing user credentials on the device.
22 | */
23 | readonly credentialsManager: ICredentialsManager;
24 |
25 | /**
26 | * Provides access to methods for direct authentication grants (e.g., password-realm).
27 | */
28 | readonly auth: IAuthenticationProvider;
29 |
30 | /**
31 | * Creates a client for interacting with the Auth0 Management API's user endpoints.
32 | *
33 | * @param token An access token with the required permissions for the management operations.
34 | * @param tokenType Optional token type ('Bearer' or 'DPoP'). Defaults to the client's configured token type.
35 | * @returns An `IUsersClient` instance configured with the provided token.
36 | */
37 | users(token: string, tokenType?: TokenType): IUsersClient;
38 |
39 | /**
40 | * Generates DPoP headers for making authenticated requests to custom APIs.
41 | * This method creates the necessary HTTP headers (Authorization and DPoP) to
42 | * securely bind the access token to a specific API request.
43 | *
44 | * @param params Parameters including the URL, HTTP method, access token, and token type.
45 | * @returns A promise that resolves to an object containing the required headers.
46 | *
47 | * @example
48 | * ```typescript
49 | * const credentials = await auth0.credentialsManager.getCredentials();
50 | * const headers = await auth0.getDPoPHeaders({
51 | * url: 'https://api.example.com/data',
52 | * method: 'GET',
53 | * accessToken: credentials.accessToken,
54 | * tokenType: credentials.tokenType
55 | * });
56 | *
57 | * fetch('https://api.example.com/data', { headers });
58 | * ```
59 | */
60 | getDPoPHeaders(params: DPoPHeadersParams): Promise>;
61 | }
62 |
--------------------------------------------------------------------------------
/src/core/utils/validation.ts:
--------------------------------------------------------------------------------
1 | import type { Auth0Options } from '../../types';
2 | import { AuthError } from '../models';
3 |
4 | /**
5 | * Validates the core Auth0 client options provided during initialization.
6 | *
7 | * @remarks
8 | * This function is called by the client factory to ensure that the developer
9 | * has provided the minimum required configuration before any network requests
10 | * are made. It throws a structured `AuthError` on failure.
11 | *
12 | * @param options The Auth0Options object to validate.
13 | * @throws {AuthError} If the domain or clientId are missing or malformed.
14 | */
15 | export function validateAuth0Options(options: Auth0Options): void {
16 | if (!options) {
17 | throw new AuthError('InitializationError', 'Auth0 options are required.');
18 | }
19 |
20 | if (
21 | !options.domain ||
22 | typeof options.domain !== 'string' ||
23 | options.domain.trim() === ''
24 | ) {
25 | throw new AuthError(
26 | 'InitializationError',
27 | 'A valid "domain" is required for the Auth0 client.',
28 | { code: 'missing_domain' }
29 | );
30 | }
31 |
32 | if (
33 | options.domain.startsWith('http://') ||
34 | options.domain.startsWith('https://')
35 | ) {
36 | throw new AuthError(
37 | 'InitializationError',
38 | 'The "domain" should not include the protocol (e.g., "https://"). Provide the hostname only.',
39 | { code: 'invalid_domain' }
40 | );
41 | }
42 |
43 | if (
44 | !options.clientId ||
45 | typeof options.clientId !== 'string' ||
46 | options.clientId.trim() === ''
47 | ) {
48 | throw new AuthError(
49 | 'InitializationError',
50 | 'A valid "clientId" is required for the Auth0 client.',
51 | { code: 'missing_client_id' }
52 | );
53 | }
54 | }
55 |
56 | /**
57 | * Validates that a given parameters object contains all specified required keys.
58 | *
59 | * @param params The object to validate.
60 | * @param requiredKeys An array of keys that must be present in the params object.
61 | * @throws {AuthError} If any of the required keys are missing.
62 | */
63 | export function validateParameters(
64 | params: Record,
65 | requiredKeys: string[]
66 | ): void {
67 | const missingKeys = requiredKeys.filter(
68 | (key) => params[key] === null || params[key] === undefined
69 | );
70 |
71 | if (missingKeys.length > 0) {
72 | throw new AuthError(
73 | 'MissingParameters',
74 | `The following parameters are required but were not provided: ${missingKeys.join(
75 | ', '
76 | )}`,
77 | { code: 'missing_parameters' }
78 | );
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/core/models/AuthError.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Represents a generic authentication or API error from Auth0.
3 | *
4 | * This class provides a structured way to handle errors, with consistent
5 | * access to status codes, error codes, and response bodies.
6 | */
7 | export class AuthError extends Error {
8 | /** The HTTP status code of the error response, if available. */
9 | public readonly status: number;
10 | /** The error code returned by Auth0 (e.g., 'invalid_grant'), if available. */
11 | public readonly code: string;
12 | /** The full JSON response body of the error, if available. */
13 | public readonly json: unknown;
14 |
15 | /**
16 | * Constructs a new AuthError instance.
17 | *
18 | * @param name The primary error identifier (e.g., the 'error' field from an OAuth2 response).
19 | * @param message A human-readable description of the error (e.g., the 'error_description' field).
20 | * @param details An object containing additional error context.
21 | */
22 | constructor(
23 | name: string,
24 | message: string,
25 | details?: {
26 | status?: number;
27 | code?: string;
28 | json?: unknown;
29 | }
30 | ) {
31 | super(message);
32 | this.name = name;
33 | this.status = details?.status ?? 0;
34 | this.code = details?.code ?? 'unknown_error';
35 | this.json = details?.json;
36 |
37 | // This is for V8 environments (like Node.js, Chrome) to capture the stack trace correctly.
38 | if (Error.captureStackTrace) {
39 | Error.captureStackTrace(this, AuthError);
40 | }
41 | }
42 |
43 | /**
44 | * A static factory method to create an AuthError from a fetch Response object.
45 | * This is a utility that platform adapters can use for consistency.
46 | *
47 | * @param response The fetch Response object.
48 | * @param body The parsed body of the response (can be JSON or text).
49 | * @returns A new AuthError instance.
50 | */
51 | static fromResponse(response: Response, body: any): AuthError {
52 | const {
53 | error = 'a0.response.invalid',
54 | error_description = 'Unknown error',
55 | code = undefined,
56 | message = undefined,
57 | } = typeof body === 'object' && body !== null ? body : {};
58 |
59 | return new AuthError(
60 | error,
61 | // Use message if it exists (for Management API errors), otherwise fall back to error_description.
62 | message ?? error_description,
63 | {
64 | status: response.status,
65 | // Use the specific 'code' if it exists, otherwise fall back to the 'error' property.
66 | code: code ?? error,
67 | json: body,
68 | }
69 | );
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/core/utils/__tests__/scope.spec.ts:
--------------------------------------------------------------------------------
1 | import { finalizeScope } from '../scope';
2 |
3 | describe('finalizeScope', () => {
4 | it('should return default scopes when input is undefined', () => {
5 | const result = finalizeScope(undefined);
6 | // The order of default scopes doesn't matter, but the content does.
7 | // We split and sort for a reliable test.
8 | expect(result.split(' ').sort()).toEqual(['email', 'openid', 'profile']);
9 | });
10 |
11 | it('should return default scopes when input is an empty string', () => {
12 | const result = finalizeScope('');
13 | expect(result.split(' ').sort()).toEqual(['email', 'openid', 'profile']);
14 | });
15 |
16 | it('should return default scopes when input is only whitespace', () => {
17 | const result = finalizeScope(' ');
18 | expect(result.split(' ').sort()).toEqual(['email', 'openid', 'profile']);
19 | });
20 |
21 | it('should add "openid" to a scope string that does not contain it', () => {
22 | const result = finalizeScope('read:messages');
23 | expect(result.split(' ').sort()).toEqual(['openid', 'read:messages']);
24 | });
25 |
26 | it('should not add "openid" if it is already present', () => {
27 | const result = finalizeScope('openid read:messages');
28 | expect(result.split(' ').sort()).toEqual(['openid', 'read:messages']);
29 | });
30 |
31 | it('should not add "openid" if it is already present with other default scopes', () => {
32 | const result = finalizeScope('openid profile write:items');
33 | expect(result.split(' ').sort()).toEqual([
34 | 'openid',
35 | 'profile',
36 | 'write:items',
37 | ]);
38 | });
39 |
40 | it('should handle extra whitespace in the input string', () => {
41 | const result = finalizeScope(' openid read:data write:data ');
42 | expect(result.split(' ').sort()).toEqual([
43 | 'openid',
44 | 'read:data',
45 | 'write:data',
46 | ]);
47 | });
48 |
49 | it('should not add default "profile" and "email" if other scopes are provided', () => {
50 | const result = finalizeScope('read:appointments');
51 | // It should ONLY add openid, not the other defaults.
52 | expect(result.split(' ').sort()).toEqual(['openid', 'read:appointments']);
53 | expect(result).not.toContain('profile');
54 | expect(result).not.toContain('email');
55 | });
56 |
57 | it('should handle being given all default scopes plus custom ones', () => {
58 | const result = finalizeScope('openid profile email read:stuff');
59 | expect(result.split(' ').sort()).toEqual([
60 | 'email',
61 | 'openid',
62 | 'profile',
63 | 'read:stuff',
64 | ]);
65 | });
66 | });
67 |
--------------------------------------------------------------------------------
/src/platforms/web/adapters/WebAuthenticationProvider.ts:
--------------------------------------------------------------------------------
1 | import type { IAuthenticationProvider } from '../../../core/interfaces';
2 | import { AuthError } from '../../../core/models';
3 |
4 | const webAuthNotSupported =
5 | 'This authentication method is not available on the web platform for security reasons. Please use the browser-based authorize() flow.';
6 | const webRefreshHandled =
7 | 'Token refresh is handled automatically by `credentialsManager.getCredentials()` on the web.';
8 | const webUserInfoHandled =
9 | 'User Info should be retrieved by decoding the ID token from `credentialsManager.getCredentials()` on the web.';
10 |
11 | /**
12 | * An IAuthenticationProvider implementation for the web that explicitly
13 | * disallows direct-grant authentication methods for security reasons.
14 | */
15 |
16 | export const UnimplementedWebAuthenticationProvider: IAuthenticationProvider = {
17 | // Original stubs
18 | passwordRealm: () =>
19 | Promise.reject(new AuthError('NotImplemented', webAuthNotSupported)),
20 | refreshToken: () =>
21 | Promise.reject(new AuthError('NotImplemented', webRefreshHandled)),
22 | userInfo: () =>
23 | Promise.reject(new AuthError('NotImplemented', webUserInfoHandled)),
24 | revoke: () =>
25 | Promise.reject(
26 | new AuthError(
27 | 'NotImplemented',
28 | '`revoke` is not available on the web platform.'
29 | )
30 | ),
31 |
32 | // Stubs for newly added methods
33 | exchange: () =>
34 | Promise.reject(new AuthError('NotImplemented', webAuthNotSupported)),
35 | exchangeNativeSocial: () =>
36 | Promise.reject(new AuthError('NotImplemented', webAuthNotSupported)),
37 | passwordlessWithEmail: () =>
38 | Promise.reject(new AuthError('NotImplemented', webAuthNotSupported)),
39 | passwordlessWithSMS: () =>
40 | Promise.reject(new AuthError('NotImplemented', webAuthNotSupported)),
41 | loginWithEmail: () =>
42 | Promise.reject(new AuthError('NotImplemented', webAuthNotSupported)),
43 | loginWithSMS: () =>
44 | Promise.reject(new AuthError('NotImplemented', webAuthNotSupported)),
45 | loginWithOTP: () =>
46 | Promise.reject(new AuthError('NotImplemented', webAuthNotSupported)),
47 | loginWithOOB: () =>
48 | Promise.reject(new AuthError('NotImplemented', webAuthNotSupported)),
49 | loginWithRecoveryCode: () =>
50 | Promise.reject(new AuthError('NotImplemented', webAuthNotSupported)),
51 | multifactorChallenge: () =>
52 | Promise.reject(new AuthError('NotImplemented', webAuthNotSupported)),
53 | resetPassword: () =>
54 | Promise.reject(new AuthError('NotImplemented', webAuthNotSupported)),
55 | createUser: () =>
56 | Promise.reject(new AuthError('NotImplemented', webAuthNotSupported)),
57 | };
58 |
--------------------------------------------------------------------------------
/REACT_NATIVE_WEB_SETUP.md:
--------------------------------------------------------------------------------
1 | # React Native Web Configuration Guide
2 |
3 | This guide provides instructions for using React Native Auth0 with React Native Web.
4 |
5 | ## What is React Native Web?
6 |
7 | React Native Web is a library that provides React Native components and APIs that are compatible with React and the web. It allows you to write your application once using React Native and run it on iOS, Android, and web platforms.
8 |
9 | **Official React Native Web Repository:** https://github.com/necolas/react-native-web
10 |
11 | ## Setup Instructions
12 |
13 | If you want to use React Native Web with React Native Auth0, follow these steps:
14 |
15 | ### 1. Install React Native Web
16 |
17 | Follow the official React Native Web installation guide:
18 | **https://necolas.github.io/react-native-web/docs/setup/**
19 |
20 | ### 2. Use React Native Auth0
21 |
22 | Once React Native Web and Auth0 SPA JS are installed, you can use React Native Auth0 exactly as you would in a native React Native app. The library will automatically detect the web platform and use the appropriate implementation.
23 |
24 | ---
25 |
26 | ## 🔁 Web Callback and Logout URL Setup
27 |
28 | When running on Web—especially with **Expo** or custom Webpack servers—you must configure **callback** and **logout URLs** in your [Auth0 Application settings](https://manage.auth0.com/#/applications):
29 |
30 | ### ✅ For local development (Expo or Metro)
31 |
32 | Add the following URLs:
33 |
34 | * **Allowed Callback URLs**:
35 | `http://localhost:8081`
36 |
37 | * **Allowed Logout URLs**:
38 | `http://localhost:8081`
39 |
40 | > ⚠️ If you've customized your Webpack Dev Server port (e.g., `3000`), replace `8081` accordingly.
41 |
42 | ---
43 |
44 | ## Example Usage
45 |
46 | ```jsx
47 | import React from 'react';
48 | import { Auth0Provider } from 'react-native-auth0';
49 |
50 | const App = () => (
51 |
52 | {/* Your app components */}
53 |
54 | );
55 |
56 | export default App;
57 | ```
58 |
59 | ## Resources
60 |
61 | - **React Native Web Setup Guide**: https://necolas.github.io/react-native-web/docs/setup/
62 | - **React Native Web GitHub**: https://github.com/necolas/react-native-web
63 | - **Auth0 SPA JS**: https://github.com/auth0/auth0-spa-js
64 | - **React Native Auth0 Documentation**: https://auth0.github.io/react-native-auth0/
65 |
66 | ## Notes
67 |
68 | - React Native Auth0 automatically detects when running on web and uses the Auth0 SPA JS library under the hood
69 | - All React Native Auth0 APIs work the same across platforms (iOS, Android, and Web)
70 | - For web-specific examples, see the [EXAMPLES-WEB.md](https://github.com/auth0/react-native-auth0/blob/master/EXAMPLES-WEB.md) file in this repository
71 |
--------------------------------------------------------------------------------
/example/src/screens/SelectionScreen.tsx:
--------------------------------------------------------------------------------
1 | // example/src/screens/SelectionScreen.tsx
2 |
3 | import React from 'react';
4 | import { View, Text, StyleSheet, SafeAreaView } from 'react-native';
5 | import { useNavigation } from '@react-navigation/native';
6 | import type { StackNavigationProp } from '@react-navigation/stack';
7 | import Button from '../components/Button';
8 | import Header from '../components/Header';
9 | import type { RootStackParamList } from '../navigation/RootNavigator';
10 |
11 | // Use the specific navigation prop type from our RootNavigator's param list
12 | // for type-safe navigation.
13 | type SelectionScreenNavigationProp = StackNavigationProp<
14 | RootStackParamList,
15 | 'Selection'
16 | >;
17 |
18 | /**
19 | * The initial screen of the application. It allows the user to navigate
20 | * to either the Hooks-based demo or the Class-based demo.
21 | */
22 | const SelectionScreen = () => {
23 | const navigation = useNavigation();
24 |
25 | return (
26 |
27 |
28 |
29 |
30 | Choose a demonstration to see the Auth0 SDK in action.
31 |
32 |
33 |
52 |
53 | );
54 | };
55 |
56 | const styles = StyleSheet.create({
57 | container: {
58 | flex: 1,
59 | backgroundColor: '#FFFFFF',
60 | },
61 | content: {
62 | flex: 1,
63 | justifyContent: 'center',
64 | alignItems: 'center',
65 | paddingHorizontal: 24,
66 | },
67 | description: {
68 | fontSize: 18,
69 | textAlign: 'center',
70 | color: '#424242',
71 | marginBottom: 40,
72 | },
73 | spacer: {
74 | height: 20,
75 | },
76 | secondaryButton: {
77 | backgroundColor: '#FFFFFF',
78 | borderWidth: 2,
79 | borderColor: '#E53935',
80 | },
81 | secondaryButtonText: {
82 | color: '#E53935',
83 | },
84 | footer: {
85 | position: 'absolute',
86 | bottom: 30,
87 | fontSize: 14,
88 | textAlign: 'center',
89 | color: '#757575',
90 | paddingHorizontal: 20,
91 | },
92 | });
93 |
94 | export default SelectionScreen;
95 |
--------------------------------------------------------------------------------
/src/core/interfaces/IWebAuthProvider.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | Credentials,
3 | WebAuthorizeParameters,
4 | ClearSessionParameters,
5 | User,
6 | } from '../../types';
7 |
8 | import type {
9 | NativeAuthorizeOptions,
10 | NativeClearSessionOptions,
11 | WebAuthorizeOptions,
12 | WebClearSessionOptions,
13 | } from '../../types/platform-specific';
14 |
15 | /**
16 | * Defines the contract for a provider that handles web-based authentication flows,
17 | * such as redirecting to the Auth0 Universal Login page.
18 | */
19 | export interface IWebAuthProvider {
20 | /**
21 | * Initiates the web-based authentication flow.
22 | *
23 | * @remarks
24 | * This method will redirect the user to a browser to log in. The `options` parameter
25 | * is generic to allow platform-specific configurations.
26 | *
27 | * @param parameters The parameters to send to the `/authorize` endpoint.
28 | * @param options Platform-specific options to customize the authentication experience.
29 | * @returns A promise that resolves with the user's credentials upon successful authentication.
30 | */
31 | authorize(
32 | parameters: WebAuthorizeParameters,
33 | options?: NativeAuthorizeOptions | WebAuthorizeOptions
34 | ): Promise;
35 |
36 | /**
37 | * Handles the redirect callback after authentication.
38 | *
39 | * @remarks
40 | * **Platform specific:** This method is only available in the context of a web application.
41 | * @returns A promise that resolves when the redirect callback has been processed.
42 | */
43 | handleRedirectCallback(): Promise;
44 |
45 | /**
46 | * Clears the user's session, including any cookies stored in the browser.
47 | *
48 | * @param parameters The parameters to send to the `/v2/logout` endpoint.
49 | * @param options Platform-specific options to customize the logout experience.
50 | * @returns A promise that resolves when the session has been cleared.
51 | */
52 | clearSession(
53 | parameters?: ClearSessionParameters,
54 | options?: NativeClearSessionOptions | WebClearSessionOptions
55 | ): Promise;
56 |
57 | /**
58 | * Retrives the authenticated user's profile information.
59 | *
60 | * @remarks
61 | * This method fetches the user's profile from the Auth0 session if available.
62 | *
63 | * @returns A promise that resolves with the user's profile information, or null if not authenticated.
64 | */
65 | getWebUser(): Promise;
66 |
67 | /**
68 | * Checks the user's session and updates the local state if the session is still valid.
69 | */
70 | checkWebSession(): Promise;
71 |
72 | /**
73 | * Cancels an ongoing web authentication transaction.
74 | *
75 | * @remarks
76 | * **Platform specific:** This is primarily used on iOS to handle scenarios where the user manually
77 | * dismisses the login modal. On other platforms, it may be a no-op.
78 | *
79 | * @returns A promise that resolves when the operation is complete.
80 | */
81 | cancelWebAuth(): Promise;
82 | }
83 |
--------------------------------------------------------------------------------
/src/platforms/native/adapters/NativeCredentialsManager.ts:
--------------------------------------------------------------------------------
1 | import type { ICredentialsManager } from '../../../core/interfaces';
2 | import { ApiCredentials, AuthError } from '../../../core/models';
3 | import { CredentialsManagerError } from '../../../core/models';
4 | import type {
5 | ApiCredentials as IApiCredentials,
6 | Credentials,
7 | SessionTransferCredentials,
8 | } from '../../../types';
9 | import type { INativeBridge } from '../bridge';
10 |
11 | /**
12 | * A native platform-specific implementation of the ICredentialsManager.
13 | * It delegates all credential storage, retrieval, and management logic to the
14 | * underlying native bridge, which uses secure native storage.
15 | */
16 | export class NativeCredentialsManager implements ICredentialsManager {
17 | constructor(private bridge: INativeBridge) {}
18 |
19 | private async handleError(promise: Promise): Promise {
20 | try {
21 | return await promise;
22 | } catch (e) {
23 | // Assume the bridge only throws AuthError.
24 | throw new CredentialsManagerError(e as AuthError);
25 | }
26 | }
27 |
28 | saveCredentials(credentials: Credentials): Promise {
29 | return this.handleError(this.bridge.saveCredentials(credentials));
30 | }
31 |
32 | getCredentials(
33 | scope?: string,
34 | minTtl?: number,
35 | parameters?: Record,
36 | forceRefresh?: boolean
37 | ): Promise {
38 | return this.handleError(
39 | this.bridge.getCredentials(scope, minTtl, parameters, forceRefresh)
40 | );
41 | }
42 |
43 | hasValidCredentials(minTtl?: number): Promise {
44 | return this.handleError(this.bridge.hasValidCredentials(minTtl));
45 | }
46 |
47 | async clearCredentials(): Promise {
48 | await this.handleError(this.bridge.clearCredentials());
49 | // Also clear the DPoP key when clearing credentials
50 | // Ignore errors from DPoP key clearing - this matches iOS behavior
51 | // where we log the error but don't fail the operation
52 | try {
53 | await this.bridge.clearDPoPKey();
54 | } catch {
55 | // Silently ignore DPoP key clearing errors
56 | // The main credentials are already cleared at this point
57 | }
58 | }
59 |
60 | async getApiCredentials(
61 | audience: string,
62 | scope?: string,
63 | minTtl?: number,
64 | parameters?: Record
65 | ): Promise {
66 | const nativeCredentials = await this.handleError(
67 | this.bridge.getApiCredentials(audience, scope, minTtl ?? 0, parameters)
68 | );
69 | // Convert plain object from native to class instance
70 | return new ApiCredentials(nativeCredentials as IApiCredentials);
71 | }
72 |
73 | clearApiCredentials(audience: string): Promise {
74 | return this.handleError(this.bridge.clearApiCredentials(audience));
75 | }
76 |
77 | getSSOCredentials(
78 | parameters?: Record,
79 | headers?: Record
80 | ): Promise {
81 | return this.handleError(this.bridge.getSSOCredentials(parameters, headers));
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.getExtOrDefault = {name ->
3 | return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['A0Auth0_' + name]
4 | }
5 |
6 | repositories {
7 | google()
8 | mavenCentral()
9 | }
10 |
11 | dependencies {
12 | classpath "com.android.tools.build:gradle:8.7.2"
13 | // noinspection DifferentKotlinGradleVersion
14 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
15 | }
16 | }
17 |
18 | def reactNativeArchitectures() {
19 | def value = rootProject.getProperties().get("reactNativeArchitectures")
20 | return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
21 | }
22 |
23 | def isNewArchitectureEnabled() {
24 | return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
25 | }
26 |
27 | apply plugin: "com.android.library"
28 | apply plugin: "kotlin-android"
29 |
30 | if (isNewArchitectureEnabled()) {
31 | apply plugin: "com.facebook.react"
32 | }
33 |
34 | def getExtOrIntegerDefault(name) {
35 | return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["A0Auth0_" + name]).toInteger()
36 | }
37 |
38 | android {
39 | namespace "com.auth0.react"
40 |
41 | compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
42 |
43 | defaultConfig {
44 | minSdkVersion getExtOrIntegerDefault("minSdkVersion")
45 | targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
46 | buildConfigField("boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString())
47 | versionCode 1
48 | versionName "1.0"
49 | }
50 |
51 | buildFeatures {
52 | buildConfig true
53 | }
54 |
55 | buildTypes {
56 | release {
57 | minifyEnabled false
58 | }
59 | }
60 |
61 | lintOptions {
62 | disable "GradleCompatible"
63 | abortOnError false
64 | }
65 |
66 | compileOptions {
67 | sourceCompatibility JavaVersion.VERSION_1_8
68 | targetCompatibility JavaVersion.VERSION_1_8
69 | }
70 |
71 | sourceSets {
72 | main {
73 | if (isNewArchitectureEnabled()) {
74 | java.srcDirs += [
75 | "src/main/newarch",
76 | ]
77 | } else {
78 | java.srcDirs += ["src/main/oldarch"]
79 | }
80 | }
81 | }
82 | }
83 |
84 | repositories {
85 | mavenCentral()
86 | google()
87 | maven {
88 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
89 | url "$projectDir/../node_modules/react-native/android"
90 | }
91 | }
92 |
93 | def kotlin_version = getExtOrDefault("kotlinVersion")
94 |
95 | dependencies {
96 | implementation "com.facebook.react:react-android"
97 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
98 | implementation "androidx.browser:browser:1.2.0"
99 | implementation 'com.auth0.android:auth0:3.11.0'
100 | }
101 |
102 | if (isNewArchitectureEnabled()) {
103 | react {
104 | jsRootDir = file("../src/")
105 | libraryName = "A0Auth0"
106 | codegenJavaPackageName = "com.auth0.react"
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/core/models/DPoPError.ts:
--------------------------------------------------------------------------------
1 | import { AuthError } from './AuthError';
2 |
3 | const ERROR_CODE_MAP: Record = {
4 | // --- DPoP-specific error codes ---
5 | DPOP_GENERATION_FAILED: 'DPOP_GENERATION_FAILED',
6 | DPOP_PROOF_FAILED: 'DPOP_PROOF_FAILED',
7 | DPOP_KEY_GENERATION_FAILED: 'DPOP_KEY_GENERATION_FAILED',
8 | DPOP_KEY_STORAGE_FAILED: 'DPOP_KEY_STORAGE_FAILED',
9 | DPOP_KEY_RETRIEVAL_FAILED: 'DPOP_KEY_RETRIEVAL_FAILED',
10 | DPOP_NONCE_MISMATCH: 'DPOP_NONCE_MISMATCH',
11 | DPOP_INVALID_TOKEN_TYPE: 'DPOP_INVALID_TOKEN_TYPE',
12 | DPOP_MISSING_PARAMETER: 'DPOP_MISSING_PARAMETER',
13 | DPOP_CLEAR_KEY_FAILED: 'DPOP_CLEAR_KEY_FAILED',
14 |
15 | // --- Native platform mappings ---
16 | // iOS
17 | DPOP_KEY_NOT_FOUND: 'DPOP_KEY_RETRIEVAL_FAILED',
18 | DPOP_KEYCHAIN_ERROR: 'DPOP_KEY_STORAGE_FAILED',
19 |
20 | // Android
21 | DPOP_KEYSTORE_ERROR: 'DPOP_KEY_STORAGE_FAILED',
22 | DPOP_CRYPTO_ERROR: 'DPOP_KEY_GENERATION_FAILED',
23 |
24 | // Web
25 | dpop_generation_failed: 'DPOP_GENERATION_FAILED',
26 | dpop_proof_failed: 'DPOP_PROOF_FAILED',
27 | dpop_key_error: 'DPOP_KEY_GENERATION_FAILED',
28 |
29 | // --- Generic fallback ---
30 | UNKNOWN: 'UNKNOWN_DPOP_ERROR',
31 | OTHER: 'UNKNOWN_DPOP_ERROR',
32 | };
33 |
34 | /**
35 | * Represents an error that occurred during DPoP (Demonstrating Proof-of-Possession) operations.
36 | *
37 | * This class wraps authentication errors related to DPoP functionality, such as:
38 | * - Key generation and storage failures
39 | * - DPoP proof generation failures
40 | * - Token binding validation errors
41 | * - Nonce handling errors
42 | *
43 | * The `type` property provides a normalized, platform-agnostic error code that
44 | * applications can use for consistent error handling across iOS, Android, and Web.
45 | *
46 | * @example
47 | * ```typescript
48 | * try {
49 | * const headers = await auth0.getDPoPHeaders({
50 | * url: 'https://api.example.com/data',
51 | * method: 'GET',
52 | * accessToken: credentials.accessToken,
53 | * tokenType: credentials.tokenType
54 | * });
55 | * } catch (error) {
56 | * if (error instanceof DPoPError) {
57 | * switch (error.type) {
58 | * case 'DPOP_GENERATION_FAILED':
59 | * console.log('Failed to generate DPoP proof');
60 | * break;
61 | * case 'DPOP_KEY_STORAGE_FAILED':
62 | * console.log('Failed to store DPoP key securely');
63 | * break;
64 | * }
65 | * }
66 | * }
67 | * ```
68 | */
69 | export class DPoPError extends AuthError {
70 | /**
71 | * A normalized error type that is consistent across platforms.
72 | * This can be used for reliable error handling in application code.
73 | */
74 | public readonly type: string;
75 |
76 | /**
77 | * Constructs a new DPoPError instance from an AuthError.
78 | *
79 | * @param originalError The original AuthError that occurred during a DPoP operation.
80 | */
81 | constructor(originalError: AuthError) {
82 | super(originalError.name, originalError.message, {
83 | status: originalError.status,
84 | code: originalError.code,
85 | json: originalError.json,
86 | });
87 |
88 | // Map the original error code to a normalized type
89 | this.type = ERROR_CODE_MAP[originalError.code] || 'UNKNOWN_DPOP_ERROR';
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/Auth0.ts:
--------------------------------------------------------------------------------
1 | import type { IAuth0Client } from './core/interfaces/IAuth0Client';
2 | import type { TokenType } from './types/common';
3 | import { Auth0ClientFactory } from './factory/Auth0ClientFactory';
4 | import type { Auth0Options, DPoPHeadersParams } from './types';
5 |
6 | /**
7 | * The main Auth0 client class.
8 | *
9 | * This class acts as a facade, creating and delegating to a platform-specific
10 | * client instance (Native or Web) under the hood.
11 | *
12 | * @example
13 | * ```
14 | * import Auth0 from 'react-native-auth0';
15 | *
16 | * const auth0 = new Auth0({
17 | * domain: 'YOUR_AUTH0_DOMAIN',
18 | * clientId: 'YOUR_AUTH0_CLIENT_ID'
19 | * });
20 | * ```
21 | */
22 | class Auth0 {
23 | private client: IAuth0Client;
24 |
25 | /**
26 | * Creates an instance of the Auth0 client.
27 | * @param options Configuration options for the client.
28 | */
29 | constructor(options: Auth0Options) {
30 | // The factory detects the platform and returns the appropriate client implementation.
31 | // The rest of this class is completely unaware of whether it's running on native or web.
32 | this.client = Auth0ClientFactory.createClient(options);
33 | }
34 |
35 | /**
36 | * Provides access to the web-based authentication methods.
37 | * @see IWebAuthProvider
38 | */
39 | get webAuth() {
40 | return this.client.webAuth;
41 | }
42 |
43 | /**
44 | * Provides access to the credentials management methods.
45 | * @see ICredentialsManager
46 | */
47 | get credentialsManager() {
48 | return this.client.credentialsManager;
49 | }
50 |
51 | /**
52 | * Provides access to direct authentication methods (e.g., password-realm).
53 | * @see IAuthenticationProvider
54 | */
55 | get auth() {
56 | return this.client.auth;
57 | }
58 |
59 | /**
60 | * Provides access to the Management API (e.g., for user patching).
61 | * @param token An access token with the required permissions for the management operations.
62 | * @param tokenType Optional token type ('Bearer' or 'DPoP'). Defaults to the client's configured token type.
63 | */
64 | users(token: string, tokenType?: TokenType) {
65 | return this.client.users(token, tokenType);
66 | }
67 |
68 | /**
69 | * Generates DPoP headers for making authenticated requests to custom APIs.
70 | * This method creates the necessary HTTP headers (Authorization and DPoP) to
71 | * securely bind the access token to a specific API request.
72 | *
73 | * @param params Parameters including the URL, HTTP method, access token, and token type.
74 | * @returns A promise that resolves to an object containing the required headers.
75 | *
76 | * @example
77 | * ```typescript
78 | * const credentials = await auth0.credentialsManager.getCredentials();
79 | *
80 | * if (credentials.tokenType === 'DPoP') {
81 | * const headers = await auth0.getDPoPHeaders({
82 | * url: 'https://api.example.com/data',
83 | * method: 'GET',
84 | * accessToken: credentials.accessToken,
85 | * tokenType: credentials.tokenType
86 | * });
87 | *
88 | * const response = await fetch('https://api.example.com/data', { headers });
89 | * }
90 | * ```
91 | */
92 | getDPoPHeaders(params: DPoPHeadersParams) {
93 | return this.client.getDPoPHeaders(params);
94 | }
95 | }
96 |
97 | export default Auth0;
98 |
--------------------------------------------------------------------------------
/src/core/models/WebAuthError.ts:
--------------------------------------------------------------------------------
1 | import { AuthError } from './AuthError';
2 |
3 | /**
4 | * Public constants exposing all possible WebAuth error codes.
5 | */
6 | export const WebAuthErrorCodes = {
7 | USER_CANCELLED: 'USER_CANCELLED',
8 | ACCESS_DENIED: 'ACCESS_DENIED',
9 | NETWORK_ERROR: 'NETWORK_ERROR',
10 | ID_TOKEN_VALIDATION_FAILED: 'ID_TOKEN_VALIDATION_FAILED',
11 | BIOMETRICS_CONFIGURATION_ERROR: 'BIOMETRICS_CONFIGURATION_ERROR',
12 | BROWSER_NOT_AVAILABLE: 'BROWSER_NOT_AVAILABLE',
13 | FAILED_TO_LOAD_URL: 'FAILED_TO_LOAD_URL',
14 | BROWSER_TERMINATED: 'BROWSER_TERMINATED',
15 | NO_BUNDLE_IDENTIFIER: 'NO_BUNDLE_IDENTIFIER',
16 | TRANSACTION_ACTIVE_ALREADY: 'TRANSACTION_ACTIVE_ALREADY',
17 | NO_AUTHORIZATION_CODE: 'NO_AUTHORIZATION_CODE',
18 | PKCE_NOT_ALLOWED: 'PKCE_NOT_ALLOWED',
19 | INVALID_INVITATION_URL: 'INVALID_INVITATION_URL',
20 | INVALID_STATE: 'INVALID_STATE',
21 | TIMEOUT_ERROR: 'TIMEOUT_ERROR',
22 | CONSENT_REQUIRED: 'CONSENT_REQUIRED',
23 | INVALID_CONFIGURATION: 'INVALID_CONFIGURATION',
24 | UNKNOWN_ERROR: 'UNKNOWN_ERROR',
25 | } as const;
26 |
27 | const ERROR_CODE_MAP: Record = {
28 | // --- Common Codes ---
29 | 'a0.session.user_cancelled': WebAuthErrorCodes.USER_CANCELLED,
30 | 'USER_CANCELLED': WebAuthErrorCodes.USER_CANCELLED,
31 | 'access_denied': WebAuthErrorCodes.ACCESS_DENIED,
32 | 'a0.network_error': WebAuthErrorCodes.NETWORK_ERROR,
33 | 'a0.session.invalid_idtoken': WebAuthErrorCodes.ID_TOKEN_VALIDATION_FAILED,
34 | 'ID_TOKEN_VALIDATION_FAILED': WebAuthErrorCodes.ID_TOKEN_VALIDATION_FAILED,
35 | 'BIOMETRICS_CONFIGURATION_ERROR':
36 | WebAuthErrorCodes.BIOMETRICS_CONFIGURATION_ERROR,
37 |
38 | // --- Android-specific mappings ---
39 | 'a0.browser_not_available': WebAuthErrorCodes.BROWSER_NOT_AVAILABLE,
40 | 'a0.session.failed_load': WebAuthErrorCodes.FAILED_TO_LOAD_URL,
41 | 'a0.session.browser_terminated': WebAuthErrorCodes.BROWSER_TERMINATED,
42 |
43 | // --- iOS-specific mappings ---
44 | 'NO_BUNDLE_IDENTIFIER': WebAuthErrorCodes.NO_BUNDLE_IDENTIFIER,
45 | 'TRANSACTION_ACTIVE_ALREADY': WebAuthErrorCodes.TRANSACTION_ACTIVE_ALREADY,
46 | 'NO_AUTHORIZATION_CODE': WebAuthErrorCodes.NO_AUTHORIZATION_CODE,
47 | 'PKCE_NOT_ALLOWED': WebAuthErrorCodes.PKCE_NOT_ALLOWED,
48 | 'INVALID_INVITATION_URL': WebAuthErrorCodes.INVALID_INVITATION_URL,
49 |
50 | // --- Web (@auth0/auth0-spa-js) mappings ---
51 | 'cancelled': WebAuthErrorCodes.USER_CANCELLED,
52 | 'state_mismatch': WebAuthErrorCodes.INVALID_STATE,
53 | 'login_required': WebAuthErrorCodes.ACCESS_DENIED,
54 | 'timeout': WebAuthErrorCodes.TIMEOUT_ERROR,
55 | 'consent_required': WebAuthErrorCodes.CONSENT_REQUIRED,
56 |
57 | // --- Generic Fallbacks ---
58 | 'a0.invalid_configuration': WebAuthErrorCodes.INVALID_CONFIGURATION,
59 | 'UNKNOWN': WebAuthErrorCodes.UNKNOWN_ERROR,
60 | 'OTHER': WebAuthErrorCodes.UNKNOWN_ERROR,
61 | };
62 |
63 | export class WebAuthError extends AuthError {
64 | public readonly type: string;
65 |
66 | constructor(originalError: AuthError) {
67 | super(originalError.name, originalError.message, {
68 | status: originalError.status,
69 | code: originalError.code,
70 | json: originalError.json,
71 | });
72 |
73 | if (
74 | originalError.message.includes('state is invalid') ||
75 | originalError.code === 'state_mismatch'
76 | ) {
77 | this.type = WebAuthErrorCodes.INVALID_STATE;
78 | } else {
79 | this.type =
80 | ERROR_CODE_MAP[originalError.code] || WebAuthErrorCodes.UNKNOWN_ERROR;
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/android/src/main/oldarch/com/auth0/react/A0Auth0Spec.kt:
--------------------------------------------------------------------------------
1 | package com.auth0.react
2 |
3 | import com.facebook.common.internal.DoNotStrip
4 | import com.facebook.react.bridge.ReactApplicationContext
5 | import com.facebook.react.bridge.ReactContextBaseJavaModule
6 | import com.facebook.react.bridge.Promise
7 | import com.facebook.react.bridge.ReactMethod
8 | import com.facebook.react.bridge.ReadableMap
9 |
10 | abstract class A0Auth0Spec(context: ReactApplicationContext) : ReactContextBaseJavaModule(context) {
11 |
12 | abstract override fun getName(): String
13 |
14 | @ReactMethod
15 | @DoNotStrip
16 | abstract fun getBundleIdentifier(promise: Promise)
17 |
18 | @ReactMethod
19 | @DoNotStrip
20 | abstract fun hasValidAuth0InstanceWithConfiguration(clientId: String, domain: String, promise: Promise)
21 |
22 | @ReactMethod
23 | @DoNotStrip
24 | abstract fun initializeAuth0WithConfiguration(
25 | clientId: String,
26 | domain: String,
27 | localAuthenticationOptions: ReadableMap?,
28 | useDPoP: Boolean?,
29 | promise: Promise
30 | )
31 |
32 | @ReactMethod
33 | @DoNotStrip
34 | abstract fun saveCredentials(credentials: ReadableMap, promise: Promise)
35 |
36 | @ReactMethod
37 | @DoNotStrip
38 | abstract fun getCredentials(
39 | scope: String?,
40 | minTTL: Double,
41 | parameters: ReadableMap,
42 | forceRefresh: Boolean,
43 | promise: Promise
44 | )
45 |
46 | @ReactMethod
47 | @DoNotStrip
48 | abstract fun hasValidCredentials(minTTL: Double, promise: Promise)
49 |
50 | @ReactMethod
51 | @DoNotStrip
52 | abstract fun clearCredentials(promise: Promise)
53 |
54 | @ReactMethod
55 | @DoNotStrip
56 | abstract fun getApiCredentials(
57 | audience: String,
58 | scope: String?,
59 | minTTL: Double,
60 | parameters: ReadableMap,
61 | promise: Promise
62 | )
63 |
64 | @ReactMethod
65 | @DoNotStrip
66 | abstract fun clearApiCredentials(audience: String, promise: Promise)
67 |
68 | @ReactMethod
69 | @DoNotStrip
70 | abstract fun webAuth(
71 | scheme: String,
72 | redirectUri: String?,
73 | state: String?,
74 | nonce: String?,
75 | audience: String?,
76 | scope: String?,
77 | connection: String?,
78 | maxAge: Double?,
79 | organization: String?,
80 | invitationUrl: String?,
81 | leeway: Double?,
82 | ephemeralSession: Boolean?,
83 | safariViewControllerPresentationStyle: Double?,
84 | additionalParameters: ReadableMap?,
85 | promise: Promise
86 | )
87 |
88 | @ReactMethod
89 | @DoNotStrip
90 | abstract fun webAuthLogout(scheme: String, federated: Boolean, redirectUri: String?, promise: Promise)
91 |
92 | @ReactMethod
93 | @DoNotStrip
94 | abstract fun resumeWebAuth(url: String, promise: Promise)
95 |
96 | @ReactMethod
97 | @DoNotStrip
98 | abstract fun cancelWebAuth(promise: Promise)
99 |
100 | @ReactMethod
101 | @DoNotStrip
102 | abstract fun getDPoPHeaders(url: String, method: String, accessToken: String, tokenType: String, nonce: String?, promise: Promise)
103 |
104 | @ReactMethod
105 | @DoNotStrip
106 | abstract fun clearDPoPKey(promise: Promise)
107 |
108 | @ReactMethod
109 | @DoNotStrip
110 | abstract fun getSSOCredentials(parameters: ReadableMap?, headers: ReadableMap?, promise: Promise)
111 | }
--------------------------------------------------------------------------------
/.snyk:
--------------------------------------------------------------------------------
1 | # Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
2 | version: v1.25.0
3 | # ignores vulnerabilities until expiry date; change duration by modifying expiry date
4 | ignore:
5 | SNYK-JS-INFLIGHT-6095116:
6 | - '*':
7 | reason: No fix available
8 | expires: 2025-05-12T09:15:05.191Z
9 | created: 2025-02-02T05:47:18.380Z
10 | snyk:lic:npm:lightningcss-win32-x64-msvc:MPL-2.0:
11 | - '*':
12 | reason: This issue is temporarily ignored while we evaluate alternative dependencies or wait for an update from Expo/Metro.
13 | expires: 2025-05-12T09:15:05.191Z
14 | created: 2025-03-12T09:15:05.191Z
15 | snyk:lic:npm:lightningcss-linux-x64-musl:MPL-2.0:
16 | - '*':
17 | reason: This issue is temporarily ignored while we evaluate alternative dependencies or wait for an update from Expo/Metro.
18 | expires: 2025-05-12T09:15:05.191Z
19 | created: 2025-03-12T09:15:05.191Z
20 | snyk:lic:npm:lightningcss-linux-x64-gnu:MPL-2.0:
21 | - '*':
22 | reason: This issue is temporarily ignored while we evaluate alternative dependencies or wait for an update from Expo/Metro.
23 | expires: 2025-05-12T09:15:05.191Z
24 | created: 2025-03-12T09:15:05.191Z
25 | snyk:lic:npm:lightningcss-linux-arm64-musl:MPL-2.0:
26 | - '*':
27 | reason: This issue is temporarily ignored while we evaluate alternative dependencies or wait for an update from Expo/Metro.
28 | expires: 2025-05-12T09:15:05.191Z
29 | created: 2025-03-12T09:15:05.191Z
30 | snyk:lic:npm:lightningcss-linux-arm64-gnu:MPL-2.0:
31 | - '*':
32 | reason: This issue is temporarily ignored while we evaluate alternative dependencies or wait for an update from Expo/Metro.
33 | expires: 2025-05-12T09:15:05.191Z
34 | created: 2025-03-12T09:15:05.191Z
35 | snyk:lic:npm:lightningcss-linux-arm-gnueabihf:MPL-2.0:
36 | - '*':
37 | reason: This issue is temporarily ignored while we evaluate alternative dependencies or wait for an update from Expo/Metro.
38 | expires: 2025-05-12T09:15:05.191Z
39 | created: 2025-03-12T09:15:05.191Z
40 | snyk:lic:npm:lightningcss-freebsd-x64:MPL-2.0:
41 | - '*':
42 | reason: This issue is temporarily ignored while we evaluate alternative dependencies or wait for an update from Expo/Metro.
43 | expires: 2025-05-12T09:15:05.191Z
44 | created: 2025-03-12T09:15:05.191Z
45 | snyk:lic:npm:lightningcss-darwin-x64:MPL-2.0:
46 | - '*':
47 | reason: This issue is temporarily ignored while we evaluate alternative dependencies or wait for an update from Expo/Metro.
48 | expires: 2025-05-12T09:15:05.191Z
49 | created: 2025-03-12T09:15:05.191Z
50 | snyk:lic:npm:lightningcss-darwin-arm64:MPL-2.0:
51 | - '*':
52 | reason: This issue is temporarily ignored while we evaluate alternative dependencies or wait for an update from Expo/Metro.
53 | expires: 2025-05-12T09:15:05.191Z
54 | created: 2025-03-12T09:15:05.191Z
55 | snyk:lic:npm:lightningcss:MPL-2.0:
56 | - '*':
57 | reason: This issue is temporarily ignored while we evaluate alternative dependencies or wait for an update from Expo/Metro.
58 | expires: 2025-05-12T09:15:05.191Z
59 | created: 2025-03-12T09:15:05.191Z
60 | snyk:lic:npm:lightningcss-win32-arm64-msvc:MPL-2.0:
61 | - '*':
62 | reason: This issue is temporarily ignored while we evaluate alternative dependencies or wait for an update from Expo/Metro.
63 | expires: 2025-05-12T09:15:05.191Z
64 | created: 2025-03-12T09:15:05.191Z
65 | patch: {}
66 |
--------------------------------------------------------------------------------
/example/android/gradlew.bat:
--------------------------------------------------------------------------------
1 | @REM Copyright (c) Meta Platforms, Inc. and affiliates.
2 | @REM
3 | @REM This source code is licensed under the MIT license found in the
4 | @REM LICENSE file in the root directory of this source tree.
5 |
6 | @rem
7 | @rem Copyright 2015 the original author or authors.
8 | @rem
9 | @rem Licensed under the Apache License, Version 2.0 (the "License");
10 | @rem you may not use this file except in compliance with the License.
11 | @rem You may obtain a copy of the License at
12 | @rem
13 | @rem https://www.apache.org/licenses/LICENSE-2.0
14 | @rem
15 | @rem Unless required by applicable law or agreed to in writing, software
16 | @rem distributed under the License is distributed on an "AS IS" BASIS,
17 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 | @rem See the License for the specific language governing permissions and
19 | @rem limitations under the License.
20 | @rem
21 | @rem SPDX-License-Identifier: Apache-2.0
22 | @rem
23 |
24 | @if "%DEBUG%"=="" @echo off
25 | @rem ##########################################################################
26 | @rem
27 | @rem Gradle startup script for Windows
28 | @rem
29 | @rem ##########################################################################
30 |
31 | @rem Set local scope for the variables with windows NT shell
32 | if "%OS%"=="Windows_NT" setlocal
33 |
34 | set DIRNAME=%~dp0
35 | if "%DIRNAME%"=="" set DIRNAME=.
36 | @rem This is normally unused
37 | set APP_BASE_NAME=%~n0
38 | set APP_HOME=%DIRNAME%
39 |
40 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
41 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
42 |
43 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
44 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
45 |
46 | @rem Find java.exe
47 | if defined JAVA_HOME goto findJavaFromJavaHome
48 |
49 | set JAVA_EXE=java.exe
50 | %JAVA_EXE% -version >NUL 2>&1
51 | if %ERRORLEVEL% equ 0 goto execute
52 |
53 | echo. 1>&2
54 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
55 | echo. 1>&2
56 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
57 | echo location of your Java installation. 1>&2
58 |
59 | goto fail
60 |
61 | :findJavaFromJavaHome
62 | set JAVA_HOME=%JAVA_HOME:"=%
63 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
64 |
65 | if exist "%JAVA_EXE%" goto execute
66 |
67 | echo. 1>&2
68 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
69 | echo. 1>&2
70 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
71 | echo location of your Java installation. 1>&2
72 |
73 | goto fail
74 |
75 | :execute
76 | @rem Setup the command line
77 |
78 | set CLASSPATH=
79 |
80 |
81 | @rem Execute Gradle
82 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
83 |
84 | :end
85 | @rem End local scope for the variables with windows NT shell
86 | if %ERRORLEVEL% equ 0 goto mainEnd
87 |
88 | :fail
89 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
90 | rem the _cmd.exe /c_ return code!
91 | set EXIT_CODE=%ERRORLEVEL%
92 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
93 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
94 | exit /b %EXIT_CODE%
95 |
96 | :mainEnd
97 | if "%OS%"=="Windows_NT" endlocal
98 |
99 | :omega
100 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/Bug Report.yml:
--------------------------------------------------------------------------------
1 | name: 🐞 Report a bug
2 | description: Have you found a bug or issue? Create a bug report for this library
3 | labels: ['bug']
4 |
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: |
9 | **Please do not report security vulnerabilities here**. The [Responsible Disclosure Program](https://auth0.com/responsible-disclosure-policy) details the procedure for disclosing security issues.
10 |
11 | - type: checkboxes
12 | id: checklist
13 | attributes:
14 | label: Checklist
15 | options:
16 | - label: The issue can be reproduced in the [react-native-auth0 sample app](https://github.com/auth0-samples/auth0-react-native-sample) (or N/A).
17 | required: true
18 | - label: I have looked into the [Readme](https://github.com/auth0/react-native-auth0#readme), [Examples](https://github.com/auth0/react-native-auth0/blob/master/EXAMPLES.md), and [FAQ](https://github.com/auth0/react-native-auth0/blob/master/FAQ.md) and have not found a suitable solution or answer.
19 | required: true
20 | - label: I have looked into the [API documentation](https://auth0.github.io/react-native-auth0/) and have not found a suitable solution or answer.
21 | required: true
22 | - label: I have searched the [issues](https://github.com/auth0/react-native-auth0/issues) and have not found a suitable solution or answer.
23 | required: true
24 | - label: I have searched the [Auth0 Community](https://community.auth0.com) forums and have not found a suitable solution or answer.
25 | required: true
26 | - label: I agree to the terms within the [Auth0 Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md).
27 | required: true
28 |
29 | - type: textarea
30 | id: description
31 | attributes:
32 | label: Description
33 | description: Provide a clear and concise description of the issue, including what you expected to happen.
34 | validations:
35 | required: true
36 |
37 | - type: textarea
38 | id: reproduction
39 | attributes:
40 | label: Reproduction
41 | description: Detail the steps taken to reproduce this error, and whether this issue can be reproduced consistently or if it is intermittent.
42 | placeholder: |
43 | 1. Step 1...
44 | 2. Step 2...
45 | 3. ...
46 | validations:
47 | required: true
48 |
49 | - type: textarea
50 | id: additional-context
51 | attributes:
52 | label: Additional context
53 | description: Other libraries that might be involved, or any other relevant information you think would be useful.
54 | validations:
55 | required: false
56 |
57 | - type: input
58 | id: environment-version
59 | attributes:
60 | label: react-native-auth0 version
61 | validations:
62 | required: true
63 |
64 | - type: input
65 | id: environment-react-native-version
66 | attributes:
67 | label: React Native version
68 | validations:
69 | required: true
70 |
71 | - type: input
72 | id: environment-expo-version
73 | attributes:
74 | label: Expo version
75 | validations:
76 | required: false
77 |
78 | - type: dropdown
79 | id: environment-platform
80 | attributes:
81 | label: Platform
82 | multiple: true
83 | options:
84 | - Android
85 | - iOS
86 | validations:
87 | required: true
88 |
89 | - type: input
90 | id: environment-platform-version
91 | attributes:
92 | label: Platform version(s)
93 | validations:
94 | required: true
95 |
--------------------------------------------------------------------------------
/example/ios/Auth0Example.xcodeproj/xcshareddata/xcschemes/Auth0Example.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
53 |
55 |
61 |
62 |
63 |
64 |
70 |
72 |
78 |
79 |
80 |
81 |
83 |
84 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Contributions are always welcome, no matter how large or small!
4 |
5 | We appreciate feedback and contribution to this repo! Before you get started, please see [Auth0's general contribution guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md)
6 |
7 | ## Development workflow
8 |
9 | This project is a monorepo managed using [Yarn workspaces](https://yarnpkg.com/features/workspaces). It contains the following packages:
10 |
11 | - The library package in the root directory.
12 | - An example app in the `example/` directory.
13 |
14 | To get started with the project, run `yarn` in the root directory to install the required dependencies for each package:
15 |
16 | ```sh
17 | yarn
18 | ```
19 |
20 | > Since the project relies on Yarn workspaces, you cannot use [`npm`](https://github.com/npm/cli) for development.
21 |
22 | The [example app](/example/) demonstrates usage of the library. You need to run it to test any changes you make.
23 |
24 | It is configured to use the local version of the library, so any changes you make to the library's source code will be reflected in the example app. Changes to the library's JavaScript code will be reflected in the example app without a rebuild, but native code changes will require a rebuild of the example app.
25 |
26 | If you want to use Android Studio or XCode to edit the native code, you can open the `example/android` or `example/ios` directories respectively in those editors. To edit the Objective-C or Swift files, open `example/ios/Auth0Example.xcworkspace` in XCode and find the source files at `Pods > Development Pods > react-native-auth0`.
27 |
28 | To edit the Java or Kotlin files, open `example/android` in Android studio and find the source files at `react-native-auth0` under `Android`.
29 |
30 | You can use various commands from the root directory to work with the project.
31 |
32 | To start the packager:
33 |
34 | ```sh
35 | yarn example start
36 | ```
37 |
38 | To run the example app on Android:
39 |
40 | ```sh
41 | yarn example android
42 | ```
43 |
44 | To run the example app on iOS:
45 |
46 | ```sh
47 | yarn example ios
48 | ```
49 |
50 | To confirm that the app is running with the new architecture, you can check the Metro logs for a message like this:
51 |
52 | ```sh
53 | Running "Auth0Example" with {"fabric":true,"initialProps":{"concurrentRoot":true},"rootTag":1}
54 | ```
55 |
56 | Note the `"fabric":true` and `"concurrentRoot":true` properties.
57 |
58 | Make sure your code passes TypeScript and ESLint. Run the following to verify:
59 |
60 | ```sh
61 | yarn typecheck
62 | yarn lint
63 | ```
64 |
65 | To fix formatting errors, run the following:
66 |
67 | ```sh
68 | yarn lint --fix
69 | ```
70 |
71 | Remember to add tests for your change if possible. Run the unit tests by:
72 |
73 | ```sh
74 | yarn test
75 | ```
76 |
77 | ### Scripts
78 |
79 | The `package.json` file contains various scripts for common tasks:
80 |
81 | - `yarn`: setup project by installing dependencies.
82 | - `yarn typecheck`: type-check files with TypeScript.
83 | - `yarn lint`: lint files with ESLint.
84 | - `yarn test`: run unit tests with Jest.
85 | - `yarn example start`: start the Metro server for the example app.
86 | - `yarn example android`: run the example app on Android.
87 | - `yarn example ios`: run the example app on iOS.
88 |
89 | ### Sending a pull request
90 |
91 | When you're sending a pull request:
92 |
93 | - Prefer small pull requests focused on one change.
94 | - Verify that linters and tests are passing.
95 | - Review the documentation to make sure it looks good.
96 | - Follow the pull request template when opening a pull request.
97 | - For pull requests that change the API or implementation, discuss with maintainers first by opening an issue.
98 |
--------------------------------------------------------------------------------
/example/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const HtmlWebpackPlugin = require('html-webpack-plugin');
3 |
4 | const appDirectory = path.resolve(__dirname);
5 |
6 | // This is needed for webpack to compile JavaScript.
7 | // Many OSS React Native packages are not compiled to ES5 before being
8 | // published. If you depend on uncompiled packages they may cause webpack build
9 | // errors. To fix this webpack can be configured to compile to the necessary
10 | // `node_module`.
11 | const babelLoaderConfiguration = {
12 | test: /\.(js|jsx|ts|tsx)$/,
13 | // Add every directory that needs to be compiled by Babel during the build.
14 | include: [
15 | path.resolve(appDirectory, 'index.js'),
16 | path.resolve(appDirectory, 'src'),
17 | path.resolve(appDirectory, '../src'), // Included react-native-auth0 source
18 | path.resolve(__dirname, 'node_modules/@react-navigation'),
19 | path.resolve(__dirname, 'node_modules/react-native-safe-area-context'),
20 | path.resolve(__dirname, 'node_modules/react-native-screens'),
21 | path.resolve(__dirname, 'node_modules/react-native-vector-icons'),
22 | ],
23 | use: {
24 | loader: 'babel-loader',
25 | options: {
26 | cacheDirectory: true,
27 | // The 'metro-react-native-babel-preset' preset is recommended to match React Native's packager
28 | presets: ['@react-native/babel-preset'],
29 | // Re-write paths to import only the modules needed by the app
30 | plugins: ['react-native-web'],
31 | },
32 | },
33 | };
34 |
35 | // This is needed for webpack to import static images in JavaScript files.
36 | const imageLoaderConfiguration = {
37 | test: /\.(gif|jpe?g|png|svg)$/,
38 | use: {
39 | loader: 'url-loader',
40 | options: {
41 | // name: '[name].[ext]',
42 | esModule: false,
43 | },
44 | },
45 | };
46 |
47 | module.exports = {
48 | entry: [
49 | // load any web API polyfills
50 | // path.resolve(appDirectory, 'polyfills-web.js'),
51 | // your web-specific entry file
52 | path.resolve(appDirectory, 'index.js'),
53 | ],
54 |
55 | // configures where the build ends up
56 | output: {
57 | filename: 'bundle.web.js',
58 | path: path.resolve(appDirectory, 'dist'),
59 | },
60 |
61 | devServer: {
62 | static: {
63 | directory: path.join(__dirname, 'public'),
64 | },
65 | compress: true,
66 | port: 3000,
67 | historyApiFallback: true,
68 | hot: true,
69 | client: {
70 | webSocketTransport: 'ws',
71 | overlay: {
72 | errors: true,
73 | warnings: false,
74 | },
75 | },
76 | webSocketServer: 'ws',
77 | },
78 |
79 | plugins: [
80 | new HtmlWebpackPlugin({
81 | template: path.resolve(appDirectory, 'public/index.html'),
82 | }),
83 | ],
84 |
85 | module: {
86 | rules: [babelLoaderConfiguration, imageLoaderConfiguration],
87 | },
88 |
89 | resolve: {
90 | // This will only alias the exact import "react-native"
91 | alias: {
92 | 'react-native$': 'react-native-web',
93 | 'react-native-auth0': path.resolve(__dirname, '../src'),
94 | },
95 | // Add module resolution paths to help webpack find react-native-web
96 | modules: [
97 | path.resolve(__dirname, 'node_modules'),
98 | path.resolve(__dirname, '../node_modules'),
99 | 'node_modules',
100 | ],
101 | // If you're working on a multi-platform React Native app, web-specific
102 | // module implementations should be written in files using the extension
103 | // `.web.js`.
104 | extensions: [
105 | '.web.js',
106 | '.web.ts',
107 | '.web.jsx',
108 | '.web.tsx',
109 | '.js',
110 | '.ts',
111 | '.tsx',
112 | ],
113 | },
114 | };
115 |
--------------------------------------------------------------------------------
/example/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | CFPropertyList (3.0.7)
5 | base64
6 | nkf
7 | rexml
8 | activesupport (7.2.2.2)
9 | base64
10 | benchmark (>= 0.3)
11 | bigdecimal
12 | concurrent-ruby (~> 1.0, >= 1.3.1)
13 | connection_pool (>= 2.2.5)
14 | drb
15 | i18n (>= 1.6, < 2)
16 | logger (>= 1.4.2)
17 | minitest (>= 5.1)
18 | securerandom (>= 0.3)
19 | tzinfo (~> 2.0, >= 2.0.5)
20 | addressable (2.8.7)
21 | public_suffix (>= 2.0.2, < 7.0)
22 | algoliasearch (1.27.5)
23 | httpclient (~> 2.8, >= 2.8.3)
24 | json (>= 1.5.1)
25 | atomos (0.1.3)
26 | base64 (0.3.0)
27 | benchmark (0.4.1)
28 | bigdecimal (3.3.1)
29 | claide (1.1.0)
30 | cocoapods (1.15.2)
31 | addressable (~> 2.8)
32 | claide (>= 1.0.2, < 2.0)
33 | cocoapods-core (= 1.15.2)
34 | cocoapods-deintegrate (>= 1.0.3, < 2.0)
35 | cocoapods-downloader (>= 2.1, < 3.0)
36 | cocoapods-plugins (>= 1.0.0, < 2.0)
37 | cocoapods-search (>= 1.0.0, < 2.0)
38 | cocoapods-trunk (>= 1.6.0, < 2.0)
39 | cocoapods-try (>= 1.1.0, < 2.0)
40 | colored2 (~> 3.1)
41 | escape (~> 0.0.4)
42 | fourflusher (>= 2.3.0, < 3.0)
43 | gh_inspector (~> 1.0)
44 | molinillo (~> 0.8.0)
45 | nap (~> 1.0)
46 | ruby-macho (>= 2.3.0, < 3.0)
47 | xcodeproj (>= 1.23.0, < 2.0)
48 | cocoapods-core (1.15.2)
49 | activesupport (>= 5.0, < 8)
50 | addressable (~> 2.8)
51 | algoliasearch (~> 1.0)
52 | concurrent-ruby (~> 1.1)
53 | fuzzy_match (~> 2.0.4)
54 | nap (~> 1.0)
55 | netrc (~> 0.11)
56 | public_suffix (~> 4.0)
57 | typhoeus (~> 1.0)
58 | cocoapods-deintegrate (1.0.5)
59 | cocoapods-downloader (2.1)
60 | cocoapods-plugins (1.0.0)
61 | nap
62 | cocoapods-search (1.0.1)
63 | cocoapods-trunk (1.6.0)
64 | nap (>= 0.8, < 2.0)
65 | netrc (~> 0.11)
66 | cocoapods-try (1.2.0)
67 | colored2 (3.1.2)
68 | concurrent-ruby (1.3.3)
69 | connection_pool (2.5.4)
70 | drb (2.2.3)
71 | escape (0.0.4)
72 | ethon (0.15.0)
73 | ffi (>= 1.15.0)
74 | ffi (1.17.2)
75 | ffi (1.17.2-aarch64-linux-gnu)
76 | ffi (1.17.2-aarch64-linux-musl)
77 | ffi (1.17.2-arm-linux-gnu)
78 | ffi (1.17.2-arm-linux-musl)
79 | ffi (1.17.2-arm64-darwin)
80 | ffi (1.17.2-x86-linux-gnu)
81 | ffi (1.17.2-x86-linux-musl)
82 | ffi (1.17.2-x86_64-darwin)
83 | ffi (1.17.2-x86_64-linux-gnu)
84 | ffi (1.17.2-x86_64-linux-musl)
85 | fourflusher (2.3.1)
86 | fuzzy_match (2.0.4)
87 | gh_inspector (1.1.3)
88 | httpclient (2.9.0)
89 | mutex_m
90 | i18n (1.14.7)
91 | concurrent-ruby (~> 1.0)
92 | json (2.15.1)
93 | logger (1.7.0)
94 | minitest (5.26.0)
95 | molinillo (0.8.0)
96 | mutex_m (0.3.0)
97 | nanaimo (0.3.0)
98 | nap (1.1.0)
99 | netrc (0.11.0)
100 | nkf (0.2.0)
101 | public_suffix (4.0.7)
102 | rexml (3.4.4)
103 | ruby-macho (2.5.1)
104 | securerandom (0.4.1)
105 | typhoeus (1.5.0)
106 | ethon (>= 0.9.0, < 0.16.0)
107 | tzinfo (2.0.6)
108 | concurrent-ruby (~> 1.0)
109 | xcodeproj (1.25.1)
110 | CFPropertyList (>= 2.3.3, < 4.0)
111 | atomos (~> 0.1.3)
112 | claide (>= 1.0.2, < 2.0)
113 | colored2 (~> 3.1)
114 | nanaimo (~> 0.3.0)
115 | rexml (>= 3.3.6, < 4.0)
116 |
117 | PLATFORMS
118 | aarch64-linux-gnu
119 | aarch64-linux-musl
120 | arm-linux-gnu
121 | arm-linux-musl
122 | arm64-darwin
123 | ruby
124 | x86-linux-gnu
125 | x86-linux-musl
126 | x86_64-darwin
127 | x86_64-linux-gnu
128 | x86_64-linux-musl
129 |
130 | DEPENDENCIES
131 | activesupport (>= 6.1.7.5, != 7.1.0)
132 | benchmark
133 | bigdecimal
134 | cocoapods (>= 1.13, != 1.15.1, != 1.15.0)
135 | concurrent-ruby (< 1.3.4)
136 | logger
137 | mutex_m
138 | xcodeproj (< 1.26.0)
139 |
140 | RUBY VERSION
141 | ruby 3.4.2p28
142 |
143 | BUNDLED WITH
144 | 2.6.9
145 |
--------------------------------------------------------------------------------
/src/core/services/ManagementApiOrchestrator.ts:
--------------------------------------------------------------------------------
1 | import type { IUsersClient } from '../interfaces/IUsersClient';
2 | import {
3 | TokenType,
4 | type GetUserParameters,
5 | type PatchUserParameters,
6 | type User,
7 | } from '../../types';
8 | import { Auth0User, AuthError } from '../models';
9 | import {
10 | HttpClient,
11 | getBearerHeader,
12 | type DPoPHeadersProvider,
13 | } from '../services/HttpClient';
14 | import { deepCamelCase } from '../utils';
15 |
16 | /**
17 | * Orchestrates interactions with the Auth0 Management API's user endpoints.
18 | */
19 | export class ManagementApiOrchestrator implements IUsersClient {
20 | private readonly client: HttpClient;
21 | private readonly token: string;
22 | private readonly tokenType: TokenType;
23 | private readonly baseUrl: string;
24 | private readonly getDPoPHeaders?: DPoPHeadersProvider;
25 |
26 | constructor(options: {
27 | token: string;
28 | httpClient: HttpClient;
29 | tokenType?: TokenType;
30 | baseUrl?: string;
31 | getDPoPHeaders?: DPoPHeadersProvider;
32 | }) {
33 | this.token = options.token;
34 | this.client = options.httpClient;
35 | this.tokenType = options.tokenType ?? TokenType.bearer;
36 | this.baseUrl = options.baseUrl ?? '';
37 | this.getDPoPHeaders = options.getDPoPHeaders;
38 | }
39 |
40 | /**
41 | * Creates the specific headers required for Management API requests,
42 | * including the Bearer or DPoP token based on tokenType.
43 | * @param path - The API path (used to build full URL for DPoP proof generation)
44 | * @param method - The HTTP method (needed for DPoP proof generation)
45 | * @returns A promise that resolves to a record of headers for the request.
46 | */
47 | private async getRequestHeaders(
48 | path: string,
49 | method: string
50 | ): Promise> {
51 | if (this.tokenType === TokenType.dpop && this.getDPoPHeaders) {
52 | const fullUrl = `${this.baseUrl}${path}`;
53 | return this.getDPoPHeaders({
54 | url: fullUrl,
55 | method,
56 | accessToken: this.token,
57 | tokenType: TokenType.dpop,
58 | });
59 | }
60 | return getBearerHeader(this.token);
61 | }
62 |
63 | /**
64 | * A helper to process the raw user profile from the Management API.
65 | * It camelCases the keys and maps `userId` to `sub`.
66 | */
67 | private processUserProfile(rawProfile: any): User {
68 | const camelCasedProfile = deepCamelCase(rawProfile);
69 |
70 | // FIX: The Management API returns `user_id`. We must map it to `sub`
71 | // for our `Auth0User` model to be valid.
72 | if (camelCasedProfile.userId && !camelCasedProfile.sub) {
73 | camelCasedProfile.sub = camelCasedProfile.userId;
74 | }
75 |
76 | return camelCasedProfile as User;
77 | }
78 |
79 | async getUser(parameters: GetUserParameters): Promise {
80 | const path = `/api/v2/users/${encodeURIComponent(parameters.id)}`;
81 | const headers = await this.getRequestHeaders(path, 'GET');
82 |
83 | const { json, response } = await this.client.get(
84 | path,
85 | undefined, // No query parameters
86 | headers
87 | );
88 |
89 | if (!response.ok) {
90 | throw AuthError.fromResponse(response, json);
91 | }
92 |
93 | const processedProfile = this.processUserProfile(json);
94 | return new Auth0User(processedProfile);
95 | }
96 |
97 | async patchUser(parameters: PatchUserParameters): Promise {
98 | const path = `/api/v2/users/${encodeURIComponent(parameters.id)}`;
99 | const body = {
100 | user_metadata: parameters.metadata,
101 | };
102 | const headers = await this.getRequestHeaders(path, 'PATCH');
103 |
104 | const { json, response } = await this.client.patch(
105 | path,
106 | body,
107 | headers
108 | );
109 |
110 | if (!response.ok) {
111 | throw AuthError.fromResponse(response, json);
112 | }
113 |
114 | const processedProfile = this.processUserProfile(json);
115 | return new Auth0User(processedProfile);
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/example/ios/Auth0Example/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
24 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/src/core/utils/__tests__/validation.spec.ts:
--------------------------------------------------------------------------------
1 | import { validateAuth0Options } from '../validation';
2 | import { AuthError } from '../../models';
3 |
4 | describe('validateAuth0Options', () => {
5 | const validOptions = {
6 | domain: 'my-tenant.auth0.com',
7 | clientId: 'MyClientId123',
8 | };
9 |
10 | it('should not throw an error for valid options', () => {
11 | // We expect this function call to complete without throwing any error.
12 | expect(() => validateAuth0Options(validOptions)).not.toThrow();
13 | });
14 |
15 | it('should throw an error if the options object is null or undefined', () => {
16 | // Test with undefined
17 | expect(() => validateAuth0Options(undefined as any)).toThrow(AuthError);
18 | expect(() => validateAuth0Options(undefined as any)).toThrow(
19 | 'Auth0 options are required.'
20 | );
21 |
22 | // Test with null
23 | expect(() => validateAuth0Options(null as any)).toThrow(AuthError);
24 | expect(() => validateAuth0Options(null as any)).toThrow(
25 | 'Auth0 options are required.'
26 | );
27 | });
28 |
29 | describe('domain validation', () => {
30 | it('should throw an error if domain is missing', () => {
31 | const options = { ...validOptions, domain: undefined as any };
32 | expect(() => validateAuth0Options(options)).toThrow(
33 | 'A valid "domain" is required for the Auth0 client.'
34 | );
35 | });
36 |
37 | it('should throw an error if domain is not a string', () => {
38 | const options = { ...validOptions, domain: 12345 as any };
39 | expect(() => validateAuth0Options(options)).toThrow(
40 | 'A valid "domain" is required for the Auth0 client.'
41 | );
42 | });
43 |
44 | it('should throw an error if domain is an empty string', () => {
45 | const options = { ...validOptions, domain: '' };
46 | expect(() => validateAuth0Options(options)).toThrow(
47 | 'A valid "domain" is required for the Auth0 client.'
48 | );
49 | });
50 |
51 | it('should throw an error if domain is only whitespace', () => {
52 | const options = { ...validOptions, domain: ' ' };
53 | expect(() => validateAuth0Options(options)).toThrow(
54 | 'A valid "domain" is required for the Auth0 client.'
55 | );
56 | });
57 |
58 | it('should throw an error if domain includes http:// protocol', () => {
59 | const options = { ...validOptions, domain: 'http://my-tenant.auth0.com' };
60 | expect(() => validateAuth0Options(options)).toThrow(
61 | 'The "domain" should not include the protocol (e.g., "https://"). Provide the hostname only.'
62 | );
63 | });
64 |
65 | it('should throw an error if domain includes https:// protocol', () => {
66 | const options = {
67 | ...validOptions,
68 | domain: 'https://my-tenant.auth0.com',
69 | };
70 | expect(() => validateAuth0Options(options)).toThrow(
71 | 'The "domain" should not include the protocol (e.g., "https://"). Provide the hostname only.'
72 | );
73 | });
74 | });
75 |
76 | describe('clientId validation', () => {
77 | it('should throw an error if clientId is missing', () => {
78 | const options = { ...validOptions, clientId: undefined as any };
79 | expect(() => validateAuth0Options(options)).toThrow(
80 | 'A valid "clientId" is required for the Auth0 client.'
81 | );
82 | });
83 |
84 | it('should throw an error if clientId is not a string', () => {
85 | const options = { ...validOptions, clientId: 12345 as any };
86 | expect(() => validateAuth0Options(options)).toThrow(
87 | 'A valid "clientId" is required for the Auth0 client.'
88 | );
89 | });
90 |
91 | it('should throw an error if clientId is an empty string', () => {
92 | const options = { ...validOptions, clientId: '' };
93 | expect(() => validateAuth0Options(options)).toThrow(
94 | 'A valid "clientId" is required for the Auth0 client.'
95 | );
96 | });
97 |
98 | it('should throw an error if clientId is only whitespace', () => {
99 | const options = { ...validOptions, clientId: ' ' };
100 | expect(() => validateAuth0Options(options)).toThrow(
101 | 'A valid "clientId" is required for the Auth0 client.'
102 | );
103 | });
104 | });
105 | });
106 |
--------------------------------------------------------------------------------
/src/core/utils/__tests__/conversion.spec.ts:
--------------------------------------------------------------------------------
1 | import { deepCamelCase, toUrlQueryParams } from '../conversion';
2 |
3 | describe('conversion utilities', () => {
4 | describe('deepCamelCase', () => {
5 | it('should convert top-level snake_case keys to camelCase', () => {
6 | const input = {
7 | first_name: 'John',
8 | last_name: 'Doe',
9 | email_verified: true,
10 | };
11 | const expected = {
12 | firstName: 'John',
13 | lastName: 'Doe',
14 | emailVerified: true,
15 | };
16 | expect(deepCamelCase(input)).toEqual(expected);
17 | });
18 |
19 | it('should convert nested snake_case keys to camelCase', () => {
20 | const input = {
21 | user_profile: {
22 | first_name: 'Jane',
23 | phone_number: '555-555-5555',
24 | },
25 | app_metadata: {
26 | plan_type: 'premium',
27 | },
28 | };
29 | const expected = {
30 | userProfile: {
31 | firstName: 'Jane',
32 | phoneNumber: '555-555-5555',
33 | },
34 | appMetadata: {
35 | planType: 'premium',
36 | },
37 | };
38 | expect(deepCamelCase(input)).toEqual(expected);
39 | });
40 |
41 | it('should convert keys in an array of objects', () => {
42 | const input = [
43 | { user_id: '123', display_name: 'user_one' },
44 | { user_id: '456', display_name: 'user_two' },
45 | ];
46 | const expected = [
47 | { userId: '123', displayName: 'user_one' },
48 | { userId: '456', displayName: 'user_two' },
49 | ];
50 | expect(deepCamelCase(input)).toEqual(expected);
51 | });
52 |
53 | it('should handle keys with numbers like "claim_1"', () => {
54 | const input = { custom_claim_1: 'value1', custom_claim_2: 'value2' };
55 | const expected = { customClaim1: 'value1', customClaim2: 'value2' };
56 | expect(deepCamelCase(input)).toEqual(expected);
57 | });
58 |
59 | it('should not modify keys that are already camelCased', () => {
60 | const input = {
61 | firstName: 'John',
62 | userProfile: {
63 | lastName: 'Doe',
64 | },
65 | };
66 | // It should be identical
67 | expect(deepCamelCase(input)).toEqual(input);
68 | });
69 |
70 | it('should return primitives, null, and undefined as-is', () => {
71 | expect(deepCamelCase('a string')).toBe('a string');
72 | expect(deepCamelCase(12345)).toBe(12345);
73 | expect(deepCamelCase(true)).toBe(true);
74 | expect(deepCamelCase(null)).toBeNull();
75 | expect(deepCamelCase(undefined)).toBeUndefined();
76 | });
77 |
78 | it('should handle an empty object and an empty array', () => {
79 | expect(deepCamelCase({})).toEqual({});
80 | expect(deepCamelCase([])).toEqual([]);
81 | });
82 | });
83 |
84 | describe('toUrlQueryParams', () => {
85 | it('should convert a simple object to a query string', () => {
86 | const params = {
87 | response_type: 'code',
88 | client_id: '12345',
89 | state: 'xyz',
90 | };
91 | const expected = 'response_type=code&client_id=12345&state=xyz';
92 | expect(toUrlQueryParams(params)).toBe(expected);
93 | });
94 |
95 | it('should correctly URL-encode special characters', () => {
96 | const params = {
97 | scope: 'openid profile email',
98 | redirect_uri: 'https://my-app.com/callback',
99 | };
100 | const expected =
101 | 'scope=openid+profile+email&redirect_uri=https%3A%2F%2Fmy-app.com%2Fcallback';
102 | expect(toUrlQueryParams(params)).toBe(expected);
103 | });
104 |
105 | it('should ignore keys with null or undefined values', () => {
106 | const params = {
107 | scope: 'openid',
108 | connection: null,
109 | audience: undefined,
110 | state: 'xyz',
111 | };
112 | const expected = 'scope=openid&state=xyz';
113 | expect(toUrlQueryParams(params)).toBe(expected);
114 | });
115 |
116 | it('should return an empty string for an empty object', () => {
117 | expect(toUrlQueryParams({})).toBe('');
118 | });
119 |
120 | it('should handle numbers and booleans correctly', () => {
121 | const params = {
122 | max_age: 3600,
123 | ephemeral: true,
124 | };
125 | const expected = 'max_age=3600&ephemeral=true';
126 | expect(toUrlQueryParams(params)).toBe(expected);
127 | });
128 | });
129 | });
130 |
--------------------------------------------------------------------------------