├── docs ├── static │ ├── .nojekyll │ ├── img │ │ ├── google.png │ │ ├── okta.png │ │ ├── uber.png │ │ ├── hero-pattern.png │ │ ├── react-native-app-auth-logo.png │ │ ├── nearform-icon.svg │ │ ├── nearform-logo-white.svg │ │ ├── nearform-icon-white.svg │ │ └── nearform-logo.svg │ └── font │ │ ├── InterBold.woff2 │ │ ├── InterMedium.woff2 │ │ └── InterRegular.woff2 ├── babel.config.js ├── tsconfig.json ├── docs │ ├── usage │ │ ├── _category_.json │ │ ├── refresh.md │ │ ├── revoke.md │ │ ├── prefetch.md │ │ ├── logout.md │ │ ├── errors.md │ │ ├── authorization.md │ │ ├── register.md │ │ └── config.md │ ├── providers │ │ ├── _category_.json │ │ ├── azure-active-directory-b2c.md │ │ ├── keycloak.md │ │ ├── github.md │ │ ├── slack.md │ │ ├── unsplash.md │ │ ├── microsoft.md │ │ ├── reddit.md │ │ ├── fusionauth.md │ │ ├── coinbase.md │ │ ├── asgardeo.md │ │ ├── fitbit.md │ │ ├── dropbox.md │ │ ├── uber.md │ │ ├── identity-server-3.md │ │ ├── microsoft-entra-id.md │ │ ├── google.md │ │ ├── identity-server-4.md │ │ ├── strava.md │ │ ├── aws-cognito.md │ │ ├── spotify.md │ │ ├── okta.md │ │ └── azure-active-directory.md │ ├── client-secrets.md │ └── token-storage.md ├── src │ ├── components │ │ └── landing │ │ │ ├── divider.tsx │ │ │ ├── landing-images.tsx │ │ │ ├── landing-banner.tsx │ │ │ ├── nf-link-button.tsx │ │ │ ├── landing-features.tsx │ │ │ ├── landing-featured-projects.tsx │ │ │ └── landing-hero.tsx │ ├── theme │ │ ├── prism-diff-highlight.css │ │ ├── prism-include-languages.ts │ │ └── prism-diff-highlight.ts │ ├── pages │ │ └── index.tsx │ └── css │ │ └── custom.css ├── sidebars.ts ├── .gitignore ├── README.md ├── config-examples │ └── imgur.md ├── package.json ├── tailwind.config.ts └── docusaurus.config.ts ├── .gitattributes ├── examples └── demo │ ├── .watchmanconfig │ ├── ios │ ├── .xcode.env │ ├── Example │ │ ├── Images.xcassets │ │ │ ├── Contents.json │ │ │ └── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ ├── main.m │ │ ├── AppDelegate.h │ │ ├── AppDelegate.mm │ │ ├── Info.plist │ │ └── LaunchScreen.storyboard │ ├── Example.xcworkspace │ │ └── contents.xcworkspacedata │ ├── _xcode.env │ ├── ExampleTests │ │ ├── Info.plist │ │ └── ExampleTests.m │ ├── Podfile │ └── Example.xcodeproj │ │ └── xcshareddata │ │ └── xcschemes │ │ └── Example.xcscheme │ ├── jest.config.js │ ├── app.json │ ├── _bundle │ └── config │ ├── tsconfig.json │ ├── .eslintrc.js │ ├── demo.gif │ ├── babel.config.js │ ├── assets │ └── background.jpg │ ├── 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 │ │ │ │ │ │ └── example │ │ │ │ │ │ ├── MainActivity.java │ │ │ │ │ │ └── MainApplication.java │ │ │ │ └── AndroidManifest.xml │ │ │ ├── debug │ │ │ │ ├── AndroidManifest.xml │ │ │ │ └── java │ │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── ReactNativeFlipper.java │ │ │ └── release │ │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── ReactNativeFlipper.java │ │ ├── debug.keystore │ │ ├── proguard-rules.pro │ │ └── build.gradle │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── settings.gradle │ ├── build.gradle │ ├── gradle.properties │ └── gradlew.bat │ ├── .prettierrc.js │ ├── Gemfile │ ├── index.js │ ├── components │ ├── Form.js │ ├── index.js │ ├── FormLabel.js │ ├── ButtonContainer.js │ ├── FormValue.js │ ├── Heading.js │ ├── Button.js │ └── Page.js │ ├── __tests__ │ └── App.test.tsx │ ├── CHANGELOG.md │ ├── package.json │ ├── metro.config.js │ ├── .gitignore │ ├── README.md │ └── App.js ├── .eslintignore ├── packages └── react-native-app-auth │ ├── .watchmanconfig │ ├── babel.config.js │ ├── ios │ ├── .npmignore │ ├── RNAppAuth.h │ ├── RNAppAuthAuthorizationFlowManagerDelegate.h │ ├── RNAppAuth.xcworkspace │ │ └── contents.xcworkspacedata │ └── RNAppAuthAuthorizationFlowManager.h │ ├── android │ ├── .npmignore │ ├── src │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ └── com │ │ │ └── rnappauth │ │ │ ├── utils │ │ │ ├── DateUtil.java │ │ │ ├── EndSessionResponseFactory.java │ │ │ ├── MutableBrowserAllowList.java │ │ │ ├── RegistrationResponseFactory.java │ │ │ ├── CustomConnectionBuilder.java │ │ │ ├── TokenResponseFactory.java │ │ │ ├── MapUtil.java │ │ │ └── UnsafeConnectionBuilder.java │ │ │ └── RNAppAuthPackage.java │ └── build.gradle │ ├── react-native-app-auth.podspec │ ├── package.json │ ├── CHANGELOG.md │ └── index.d.ts ├── .prettierrc ├── .github ├── ISSUE_TEMPLATE │ ├── Feature_Request.md │ ├── Documentstion_Issue.md │ └── Bug_Report.md ├── PULL_REQUEST_TEMPLATE.md ├── workflows │ ├── pull-request.yml │ └── release.yml ├── CODE_OF_CONDUCT.md └── CONTRIBUTING.md ├── .changeset └── config.json ├── .eslintrc ├── .gitignore ├── LICENSE └── package.json /docs/static/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text -------------------------------------------------------------------------------- /examples/demo/.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | Example 2 | docs/docusaurus.config.ts 3 | -------------------------------------------------------------------------------- /examples/demo/ios/.xcode.env: -------------------------------------------------------------------------------- 1 | export NODE_BINARY=$(command -v node) 2 | -------------------------------------------------------------------------------- /examples/demo/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'react-native', 3 | }; 4 | -------------------------------------------------------------------------------- /examples/demo/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Example", 3 | "displayName": "Example" 4 | } 5 | -------------------------------------------------------------------------------- /examples/demo/_bundle/config: -------------------------------------------------------------------------------- 1 | BUNDLE_PATH: "vendor/bundle" 2 | BUNDLE_FORCE_RUBY_PLATFORM: 1 3 | -------------------------------------------------------------------------------- /examples/demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/react-native/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /examples/demo/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: '@react-native', 4 | }; 5 | -------------------------------------------------------------------------------- /examples/demo/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/react-native-app-auth/master/examples/demo/demo.gif -------------------------------------------------------------------------------- /packages/react-native-app-auth/.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": [ 3 | "node_modules" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /docs/static/img/google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/react-native-app-auth/master/docs/static/img/google.png -------------------------------------------------------------------------------- /docs/static/img/okta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/react-native-app-auth/master/docs/static/img/okta.png -------------------------------------------------------------------------------- /docs/static/img/uber.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/react-native-app-auth/master/docs/static/img/uber.png -------------------------------------------------------------------------------- /examples/demo/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['module:metro-react-native-babel-preset'], 3 | }; 4 | -------------------------------------------------------------------------------- /docs/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "singleQuote": true, 4 | "semi": true, 5 | "trailingComma": "es5" 6 | } 7 | -------------------------------------------------------------------------------- /docs/static/font/InterBold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/react-native-app-auth/master/docs/static/font/InterBold.woff2 -------------------------------------------------------------------------------- /docs/static/img/hero-pattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/react-native-app-auth/master/docs/static/img/hero-pattern.png -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@docusaurus/tsconfig", 3 | "compilerOptions": { 4 | "baseUrl": "." 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /docs/static/font/InterMedium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/react-native-app-auth/master/docs/static/font/InterMedium.woff2 -------------------------------------------------------------------------------- /docs/static/font/InterRegular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/react-native-app-auth/master/docs/static/font/InterRegular.woff2 -------------------------------------------------------------------------------- /examples/demo/assets/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/react-native-app-auth/master/examples/demo/assets/background.jpg -------------------------------------------------------------------------------- /packages/react-native-app-auth/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['module:metro-react-native-babel-preset'], 3 | }; 4 | -------------------------------------------------------------------------------- /examples/demo/android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Example 3 | 4 | -------------------------------------------------------------------------------- /examples/demo/ios/Example/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/react-native-app-auth/ios/.npmignore: -------------------------------------------------------------------------------- 1 | */project.xcworkspace/ 2 | */xcuserdata/ 3 | .DS_Store 4 | .npmignore 5 | Pods/ 6 | build/ 7 | -------------------------------------------------------------------------------- /docs/docs/usage/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Usage", 3 | "position": 3, 4 | "link": { 5 | "type": "generated-index" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/demo/android/app/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/react-native-app-auth/master/examples/demo/android/app/debug.keystore -------------------------------------------------------------------------------- /docs/docs/providers/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Providers", 3 | "position": 4, 4 | "link": { 5 | "type": "generated-index" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /docs/static/img/react-native-app-auth-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/react-native-app-auth/master/docs/static/img/react-native-app-auth-logo.png -------------------------------------------------------------------------------- /packages/react-native-app-auth/ios/RNAppAuth.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface RNAppAuth : NSObject 4 | 5 | @end 6 | 7 | -------------------------------------------------------------------------------- /docs/src/components/landing/divider.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const Divider = () => { 4 | return
; 5 | }; 6 | -------------------------------------------------------------------------------- /examples/demo/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/react-native-app-auth/master/examples/demo/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /examples/demo/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | arrowParens: 'avoid', 3 | bracketSameLine: true, 4 | bracketSpacing: false, 5 | singleQuote: true, 6 | trailingComma: 'all', 7 | }; 8 | -------------------------------------------------------------------------------- /examples/demo/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/react-native-app-auth/master/examples/demo/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/demo/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/react-native-app-auth/master/examples/demo/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /packages/react-native-app-auth/android/.npmignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .DS_Store 3 | .gradle/ 4 | .idea/ 5 | .npmignore 6 | build/ 7 | gradle/ 8 | gradlew 9 | gradlew.bat 10 | local.properties 11 | -------------------------------------------------------------------------------- /examples/demo/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/react-native-app-auth/master/examples/demo/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/demo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/react-native-app-auth/master/examples/demo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/demo/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 | gem 'cocoapods', '~> 1.12' 7 | -------------------------------------------------------------------------------- /examples/demo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/react-native-app-auth/master/examples/demo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /packages/react-native-app-auth/android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/demo/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/react-native-app-auth/master/examples/demo/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /examples/demo/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/react-native-app-auth/master/examples/demo/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /examples/demo/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/react-native-app-auth/master/examples/demo/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /examples/demo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/react-native-app-auth/master/examples/demo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /examples/demo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envato/react-native-app-auth/master/examples/demo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Feature_Request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🎁 Feature request 3 | about: Requesting new features 4 | --- 5 | 6 | 7 | -------------------------------------------------------------------------------- /docs/sidebars.ts: -------------------------------------------------------------------------------- 1 | import { SidebarsConfig } from '@docusaurus/plugin-content-docs'; 2 | 3 | const sidebars: SidebarsConfig = { 4 | sidebar: [{ type: 'autogenerated', dirName: '.' }], 5 | }; 6 | 7 | export default sidebars; 8 | -------------------------------------------------------------------------------- /docs/src/components/landing/landing-images.tsx: -------------------------------------------------------------------------------- 1 | import uber from '@site/static/img/uber.png'; 2 | import google from '@site/static/img/google.png'; 3 | import okta from '@site/static/img/okta.png'; 4 | 5 | export { uber, google, okta }; 6 | -------------------------------------------------------------------------------- /examples/demo/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @format 3 | */ 4 | 5 | import {AppRegistry} from 'react-native'; 6 | import App from './App'; 7 | import {name as appName} from './app.json'; 8 | 9 | AppRegistry.registerComponent(appName, () => App); 10 | -------------------------------------------------------------------------------- /packages/react-native-app-auth/ios/RNAppAuthAuthorizationFlowManagerDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @protocol RNAppAuthAuthorizationFlowManagerDelegate 4 | @required 5 | -(BOOL)resumeExternalUserAgentFlowWithURL:(NSURL *)url; 6 | @end 7 | -------------------------------------------------------------------------------- /examples/demo/ios/Example/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 | } 11 | -------------------------------------------------------------------------------- /packages/react-native-app-auth/ios/RNAppAuth.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | 3 | 5 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/src/theme/prism-diff-highlight.css: -------------------------------------------------------------------------------- 1 | code .token.diff-highlight-deleted { 2 | background-color: rgba(255, 0, 0, .1); 3 | } 4 | 5 | code .token.diff-highlight-inserted { 6 | background-color: rgba(0, 255, 128, .1); 7 | } 8 | 9 | code .token.coord { 10 | font-weight: 700; 11 | } 12 | -------------------------------------------------------------------------------- /examples/demo/android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'Example' 2 | apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) 3 | include ':app' 4 | includeBuild('../node_modules/@react-native/gradle-plugin') 5 | -------------------------------------------------------------------------------- /examples/demo/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.1-all.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json", 3 | "changelog": [ 4 | "@svitejs/changesets-changelog-github-compact", 5 | { 6 | "repo": "FormidableLabs/react-native-app-auth" 7 | } 8 | ], 9 | "access": "public", 10 | "baseBranch": "main" 11 | } -------------------------------------------------------------------------------- /docs/static/img/nearform-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /examples/demo/components/Form.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyleSheet, View } from 'react-native'; 3 | 4 | const Form = props => ; 5 | 6 | const styles = StyleSheet.create({ 7 | form: { 8 | flex: 1 9 | }, 10 | }); 11 | 12 | export default Form; 13 | -------------------------------------------------------------------------------- /examples/demo/ios/Example.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /docs/static/img/nearform-logo-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/react-native-app-auth/ios/RNAppAuthAuthorizationFlowManager.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "RNAppAuthAuthorizationFlowManagerDelegate.h" 3 | 4 | @protocol RNAppAuthAuthorizationFlowManager 5 | @required 6 | @property(nonatomic, weak)idauthorizationFlowManagerDelegate; 7 | @end 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Documentstion_Issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 📖 Documentation Feedback 3 | about: Report an issue with the documentation or suggest an improvement. 4 | --- 5 | 6 | ## Documentation Feedback 7 | 8 | 9 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Fixes #... 2 | 3 | ## Description 4 | 5 | 6 | 7 | ## Steps to verify 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/demo/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /examples/demo/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as Button } from './Button'; 2 | export { default as ButtonContainer } from './ButtonContainer'; 3 | export { default as Form } from './Form'; 4 | export { default as FormLabel } from './FormLabel'; 5 | export { default as FormValue } from './FormValue'; 6 | export { default as Heading } from './Heading'; 7 | export { default as Page } from './Page'; 8 | -------------------------------------------------------------------------------- /examples/demo/ios/Example/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | #import "RNAppAuthAuthorizationFlowManager.h" 5 | 6 | @interface AppDelegate : RCTAppDelegate 7 | 8 | @property(nonatomic, weak) id authorizationFlowManagerDelegate; 9 | 10 | @end 11 | -------------------------------------------------------------------------------- /docs/static/img/nearform-icon-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/static/img/nearform-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples/demo/components/FormLabel.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Text, StyleSheet } from 'react-native'; 3 | 4 | const FormLabel = props => ; 5 | 6 | const styles = StyleSheet.create({ 7 | formText: { 8 | fontSize: 14, 9 | fontWeight: 'bold', 10 | backgroundColor: 'transparent', 11 | marginBottom: 10, 12 | }, 13 | }); 14 | 15 | export default FormLabel; 16 | -------------------------------------------------------------------------------- /examples/demo/__tests__/App.test.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @format 3 | */ 4 | 5 | import 'react-native'; 6 | import React from 'react'; 7 | import App from '../App'; 8 | 9 | // Note: import explicitly to use the types shiped with jest. 10 | import {it} from '@jest/globals'; 11 | 12 | // Note: test renderer must be required after react-native. 13 | import renderer from 'react-test-renderer'; 14 | 15 | it('renders correctly', () => { 16 | renderer.create(); 17 | }); 18 | -------------------------------------------------------------------------------- /examples/demo/components/ButtonContainer.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyleSheet, View } from 'react-native'; 3 | 4 | const ButtonContainer = props => ; 5 | 6 | const styles = StyleSheet.create({ 7 | view: { 8 | position: 'absolute', 9 | left: 0, 10 | right: 0, 11 | bottom: 0, 12 | alignSelf: 'flex-end', 13 | flexDirection: 'row', 14 | margin: 5 15 | } 16 | }); 17 | 18 | export default ButtonContainer; 19 | -------------------------------------------------------------------------------- /examples/demo/components/FormValue.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Text, StyleSheet } from 'react-native'; 3 | 4 | const FormValue = props => ( 5 | 11 | ); 12 | 13 | const styles = StyleSheet.create({ 14 | text: { 15 | fontSize: 14, 16 | backgroundColor: 'transparent', 17 | marginBottom: 20, 18 | } 19 | }); 20 | 21 | export default FormValue; 22 | -------------------------------------------------------------------------------- /examples/demo/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # rnaa-demo 2 | 3 | ## 1.0.1 4 | 5 | ### Patch Changes 6 | 7 | - demo app with new auth0 ([#1007](https://github.com/FormidableLabs/react-native-app-auth/pull/1007)) 8 | 9 | - Updated dependencies [[`31f903f`](https://github.com/FormidableLabs/react-native-app-auth/commit/31f903fe508d4007447b3e8f5c164a5027f3b6ae), [`438d512`](https://github.com/FormidableLabs/react-native-app-auth/commit/438d5121ec48d16f210b57691381f937979ee448)]: 10 | - react-native-app-auth@8.0.0 11 | -------------------------------------------------------------------------------- /examples/demo/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 | -------------------------------------------------------------------------------- /examples/demo/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 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | --- 2 | extends: 3 | - formidable 4 | - prettier 5 | plugins: 6 | - jest 7 | - prettier 8 | env: 9 | jest/globals: true 10 | browser: true 11 | rules: 12 | quotes: 13 | - error 14 | - single 15 | no-unsafe-negation: 16 | - off 17 | max-nested-callbacks: 18 | - off 19 | arrow-parens: 20 | - off 21 | comma-dangle: 22 | - 2 23 | - always-multiline 24 | prettier/prettier: 25 | - error 26 | - trailingComma: es5 27 | eqeqeq: 28 | - error 29 | - smart 30 | max-statements: 31 | - off 32 | -------------------------------------------------------------------------------- /examples/demo/components/Heading.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Platform, Text, StyleSheet } from 'react-native'; 3 | 4 | const font = Platform.select({ 5 | ios: 'GillSans-light', 6 | android: 'sans-serif-thin' 7 | }); 8 | 9 | const Heading = () => ; 10 | 11 | const styles = StyleSheet.create({ 12 | text: { 13 | color: 'black', 14 | fontSize: 32, 15 | marginTop: 120, 16 | backgroundColor: 'transparent', 17 | textAlign: 'center', 18 | } 19 | }); 20 | 21 | export default Heading; 22 | -------------------------------------------------------------------------------- /docs/docs/usage/refresh.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | --- 4 | 5 | # Refresh Token 6 | 7 | This method will refresh the accessToken using the refreshToken. Some auth providers will also give 8 | you a new refreshToken 9 | 10 | ```js 11 | import { refresh } from 'react-native-app-auth'; 12 | 13 | const config = { 14 | issuer: '', 15 | clientId: '', 16 | redirectUrl: '', 17 | scopes: [''], 18 | }; 19 | 20 | const result = await refresh(config, { 21 | refreshToken: ``, 22 | }); 23 | ``` 24 | -------------------------------------------------------------------------------- /.github/workflows/pull-request.yml: -------------------------------------------------------------------------------- 1 | name: Pull Request 2 | 3 | on: 4 | pull_request: 5 | branches: [ main ] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v4 13 | 14 | - name: Use Node.js 15 | uses: actions/setup-node@v4 16 | with: 17 | node-version: 18 18 | cache: 'yarn' 19 | 20 | - name: Install dependencies 21 | run: yarn install --frozen-lockfile 22 | 23 | - name: Lint 24 | run: yarn run lint 25 | 26 | - name: Test 27 | run: yarn run test 28 | -------------------------------------------------------------------------------- /docs/docs/usage/revoke.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 5 3 | --- 4 | 5 | # Revoke Token 6 | 7 | This method will revoke a token. The tokenToRevoke can be either an accessToken or a refreshToken 8 | 9 | ```js 10 | import { revoke } from 'react-native-app-auth'; 11 | 12 | const config = { 13 | issuer: '', 14 | clientId: '', 15 | redirectUrl: '', 16 | scopes: [''], 17 | }; 18 | 19 | const result = await revoke(config, { 20 | tokenToRevoke: ``, 21 | includeBasicAuth: true, 22 | sendClientId: true, 23 | }); 24 | ``` 25 | -------------------------------------------------------------------------------- /examples/demo/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/docs/usage/prefetch.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 8 3 | --- 4 | 5 | # Android Prefetch 6 | 7 | This will prefetch the authorization service configuration. Invoking this function is optional and will speed up calls to authorize. This is only supported on Android. 8 | 9 | ```js 10 | import { prefetchConfiguration } from 'react-native-app-auth'; 11 | 12 | const config = { 13 | warmAndPrefetchChrome: true, 14 | issuer: '', 15 | clientId: '', 16 | redirectUrl: '', 17 | scopes: [''], 18 | }; 19 | 20 | prefetchConfiguration(config); 21 | ``` 22 | -------------------------------------------------------------------------------- /packages/react-native-app-auth/android/src/main/java/com/rnappauth/utils/DateUtil.java: -------------------------------------------------------------------------------- 1 | package com.rnappauth.utils; 2 | 3 | import java.text.SimpleDateFormat; 4 | import java.util.Date; 5 | import java.util.Locale; 6 | import java.util.TimeZone; 7 | 8 | public final class DateUtil { 9 | public static final String formatTimestamp(Long timestamp) { 10 | Date expirationDate = new Date(timestamp); 11 | SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US); 12 | formatter.setTimeZone(TimeZone.getTimeZone("UTC")); 13 | return formatter.format(expirationDate); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /docs/docs/usage/logout.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 6 3 | --- 4 | 5 | # Logout 6 | 7 | This method will logout a user, as per the [OpenID Connect RP Initiated Logout](https://openid.net/specs/openid-connect-rpinitiated-1_0.html) specification. It requires an `idToken`, obtained after successfully authenticating with OpenID Connect, and a URL to redirect back after the logout has been performed. 8 | 9 | ```js 10 | import { logout } from 'react-native-app-auth'; 11 | 12 | const config = { 13 | issuer: '', 14 | }; 15 | 16 | const result = await logout(config, { 17 | idToken: '', 18 | postLogoutRedirectUrl: '', 19 | }); 20 | ``` 21 | -------------------------------------------------------------------------------- /.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 | android/gradle/ 33 | android/gradlew 34 | android/gradlew.bat 35 | 36 | # node.js 37 | # 38 | node_modules/ 39 | npm-debug.log 40 | yarn-error.log 41 | 42 | # BUCK 43 | buck-out/ 44 | \.buckd/ 45 | *.keystore 46 | 47 | Example/yarn.lock 48 | -------------------------------------------------------------------------------- /docs/src/components/landing/landing-banner.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { NFLinkButton } from './nf-link-button'; 3 | import { Divider } from './divider'; 4 | 5 | export const LandingBanner = ({ 6 | body, 7 | cta, 8 | heading, 9 | showDivider, 10 | }: { 11 | body: string; 12 | cta: { link: string; text: string }; 13 | heading: string; 14 | showDivider?: boolean; 15 | }) => ( 16 |
17 | {showDivider && } 18 | 19 |

{heading}

20 |

{body}

21 | 22 |
23 | ); 24 | -------------------------------------------------------------------------------- /examples/demo/components/Button.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Platform, StyleSheet, TouchableOpacity, Text } from 'react-native'; 3 | 4 | const Button = ({ text, color, onPress }) => ( 5 | 10 | {text} 11 | 12 | ); 13 | 14 | const styles = StyleSheet.create({ 15 | text: { 16 | color: 'white', 17 | }, 18 | buttonBox: { 19 | height: 50, 20 | flex: 1, 21 | margin: 5, 22 | alignItems: 'center', 23 | justifyContent: 'center' 24 | } 25 | }); 26 | 27 | export default Button; 28 | -------------------------------------------------------------------------------- /examples/demo/android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext { 5 | buildToolsVersion = "33.0.0" 6 | minSdkVersion = 21 7 | compileSdkVersion = 33 8 | targetSdkVersion = 33 9 | 10 | // We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP. 11 | ndkVersion = "23.1.7779620" 12 | } 13 | repositories { 14 | google() 15 | mavenCentral() 16 | } 17 | dependencies { 18 | classpath("com.android.tools.build:gradle") 19 | classpath("com.facebook.react:react-native-gradle-plugin") 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/demo/components/Page.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {ImageBackground, StyleSheet, SafeAreaView} from 'react-native'; 3 | 4 | const Page = ({children}) => ( 5 | 8 | {children} 9 | 10 | ); 11 | 12 | const styles = StyleSheet.create({ 13 | background: { 14 | flex: 1, 15 | backgroundColor: 'white', 16 | paddingTop: 40, 17 | paddingHorizontal: 10, 18 | paddingBottom: 10, 19 | width: '100%', 20 | height: '100%', 21 | }, 22 | safe: { 23 | flex: 1, 24 | }, 25 | }); 26 | 27 | export default Page; 28 | -------------------------------------------------------------------------------- /packages/react-native-app-auth/react-native-app-auth.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 = package['name'] 7 | s.version = package['version'] 8 | s.summary = package['description'] 9 | s.license = package['license'] 10 | s.authors = package['author'] 11 | s.homepage = package['homepage'] 12 | s.platform = :ios, '10.0' 13 | s.source = { :git => 'https://github.com/FormidableLabs/react-native-app-auth.git', :tag => "v#{s.version}" } 14 | s.source_files = 'ios/**/*.{h,m}' 15 | s.requires_arc = true 16 | s.dependency 'React-Core' 17 | s.dependency 'AppAuth', '>= 1.7.6' 18 | end 19 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator. 4 | 5 | ### Installation 6 | 7 | ``` 8 | $ yarn 9 | ``` 10 | 11 | ### Local Development 12 | 13 | ``` 14 | $ yarn start 15 | ``` 16 | 17 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. 18 | 19 | ### Build 20 | 21 | ``` 22 | $ yarn build 23 | ``` 24 | 25 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 26 | 27 | ### Deployment 28 | 29 | This site is deployed using Vercel, which will automatically detect the site config and deploy 30 | -------------------------------------------------------------------------------- /docs/docs/providers/azure-active-directory-b2c.md: -------------------------------------------------------------------------------- 1 | # Azure Active Directory B2C 2 | 3 | Detailed documentation [here](https://docs.microsoft.com/en-us/azure/active-directory-b2c/openid-connect). 4 | 5 | ```js 6 | const config = { 7 | issuer: 'https://.b2clogin.com/.onmicrosoft.com//v2.0', 8 | clientId: '', 9 | redirectUrl: 'com.myapp://redirect/url/', // the redirectUrl must end with a slash 10 | scopes: ['openid', 'offline_access'], 11 | }; 12 | 13 | // Log in to get an authentication token 14 | const authState = await authorize(config); 15 | 16 | // Refresh token 17 | const refreshedState = await refresh(config, { 18 | refreshToken: authState.refreshToken, 19 | }); 20 | ``` 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Bug_Report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: ⚠️ Bug/Issue report 3 | about: Please provide as much detail as possible to help us with a bug or issue. 4 | --- 5 | 6 | ## Issue 7 | 8 | 9 | 10 | --- 11 | 12 | ## Environment 13 | 14 | * **Your Identity Provider**: `e.g. IdentityServer 4 / Okta / Azure` 15 | * **Platform that you're experiencing the issue on**: `iOS / Android / both` 16 | * **Your `react-native` Version**: `e.g. 0.72.1` 17 | * **Your `react-native-app-auth` Version**: `e.g. 7.0.0` 18 | * **Are you using Expo?** 19 | -------------------------------------------------------------------------------- /docs/docs/usage/errors.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 7 3 | --- 4 | 5 | # Error Messages 6 | 7 | Values are in the `code` field of the rejected Error object. 8 | 9 | - OAuth Authorization [error codes](https://tools.ietf.org/html/rfc6749#section-4.1.2.1) 10 | - OAuth Access Token [error codes](https://tools.ietf.org/html/rfc6749#section-5.2) 11 | - OpendID Connect Registration [error codes](https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationError) 12 | - `service_configuration_fetch_error` - could not fetch the service configuration 13 | - `authentication_failed` - user authentication failed 14 | - `token_refresh_failed` - could not exchange the refresh token for a new JWT 15 | - `registration_failed` - could not register 16 | - `browser_not_found` (Android only) - no suitable browser installed 17 | -------------------------------------------------------------------------------- /examples/demo/android/app/src/release/java/com/example/ReactNativeFlipper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | *

This source code is licensed under the MIT license found in the LICENSE file in the root 5 | * directory of this source tree. 6 | */ 7 | package com.example; 8 | 9 | import android.content.Context; 10 | import com.facebook.react.ReactInstanceManager; 11 | 12 | /** 13 | * Class responsible of loading Flipper inside your React Native application. This is the release 14 | * flavor of it so it's empty as we don't want to load Flipper. 15 | */ 16 | public class ReactNativeFlipper { 17 | public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) { 18 | // Do nothing as we don't want to initialize Flipper on Release. 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/demo/ios/ExampleTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /docs/docs/providers/keycloak.md: -------------------------------------------------------------------------------- 1 | # Keycloak 2 | 3 | Keycloak versions [prior to May 2020](https://github.com/keycloak/keycloak/pull/7106) do not specify a revocation endpoint so revoke functionality doesn't work. If you require the ability to call `revoke` you'll need to ensure you're on a modern version of Keycloak. 4 | 5 | If you use [JHipster](http://www.jhipster.tech/)'s default Keycloak Docker image, everything will work with the following settings. 6 | 7 | ```js 8 | const config = { 9 | issuer: 'http://localhost:9080/auth/realms/jhipster', 10 | clientId: 'web_app', 11 | redirectUrl: ':/callback', 12 | scopes: ['openid', 'profile'], 13 | }; 14 | 15 | // Log in to get an authentication token 16 | const authState = await authorize(config); 17 | 18 | // Refresh token 19 | const refreshedState = await refresh(config, { 20 | refreshToken: authState.refreshToken, 21 | }); 22 | ``` 23 | -------------------------------------------------------------------------------- /docs/src/components/landing/nf-link-button.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | interface ButtonProps { 4 | text: string; 5 | link: string; 6 | screenReaderLabel?: string; 7 | } 8 | 9 | export const NFLinkButton = ({ text, link }: ButtonProps) => { 10 | return ( 11 | 15 | {text} 16 | 17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /docs/docs/providers/github.md: -------------------------------------------------------------------------------- 1 | # GitHub 2 | 3 | Go to [OAuth Apps](https://github.com/settings/developers) to create your app. 4 | 5 | For the Authorization callback URL, choose something like `com.myapp://oauthredirect` and ensure you use `com.myapp` in your `appAuthRedirectScheme` in `android/app/build.gradle`. 6 | 7 | ```js 8 | const config = { 9 | redirectUrl: 'com.my.auth.app://oauthredirect', 10 | clientId: '', 11 | clientSecret: '', 12 | scopes: ['identity'], 13 | additionalHeaders: { Accept: 'application/json' }, 14 | serviceConfiguration: { 15 | authorizationEndpoint: 'https://github.com/login/oauth/authorize', 16 | tokenEndpoint: 'https://github.com/login/oauth/access_token', 17 | revocationEndpoint: 'https://github.com/settings/connections/applications/', 18 | }, 19 | }; 20 | 21 | // Log in to get an authentication token 22 | const authState = await authorize(config); 23 | ``` 24 | -------------------------------------------------------------------------------- /packages/react-native-app-auth/android/src/main/java/com/rnappauth/utils/EndSessionResponseFactory.java: -------------------------------------------------------------------------------- 1 | package com.rnappauth.utils; 2 | 3 | import com.facebook.react.bridge.Arguments; 4 | import com.facebook.react.bridge.WritableMap; 5 | 6 | import net.openid.appauth.EndSessionResponse; 7 | 8 | public final class EndSessionResponseFactory { 9 | /* 10 | * Read raw end session response into a React Native map to be passed down the bridge 11 | */ 12 | public static final WritableMap endSessionResponseToMap(EndSessionResponse response) { 13 | WritableMap map = Arguments.createMap(); 14 | 15 | map.putString("state", response.state); 16 | map.putString("idTokenHint", response.request.idTokenHint); 17 | if (response.request.postLogoutRedirectUri != null) { 18 | map.putString("postLogoutRedirectUri", response.request.postLogoutRedirectUri.toString()); 19 | } 20 | 21 | return map; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /docs/docs/providers/slack.md: -------------------------------------------------------------------------------- 1 | # Slack 2 | 3 | If you don't already have a slack app, create it [here](https://api.slack.com/apps). 4 | 5 | Once you have an app, go "Add features and functionality" => "Permissions". Here you'll need to add two things: 6 | 7 | 1. Redirect URL 8 | Under "Redirect URLs", add one for your app, e.g. `com.myapp://oauth` and save 9 | 10 | 2. Scopes 11 | Under "Scopes", add the scopes you want to request from the user, e.g, "emoji:read" 12 | 13 | ```js 14 | const config = { 15 | clientId: '', // found under App Credentials 16 | clientSecret: '', // found under App Credentials 17 | scopes: ['emoji:read'], // choose any of the scopes set up in step 1 18 | redirectUrl: 'com.myapp://oauth', // set up in step 2 19 | serviceConfiguration: { 20 | authorizationEndpoint: 'https://slack.com/oauth/authorize', 21 | tokenEndpoint: 'https://slack.com/api/oauth.access', 22 | }, 23 | }; 24 | 25 | const authState = await authorize(config); 26 | ``` 27 | -------------------------------------------------------------------------------- /docs/docs/client-secrets.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # Client Secrets 6 | 7 | Some authentication providers, including examples cited below, require you to provide a client secret. The authors of the AppAuth library 8 | 9 | > [strongly recommend](https://github.com/openid/AppAuth-Android#utilizing-client-secrets-dangerous) you avoid using static client secrets in your native applications whenever possible. Client secrets derived via a dynamic client registration are safe to use, but static client secrets can be easily extracted from your apps and allow others to impersonate your app and steal user data. If client secrets must be used by the OAuth2 provider you are integrating with, we strongly recommend performing the code exchange step on your backend, where the client secret can be kept hidden. 10 | 11 | Having said this, in some cases using client secrets is unavoidable. In these cases, a `clientSecret` parameter can be provided to `authorize`/`refresh` calls when performing a token request. 12 | -------------------------------------------------------------------------------- /docs/docs/providers/unsplash.md: -------------------------------------------------------------------------------- 1 | # Unsplash 2 | 3 | If you don't already have a unsplash app, create it [here](https://unsplash.com/oauth/applications). 4 | 5 | Once you have an app, go to your app page. Here you'll need to add two things: 6 | 7 | 1. Redirect URL 8 | Under "Redirect URLs", add one for your app, e.g. `com.myapp://oauth` and save 9 | 10 | 2. Scopes 11 | Under "Scopes", add the scopes you want to request from the user, e.g, "public" 12 | 13 | ```js 14 | const config = { 15 | usePKCE: false, // Important !! 16 | clientId: '', // found under App Credentials 17 | clientSecret: '', // found under App Credentials 18 | scopes: ['public'], // choose any of the scopes set up in step 1 19 | redirectUrl: 'com.myapp://oauth', // set up in step 2 20 | serviceConfiguration: { 21 | authorizationEndpoint: 'https://unsplash.com/oauth/authorize', 22 | tokenEndpoint: 'https://unsplash.com/oauth/token', 23 | }, 24 | }; 25 | 26 | const authState = await authorize(config); 27 | ``` 28 | -------------------------------------------------------------------------------- /packages/react-native-app-auth/android/src/main/java/com/rnappauth/RNAppAuthPackage.java: -------------------------------------------------------------------------------- 1 | package com.rnappauth; 2 | 3 | import java.util.Arrays; 4 | import java.util.Collections; 5 | import java.util.List; 6 | 7 | import com.facebook.react.ReactPackage; 8 | import com.facebook.react.bridge.NativeModule; 9 | import com.facebook.react.bridge.ReactApplicationContext; 10 | import com.facebook.react.uimanager.ViewManager; 11 | import com.facebook.react.bridge.JavaScriptModule; 12 | 13 | public class RNAppAuthPackage implements ReactPackage { 14 | @Override 15 | public List createNativeModules(ReactApplicationContext reactContext) { 16 | return Arrays.asList(new RNAppAuthModule(reactContext)); 17 | } 18 | 19 | // Deprecated from RN 0.47 20 | public List> createJSModules() { 21 | return Collections.emptyList(); 22 | } 23 | 24 | @Override 25 | public List createViewManagers(ReactApplicationContext reactContext) { 26 | return Collections.emptyList(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/react-native-app-auth/android/src/main/java/com/rnappauth/utils/MutableBrowserAllowList.java: -------------------------------------------------------------------------------- 1 | package com.rnappauth.utils; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import net.openid.appauth.browser.BrowserDescriptor; 6 | import net.openid.appauth.browser.BrowserMatcher; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | public class MutableBrowserAllowList implements BrowserMatcher { 12 | 13 | private final List mBrowserMatchers = new ArrayList<>(); 14 | 15 | public void add(BrowserMatcher browserMatcher) { 16 | mBrowserMatchers.add(browserMatcher); 17 | } 18 | 19 | public void remove(BrowserMatcher browserMatcher) { 20 | mBrowserMatchers.remove(browserMatcher); 21 | } 22 | 23 | @Override 24 | public boolean matches(@NonNull BrowserDescriptor descriptor) { 25 | for (BrowserMatcher matcher : mBrowserMatchers) { 26 | if (matcher.matches(descriptor)) { 27 | return true; 28 | } 29 | } 30 | 31 | return false; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | branches: 5 | - main 6 | 7 | jobs: 8 | release: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: write 12 | id-token: write 13 | issues: write 14 | repository-projects: write 15 | deployments: write 16 | packages: write 17 | pull-requests: write 18 | 19 | steps: 20 | - uses: actions/checkout@v4 21 | 22 | - name: Use Node.js 23 | uses: actions/setup-node@v4 24 | with: 25 | node-version: 18 26 | cache: 'yarn' 27 | 28 | - name: Install dependencies 29 | run: yarn install --frozen-lockfile 30 | 31 | - name: Unit Tests 32 | run: yarn test 33 | 34 | - name: PR or Publish 35 | id: changesets 36 | uses: changesets/action@v1 37 | with: 38 | version: yarn changeset version 39 | publish: yarn changeset publish 40 | env: 41 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 42 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 43 | -------------------------------------------------------------------------------- /docs/config-examples/imgur.md: -------------------------------------------------------------------------------- 1 | # Imgur 2 | 3 | Imgur provides an OAuth 2.0 endpoint for logging in with a Imgur user's credentials. You'll need to first [register your Imgur application here](https://api.imgur.com/oauth2/addclient). See [this comment](https://github.com/FormidableLabs/react-native-app-auth/issues/516#issuecomment-2115465572) for detailed setup guide. 4 | 5 | Please note: 6 | 7 | * Imgur does not provide a OIDC discovery endpoint, so `serviceConfiguration` is used instead. 8 | 9 | ```js 10 | // your configuration should look something like this 11 | const config = { 12 | issuer: 'https://api.imgur.com/oauth2/', 13 | clientId: 'abc79a5abcdb30e', // your client id 14 | redirectUrl: encodeURIComponent('com.myapp://oauth/callback'), // must wrap it in encodeURIComponent 15 | scopes: [], 16 | serviceConfiguration: { 17 | authorizationEndpoint: 'https://api.imgur.com/oauth2/authorize', 18 | tokenEndpoint: 'https://api.imgur.com/oauth2/token', 19 | }, 20 | }; 21 | 22 | // Log in to get an authentication token 23 | const authState = await authorize(config); 24 | ``` 25 | -------------------------------------------------------------------------------- /examples/demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rnaa-demo", 3 | "version": "1.0.1", 4 | "private": true, 5 | "scripts": { 6 | "android": "react-native run-android", 7 | "ios": "react-native run-ios", 8 | "lint": "eslint .", 9 | "start": "react-native start", 10 | "test": "jest" 11 | }, 12 | "dependencies": { 13 | "react": "18.2.0", 14 | "react-native": "0.72.4", 15 | "react-native-app-auth": "*" 16 | }, 17 | "devDependencies": { 18 | "@babel/core": "^7.20.0", 19 | "@babel/preset-env": "^7.20.0", 20 | "@babel/runtime": "^7.20.0", 21 | "@react-native/eslint-config": "^0.72.2", 22 | "@react-native/metro-config": "^0.72.11", 23 | "@tsconfig/react-native": "^3.0.0", 24 | "@types/react": "^18.0.24", 25 | "@types/react-test-renderer": "^18.0.0", 26 | "babel-jest": "^29.2.1", 27 | "eslint": "^8.19.0", 28 | "jest": "^29.2.1", 29 | "metro-react-native-babel-preset": "0.76.8", 30 | "prettier": "^2.4.1", 31 | "react-test-renderer": "18.2.0", 32 | "typescript": "4.8.4" 33 | }, 34 | "engines": { 35 | "node": ">=16" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Formidable Labs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /examples/demo/ios/Example/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 | -------------------------------------------------------------------------------- /docs/docs/providers/microsoft.md: -------------------------------------------------------------------------------- 1 | # Microsoft 2 | 3 | 1. Supplying "issuer" fails, because Microsoft returns `issuer` with the literal string `https://login.microsoftonline.com/{tenantid}/v2.0` when `https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration` is queried.. We need to manually specify `serviceConfiguration`. 4 | 5 | 2. `REDIRECT_URL` varies based on platform: 6 | 7 | - iOS: msauth.com.example.app://auth/ 8 | - Android: com.example.app://msauth/``/ 9 | 10 | 3. Microsoft does not have. revocationEndpoint. 11 | 12 | ```js 13 | const config = { 14 | serviceConfiguration: { 15 | authorizationEndpoint: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize', 16 | tokenEndpoint: 'https://login.microsoftonline.com/common/oauth2/v2.0/token', 17 | }, 18 | clientId: '', 19 | redirectUrl: '', 20 | scopes: ['openid', 'profile', 'email', 'offline_access'], 21 | }; 22 | 23 | // Log in to get an authentication token 24 | const authState = await authorize(config); 25 | 26 | // Refresh token 27 | const refreshedState = await refresh(config, { 28 | refreshToken: authState.refreshToken, 29 | }); 30 | ``` 31 | -------------------------------------------------------------------------------- /docs/docs/providers/reddit.md: -------------------------------------------------------------------------------- 1 | # Reddit 2 | 3 | Log in and go to [apps](https://www.reddit.com/prefs/apps) to create your app. 4 | 5 | Choose "installed app" and give it a name, description and about url of your choosing. 6 | 7 | For the redirect uri, choose something like `com.myapp//oauth2redirect/reddit` and make sure that you use `com.myapp` in your `appAuthRedirectScheme` in `android/app/build.gradle`. 8 | 9 | Reddit requires for you to add a [basic auth header](https://github.com/reddit-archive/reddit/wiki/oauth2#retrieving-the-access-token) to the token request. 10 | 11 | ```js 12 | const config = { 13 | redirectUrl: 'com.myapp://oauth2redirect/reddit', 14 | clientId: '', 15 | clientSecret: '', // empty string - needed for iOS 16 | scopes: ['identity'], 17 | serviceConfiguration: { 18 | authorizationEndpoint: 'https://www.reddit.com/api/v1/authorize.compact', 19 | tokenEndpoint: 'https://www.reddit.com/api/v1/access_token', 20 | }, 21 | customHeaders: { 22 | token: { 23 | Authorization: 'Basic ', 24 | }, 25 | }, 26 | }; 27 | 28 | // Log in to get an authentication token 29 | const authState = await authorize(config); 30 | ``` 31 | -------------------------------------------------------------------------------- /docs/docs/providers/fusionauth.md: -------------------------------------------------------------------------------- 1 | # FusionAuth 2 | 3 | FusionAuth does not specify a revocation endpoint so revoke functionality doesn't work. Other than that, full functionality is available. 4 | 5 | - [Install FusionAuth](https://fusionauth.io/docs/v1/tech/installation-guide). 6 | - Create an application in the admin screen. Note the client id. 7 | - Set the redirect_uri for the application to be a value like `fusionauth.demo:/oauthredirect` where `fusionauth.demo` is a scheme you've registered in your application. 8 | 9 | Use the following configuration (replacing the `clientId` with your application id and `fusionAuth.demo` with your scheme): 10 | 11 | ```js 12 | const config = { 13 | issuer: 'http://localhost:9011', 14 | clientId: '253eb7aa-687a-4bf3-b12b-26baa40eecbf', 15 | redirectUrl: 'fusionauth.demo:/callback', 16 | scopes: ['offline_access', 'openid'], 17 | }; 18 | 19 | // Log in to get an authentication token 20 | const authState = await authorize(config); 21 | 22 | // Refresh token 23 | const refreshedState = await refresh(config, { 24 | refreshToken: authState.refreshToken, 25 | }); 26 | ``` 27 | 28 | Check out a full tutorial here: https://fusionauth.io/blog/2020/08/19/securing-react-native-with-oauth 29 | -------------------------------------------------------------------------------- /examples/demo/metro.config.js: -------------------------------------------------------------------------------- 1 | const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config'); 2 | 3 | const path = require('path'); 4 | 5 | const packagePath = path.resolve( 6 | path.join(process.cwd(), '..', '..', 'packages', 'react-native-app-auth'), 7 | ); 8 | 9 | const projectRoot = __dirname; 10 | const monorepoRoot = path.resolve(projectRoot, '../..'); 11 | 12 | const extraNodeModules = { 13 | 'react-native-app-auth': packagePath, 14 | }; 15 | const watchFolders = [monorepoRoot, packagePath]; 16 | 17 | /** 18 | * Metro configuration 19 | * https://facebook.github.io/metro/docs/configuration 20 | * 21 | * @type {import('metro-config').MetroConfig} 22 | */ 23 | const config = { 24 | resolver: { 25 | extraNodeModules: new Proxy(extraNodeModules, { 26 | get: (target, name) => 27 | name in target 28 | ? target[name] 29 | : path.join(process.cwd(), '..', '..', 'node_modules', name), 30 | }), 31 | unstable_enableSymlinks: true, 32 | }, 33 | watchFolders, 34 | }; 35 | 36 | config.resolver.nodeModulesPaths = [ 37 | path.resolve(projectRoot, 'node_modules'), 38 | path.resolve(monorepoRoot, 'node_modules'), 39 | ]; 40 | 41 | module.exports = mergeConfig(getDefaultConfig(__dirname), config); 42 | -------------------------------------------------------------------------------- /docs/docs/providers/coinbase.md: -------------------------------------------------------------------------------- 1 | # Coinbase 2 | 3 | Create a new OAuth application in the [console](https://www.coinbase.com/oauth/applications/new). 4 | 5 | After the application is created, note down the clientId and secret (the secret will only be shown once. If you forgot to take note of it, you'll have to recreate your OAuth application). 6 | 7 | ```js 8 | const config = { 9 | clientId: '', 10 | clientSecret: '', 11 | redirectUrl: 'myapp://redirect', // this can be any valid uri as long as it's the same as what you configured 12 | scopes: ['wallet:accounts:read'], // https://developers.coinbase.com/docs/wallet/permissions 13 | serviceConfiguration: { 14 | authorizationEndpoint: 'https://www.coinbase.com/oauth/authorize', 15 | tokenEndpoint: 'https://api.coinbase.com/oauth/token', 16 | revocationEndpoint: 'https://api.coinbase.com/oauth/revoke', 17 | }, 18 | }; 19 | 20 | // Log in to get an authentication token 21 | const authState = await authorize(config); 22 | 23 | // Refresh token 24 | const refreshedState = await refresh(config, { 25 | refreshToken: authState.refreshToken, 26 | }); 27 | 28 | // Revoke token 29 | await revoke(config, { 30 | tokenToRevoke: refreshedState.refreshToken, 31 | }); 32 | ``` 33 | -------------------------------------------------------------------------------- /docs/docs/providers/asgardeo.md: -------------------------------------------------------------------------------- 1 | # Asgardeo 2 | 3 | To add authentication to your app using Asgardeo, you will first need to [create an application](https://wso2.com/asgardeo/docs/guides/applications/register-mobile-app/) in the Asgardeo console. If you don't have an Asgardeo account, [you can signup for one free](https://asgardeo.io/signup). 4 | 5 | After creating an application, take note of the configuration values listed in the **Quick Start** and **Info** tabs. You will be using those values as follows. 6 | 7 | ```js 8 | export const config = { 9 | issuer: 'https://api.asgardeo.io/t//oauth2/token', 10 | clientId: '', 11 | redirectUrl: '://example', 12 | scopes: ['openid', 'profile'], 13 | }; 14 | 15 | // Log in to get an authentication token 16 | const authState = await authorize(config); 17 | 18 | // Refresh token 19 | const refreshedState = await refresh(config, { 20 | refreshToken: authState.refreshToken, 21 | }); 22 | 23 | // Revoke token 24 | await revoke(config, { 25 | tokenToRevoke: refreshedState.refreshToken, 26 | }); 27 | 28 | // End session 29 | await logout(config, { 30 | idToken: authState.idToken, 31 | postLogoutRedirectUrl: ':/logout', 32 | }); 33 | ``` 34 | -------------------------------------------------------------------------------- /examples/demo/android/app/src/main/java/com/example/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | import com.facebook.react.ReactActivity; 4 | import com.facebook.react.ReactActivityDelegate; 5 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint; 6 | import com.facebook.react.defaults.DefaultReactActivityDelegate; 7 | 8 | public class MainActivity extends 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 15 | protected String getMainComponentName() { 16 | return "Example"; 17 | } 18 | 19 | /** 20 | * Returns the instance of the {@link ReactActivityDelegate}. Here we use a util class {@link 21 | * DefaultReactActivityDelegate} which allows you to easily enable Fabric and Concurrent React 22 | * (aka React 18) with two boolean flags. 23 | */ 24 | @Override 25 | protected ReactActivityDelegate createReactActivityDelegate() { 26 | return new DefaultReactActivityDelegate( 27 | this, 28 | getMainComponentName(), 29 | // If you opted-in for the New Architecture, we enable the Fabric Renderer. 30 | DefaultNewArchitectureEntryPoint.getFabricEnabled()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/demo/.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 | ios/.xcode.env.local 24 | 25 | # Android/IntelliJ 26 | # 27 | build/ 28 | .idea 29 | .gradle 30 | local.properties 31 | *.iml 32 | *.hprof 33 | .cxx/ 34 | *.keystore 35 | !debug.keystore 36 | 37 | # node.js 38 | # 39 | node_modules/ 40 | npm-debug.log 41 | yarn-error.log 42 | 43 | # fastlane 44 | # 45 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 46 | # screenshots whenever they are needed. 47 | # For more information about the recommended setup visit: 48 | # https://docs.fastlane.tools/best-practices/source-control/ 49 | 50 | **/fastlane/report.xml 51 | **/fastlane/Preview.html 52 | **/fastlane/screenshots 53 | **/fastlane/test_output 54 | 55 | # Bundle artifact 56 | *.jsbundle 57 | 58 | # Ruby / CocoaPods 59 | /ios/Pods/ 60 | /vendor/bundle/ 61 | 62 | # Temporary files created by Metro to check the health of the file watcher 63 | .metro-health-check* 64 | 65 | # testing 66 | /coverage 67 | -------------------------------------------------------------------------------- /docs/docs/providers/fitbit.md: -------------------------------------------------------------------------------- 1 | # Fitbit 2 | 3 | Fitbit provides an OAuth 2.0 endpoint for logging in with a Fitbit user's credentials. You'll need to first [register your Fitbit application here](https://dev.fitbit.com/apps/new). 4 | 5 | Please note: 6 | 7 | - Fitbit does not provide a OIDC discovery endpoint, so `serviceConfiguration` is used instead. 8 | - Fitbit OAuth requires a [client secret](/docs/client-secrets). 9 | 10 | ```js 11 | const config = { 12 | clientId: 'your-client-id-generated-by-fitbit', 13 | clientSecret: 'your-client-secret-generated-by-fitbit', 14 | redirectUrl: 'com.whatever.url.you.configured.in.fitbit.oauth://redirect', //note: path is required 15 | scopes: ['activity', 'sleep'], 16 | serviceConfiguration: { 17 | authorizationEndpoint: 'https://www.fitbit.com/oauth2/authorize', 18 | tokenEndpoint: 'https://api.fitbit.com/oauth2/token', 19 | revocationEndpoint: 'https://api.fitbit.com/oauth2/revoke', 20 | }, 21 | }; 22 | 23 | // Log in to get an authentication token 24 | const authState = await authorize(config); 25 | 26 | // Refresh token 27 | const refreshedState = await refresh(config, { 28 | refreshToken: authState.refreshToken, 29 | }); 30 | 31 | // Revoke token 32 | await revoke(config, { 33 | tokenToRevoke: refreshedState.refreshToken, 34 | includeBasicAuth: true, 35 | }); 36 | ``` 37 | -------------------------------------------------------------------------------- /docs/docs/providers/dropbox.md: -------------------------------------------------------------------------------- 1 | # Dropbox 2 | 3 | Dropbox provides an OAuth 2.0 endpoint for logging in with a Dropbox user's credentials. You'll need to first [register your Dropbox application here](https://www.dropbox.com/developers/apps/create). 4 | 5 | Please note: 6 | 7 | - Dropbox does not provide a OIDC discovery endpoint, so `serviceConfiguration` is used instead. 8 | - Dropbox OAuth requires a [client secret](/docs/client-secrets). 9 | - Dropbox access tokens are short lived and will expire after a short period of time. To update your access token a separate call needs to be made to [/oauth2/token](https://www.dropbox.com/developers/documentation/http/documentation#oauth2-token) to obtain a new access token. 10 | 11 | ```js 12 | const config = { 13 | clientId: 'your-client-id-generated-by-dropbox', 14 | clientSecret: 'your-client-secret-generated-by-dropbox', 15 | redirectUrl: 'your.app.bundle.id://oauth', 16 | scopes: [], 17 | serviceConfiguration: { 18 | authorizationEndpoint: 'https://www.dropbox.com/oauth2/authorize', 19 | tokenEndpoint: `https://www.dropbox.com/oauth2/token`, 20 | }, 21 | additionalParameters: { 22 | token_access_type: 'offline', 23 | }, 24 | }; 25 | 26 | // Log in to get an authentication token 27 | const authState = await authorize(config); 28 | const dropboxUID = authState.tokenAdditionalParameters.account_id; 29 | ``` 30 | -------------------------------------------------------------------------------- /docs/docs/providers/uber.md: -------------------------------------------------------------------------------- 1 | # Uber 2 | 3 | Uber provides an OAuth 2.0 endpoint for logging in with a Uber user's credentials. You'll need to first [create an Uber OAuth application here](https://developer.uber.com/docs/riders/guides/authentication/introduction). 4 | 5 | Please note: 6 | 7 | - Uber does not provide a OIDC discovery endpoint, so `serviceConfiguration` is used instead. 8 | - Uber OAuth requires a [client secret](/docs/client-secrets). 9 | 10 | ```js 11 | const config = { 12 | clientId: 'your-client-id-generated-by-uber', 13 | clientSecret: 'your-client-secret-generated-by-uber', 14 | redirectUrl: 'com.whatever.url.you.configured.in.uber.oauth://redirect', //note: path is required 15 | scopes: ['profile', 'delivery'], // whatever scopes you configured in Uber OAuth portal 16 | serviceConfiguration: { 17 | authorizationEndpoint: 'https://login.uber.com/oauth/v2/authorize', 18 | tokenEndpoint: 'https://login.uber.com/oauth/v2/token', 19 | revocationEndpoint: 'https://login.uber.com/oauth/v2/revoke', 20 | }, 21 | }; 22 | 23 | // Log in to get an authentication token 24 | const authState = await authorize(config); 25 | 26 | // Refresh token 27 | const refreshedState = await refresh(config, { 28 | refreshToken: authState.refreshToken, 29 | }); 30 | 31 | // Revoke token 32 | await revoke(config, { 33 | tokenToRevoke: refreshedState.refreshToken, 34 | }); 35 | ``` 36 | -------------------------------------------------------------------------------- /examples/demo/README.md: -------------------------------------------------------------------------------- 1 | # React Native App Auth Example 2 | 3 | ![Demo](demo.gif) 4 | 5 | ## Running the iOS app 6 | 7 | After cloning the repository, run the following: 8 | 9 | ```sh 10 | cd react-native-app-auth/Example 11 | yarn 12 | (cd ios && pod install) 13 | npx react-native run-ios 14 | ``` 15 | 16 | ## Running the Android app 17 | 18 | After cloning the repository, run the following: 19 | 20 | ```sh 21 | cd react-native-app-auth/Example 22 | yarn 23 | npx react-native run-android 24 | ``` 25 | 26 | ### Notes 27 | * You have to have the emulator open before running the last command. If you have difficulty getting the emulator to connect, open the project from Android Studio and run it through there. 28 | * ANDROID: When integrating with a project that utilizes deep linking (e.g. [React Navigation deep linking](https://reactnavigation.org/docs/deep-linking/#set-up-with-bare-react-native-projects)), update the redirectUrl in your config and the `appAuthRedirectScheme` value in build.gradle to use a custom scheme so that it differs from the scheme used in your deep linking intent-filter [as seen here](https://github.com/FormidableLabs/react-native-app-auth/issues/494#issuecomment-797394994). 29 | 30 | Example: 31 | ``` 32 | // build.gradle 33 | android { 34 | defaultConfig { 35 | manifestPlaceholders = [ 36 | appAuthRedirectScheme: 'io.identityserver.demo.auth' 37 | ] 38 | } 39 | } 40 | ``` 41 | -------------------------------------------------------------------------------- /packages/react-native-app-auth/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-app-auth", 3 | "version": "8.0.1", 4 | "description": "React Native bridge for AppAuth for supporting any OAuth 2 provider", 5 | "main": "index.js", 6 | "types": "index.d.ts", 7 | "scripts": { 8 | "test": "jest", 9 | "lint": "eslint ." 10 | }, 11 | "files": [ 12 | "android", 13 | "ios", 14 | "index.d.ts", 15 | "react-native-app-auth.podspec" 16 | ], 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/FormidableLabs/react-native-app-auth" 20 | }, 21 | "bugs": { 22 | "url": "https://github.com/FormidableLabs/react-native-app-auth/issues" 23 | }, 24 | "homepage": "https://github.com/FormidableLabs/react-native-app-auth", 25 | "keywords": [ 26 | "react", 27 | "react-native", 28 | "auth", 29 | "authentication", 30 | "oauth", 31 | "oauth2", 32 | "appauth" 33 | ], 34 | "license": "MIT", 35 | "author": "Nearform Commerce (https://commerce.nearform.com)", 36 | "peerDependencies": { 37 | "react-native": ">=0.63.0" 38 | }, 39 | "devDependencies": { 40 | "jest": "24.9.0", 41 | "react": "16.9.0", 42 | "react-native": "0.63.0" 43 | }, 44 | "dependencies": { 45 | "invariant": "2.2.4", 46 | "react-native-base64": "0.0.2" 47 | }, 48 | "jest": { 49 | "preset": "react-native" 50 | }, 51 | "publishConfig": { 52 | "provenance": true 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /docs/docs/providers/identity-server-3.md: -------------------------------------------------------------------------------- 1 | # Identity Server 3 2 | 3 | This library supports authenticating with Identity Server 3. The only difference from 4 | Identity Server 4 is that it requires a `clientSecret` and there is no way to opt out of it. 5 | 6 | ```js 7 | // You must include a clientSecret 8 | const config = { 9 | issuer: 'your-identityserver-url', 10 | clientId: 'your-client-id', 11 | clientSecret: 'your-client-secret', 12 | redirectUrl: 'com.your.app.name:/oauthredirect', 13 | scopes: ['openid', 'profile', 'offline_access'], 14 | }; 15 | 16 | // Log in to get an authentication token 17 | const authState = await authorize(config); 18 | 19 | // Refresh token 20 | const refreshedState = await refresh(config, { 21 | refreshToken: authState.refreshToken, 22 | }); 23 | 24 | // Revoke token, note that Identity Server expects a client id on revoke 25 | await revoke(config, { 26 | tokenToRevoke: refreshedState.refreshToken, 27 | sendClientId: true, 28 | }); 29 | ``` 30 | 31 |

32 | Example server configuration 33 | 34 | ``` 35 | var client = new Client 36 | { 37 | ClientId = "native.code", 38 | ClientName = "Native Client (Code with PKCE)", 39 | Flow = Flows.AuthorizationCodeWithProofKey, 40 | RedirectUris = { "com.your.app.name:/oauthredirect" }, 41 | ClientSecrets = new List { new Secret("your-client-secret".Sha256()) }, 42 | AllowAccessToAllScopes = true 43 | }; 44 | ``` 45 | 46 |
47 | -------------------------------------------------------------------------------- /docs/src/components/landing/landing-features.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Divider } from './divider'; 3 | import { NFLinkButton } from './nf-link-button'; 4 | 5 | export const LandingFeatures = ({ 6 | cta, 7 | heading, 8 | description, 9 | list, 10 | showDivider, 11 | }: { 12 | cta: { link: string; text: string }; 13 | heading: string; 14 | description?: string; 15 | list: { 16 | imgSrc: string; 17 | alt: string; 18 | title: string; 19 | body?: string; 20 | html?: { __html: string }; 21 | }[]; 22 | showDivider?: boolean; 23 | }) => ( 24 |
25 | {showDivider && } 26 |

{heading}

27 | {description &&

{description}

} 28 |
    29 | {list.map(({ alt, body, imgSrc, title, html }, i) => ( 30 |
  • 31 | {alt} 32 | {title} 33 | 34 | {body} 35 | 36 |
  • 37 | ))} 38 |
39 | 40 |
41 | ); 42 | -------------------------------------------------------------------------------- /docs/docs/usage/authorization.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # Authorization 6 | 7 | This is the main function to use for authentication. Invoking this function will do the whole login 8 | flow and returns the access token, refresh token and access token expiry date when successful, or it 9 | throws an error when not successful. 10 | 11 | ```js 12 | import { authorize } from 'react-native-app-auth'; 13 | 14 | const config = { 15 | issuer: '', 16 | clientId: '', 17 | redirectUrl: '', 18 | scopes: [''], 19 | }; 20 | 21 | const result = await authorize(config); 22 | ``` 23 | 24 | #### `result` 25 | 26 | This is the result from the auth server: 27 | 28 | - **accessToken** - (`string`) the access token 29 | - **accessTokenExpirationDate** - (`string`) the token expiration date 30 | - **authorizeAdditionalParameters** - (`Object`) additional url parameters from the authorizationEndpoint response. 31 | - **tokenAdditionalParameters** - (`Object`) additional url parameters from the tokenEndpoint response. 32 | - **idToken** - (`string`) the id token 33 | - **refreshToken** - (`string`) the refresh token 34 | - **tokenType** - (`string`) the token type, e.g. Bearer 35 | - **scopes** - ([`string`]) the scopes the user has agreed to be granted 36 | - **authorizationCode** - (`string`) the authorization code (only if `skipCodeExchange=true`) 37 | - **codeVerifier** - (`string`) the codeVerifier value used for the PKCE exchange (only if both `skipCodeExchange=true` and `usePKCE=true`) 38 | -------------------------------------------------------------------------------- /docs/docs/providers/microsoft-entra-id.md: -------------------------------------------------------------------------------- 1 | # Microsoft Entra ID 2 | 3 | If you're using Microsoft Identity platform and want to add App Auth to your React Native application, you'll need an Entra application to authorize against. 4 | 5 | Microsoft offers mupltiple different PLatform configurations you could setup for your application. In this example, we are using the `Mobile and desktop applications` platform configuration. 6 | 7 | You can find detailed instructions on registering a new Entra application [here](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app?tabs=certificate). 8 | 9 | NOTES: 10 | 11 | - Microsoft Entra ID does not have a `revocationEndpoint`. 12 | - Application ID can be viewed in your Entra application's dashboard. 13 | - Authorization and Token endpoints can be found under the `Endpoints` link at the top of the page in your Entra application's dashboard. 14 | 15 | ```js 16 | const config = { 17 | serviceConfiguration: { 18 | authorizationEndpoint: 'https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/authorize', 19 | tokenEndpoint: 'https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/token', 20 | }, 21 | clientId: '', 22 | redirectUrl: 'com.my-app://oauth/redirect', // 'com.my-app' should correspond to your app name 23 | scopes: ['openid', 'profile', 'email', 'offline_access'], 24 | }; 25 | 26 | // Log in to get an authentication token 27 | const authState = await authorize(config); 28 | 29 | // Refresh token 30 | const refreshedState = await refresh(config, { 31 | refreshToken: authState.refreshToken, 32 | }); 33 | ``` 34 | -------------------------------------------------------------------------------- /docs/docs/providers/google.md: -------------------------------------------------------------------------------- 1 | # Google 2 | 3 | Full support out of the box. 4 | 5 | ```js 6 | const GOOGLE_OAUTH_APP_GUID = 'YOUR_GOOGLE_OAUTH_APP_GUID'; // it looks something like 12345678912-k50abcdefghijkabcdefghijkabcdefv 7 | const config = { 8 | issuer: 'https://accounts.google.com', 9 | clientId: `${GOOGLE_OAUTH_APP_GUID}.apps.googleusercontent.com`, 10 | redirectUrl: `com.googleusercontent.apps.${GOOGLE_OAUTH_APP_GUID}:/oauth2redirect/google`, 11 | scopes: ['openid', 'profile'], 12 | }; 13 | 14 | // Log in to get an authentication token 15 | const authState = await authorize(config); 16 | 17 | // Refresh token 18 | const refreshedState = await refresh(config, { 19 | refreshToken: authState.refreshToken, 20 | }); 21 | 22 | // Revoke token 23 | await revoke(config, { 24 | tokenToRevoke: refreshedState.refreshToken, 25 | }); 26 | ``` 27 | 28 | ### Note for Android 29 | 30 | To [capture the authorization redirect](https://github.com/openid/AppAuth-android#capturing-the-authorization-redirect), add the following property to the defaultConfig in `android/app/build.gradle`: 31 | 32 | ``` 33 | android { 34 | defaultConfig { 35 | manifestPlaceholders = [ 36 | appAuthRedirectScheme: 'com.googleusercontent.apps.YOUR_GOOGLE_OAUTH_APP_GUID' 37 | // your url will look like com.googleusercontent.apps.12345678912-k50abcdefghijkabcdefghijkabcdefv 38 | ] 39 | } 40 | } 41 | ``` 42 | 43 | - You need to check custom URI scheme under APIs & Services -> Credentials -> OAuth 2.0 Client IDs -> Your Client Name -> Advanced Settings 44 | - It may take 5 minutes to a few hours for settings to take effect. 45 | -------------------------------------------------------------------------------- /docs/docs/providers/identity-server-4.md: -------------------------------------------------------------------------------- 1 | # Identity Server 4 2 | 3 | This library supports authenticating for Identity Server 4 out of the box. Some quirks: 4 | 5 | 1. In order to enable refresh tokens, `offline_access` must be passed in as a scope variable 6 | 2. In order to revoke the access token, we must sent client id in the method body of the request. 7 | This is not part of the OAuth spec. 8 | 9 | ```js 10 | // Note "offline_access" scope is required to get a refresh token 11 | const config = { 12 | issuer: 'https://demo.identityserver.io', 13 | clientId: 'native.code', 14 | redirectUrl: 'io.identityserver.demo:/oauthredirect', 15 | scopes: ['openid', 'profile', 'offline_access'], 16 | }; 17 | 18 | // Log in to get an authentication token 19 | const authState = await authorize(config); 20 | 21 | // Refresh token 22 | const refreshedState = await refresh(config, { 23 | refreshToken: authState.refreshToken, 24 | }); 25 | 26 | // Revoke token, note that Identity Server expects a client id on revoke 27 | await revoke(config, { 28 | tokenToRevoke: refreshedState.refreshToken, 29 | sendClientId: true, 30 | }); 31 | ``` 32 | 33 |
34 | Example server configuration 35 | 36 | ``` 37 | var client = new Client 38 | { 39 | ClientId = "native.code", 40 | ClientName = "Native Client (Code with PKCE)", 41 | RequireClientSecret = false, 42 | RedirectUris = { "io.identityserver.demo:/oauthredirect" }, 43 | AllowedGrantTypes = GrantTypes.Code, 44 | RequirePkce = true, 45 | AllowedScopes = { "openid", "profile" }, 46 | AllowOfflineAccess = true 47 | }; 48 | ``` 49 | 50 |
51 | -------------------------------------------------------------------------------- /docs/docs/providers/strava.md: -------------------------------------------------------------------------------- 1 | # Strava 2 | 3 | Strava is not 100% spec compliant, but it is still possible to get this to work with this library. 4 | 5 | If you don't already have an app, create one [here](https://www.strava.com/settings/apps). 6 | 7 | Now add a redirect uri [here](https://www.strava.com/settings/api). Unlike most providers that ask you to define the entire callback uri, here you need to add the "Authorization Callback Domain", e.g. `oauthredirect` (it can be anything really, in this case the redirect uri in used in the config will be `com.myapp://oauthredirect`). 8 | 9 | Now go to the app page to find the client id and secret and use them in your config like so: 10 | 11 | ```js 12 | config = { 13 | clientId: '', 14 | clientSecret: '', 15 | redirectUrl: 'myapp://oauthredirect', 16 | serviceConfiguration: { 17 | authorizationEndpoint: 'https://www.strava.com/oauth/mobile/authorize', 18 | tokenEndpoint: 19 | 'https://www.strava.com/oauth/token?client_id=&client_secret=', 20 | }, 21 | scopes: ['activity:read_all'], 22 | }; 23 | 24 | const authState = await authorize(config); 25 | ``` 26 | 27 | Note, they require the client secret and id being passed in the token endpoint. This is not in the spec and thus is not supported. But we can get around it by adding them to the tokenEndpoint as url params. 28 | 29 | ## Revocation 30 | 31 | The built in token revocation also won't work for Strava, because they use the param `access_token` instead of `token`, but you can easily implement it yourself using `fetch` 32 | 33 | ```js 34 | const res = await fetch(`https://www.strava.com/oauth/deauthorize?access_token=${accessToken}`, { 35 | method: 'POST', 36 | }); 37 | ``` 38 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rnaa-docs", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build --out-dir build/open-source/react-native-app-auth", 9 | "swizzle": "docusaurus swizzle", 10 | "deploy": "docusaurus deploy", 11 | "clear": "docusaurus clear", 12 | "serve": "docusaurus serve", 13 | "write-translations": "docusaurus write-translations", 14 | "write-heading-ids": "docusaurus write-heading-ids", 15 | "lint": "eslint --ext .js,.ts,.tsx src", 16 | "typecheck": "tsc" 17 | }, 18 | "dependencies": { 19 | "@docusaurus/core": "3.3.2", 20 | "@docusaurus/plugin-google-gtag": "^3.5.2", 21 | "@docusaurus/preset-classic": "3.3.2", 22 | "@easyops-cn/docusaurus-search-local": "^0.41.0", 23 | "@mdx-js/react": "^3.0.0", 24 | "clsx": "^2.0.0", 25 | "formidable-oss-badges": "^1.4.1", 26 | "prism-react-renderer": "^2.3.0", 27 | "react": "^18.0.0", 28 | "react-dom": "^18.0.0", 29 | "react-native-app-auth": "^7.2.0" 30 | }, 31 | "devDependencies": { 32 | "@docusaurus/module-type-aliases": "3.3.2", 33 | "@docusaurus/tsconfig": "3.3.2", 34 | "@docusaurus/types": "3.3.2", 35 | "autoprefixer": "^10.4.19", 36 | "postcss": "^8.4.38", 37 | "tailwindcss": "^3.4.3", 38 | "typescript": "~5.2.2" 39 | }, 40 | "browserslist": { 41 | "production": [ 42 | ">0.5%", 43 | "not dead", 44 | "not op_mini all" 45 | ], 46 | "development": [ 47 | "last 3 chrome version", 48 | "last 3 firefox version", 49 | "last 5 safari version" 50 | ] 51 | }, 52 | "engines": { 53 | "node": ">=18.0" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /docs/docs/providers/aws-cognito.md: -------------------------------------------------------------------------------- 1 | # AWS Cognito 2 | 3 | First, set up a your user pool in [the AWS console](https://eu-west-1.console.aws.amazon.com/cognito). In the details of your new user pool, go down to `App clients` to create a new client. Make sure you create a client **without** a client secret (it's redundant on mobile). You should get an alphanumeric string which is your ``. 4 | 5 | Now you need to set up your domain name. This will be on the left menu in your pool details page, under App Integration -> Domain Name. What this is depends on your preference. E.g. for AppAuth demo, mine is `https://app-auth-test.auth.eu-west-1.amazoncognito.com` as I chose `app-auth-test` as the domain and `eu-west-1` as the region. 6 | 7 | Finally, you need to configure your app client. Go to App Integration -> App Client Settings. 8 | 9 | 1. Enable your newly created user pool under Enabled Identity Providers. 10 | 2. Add the callback url (must be same as in your config, e.g. `com.myclientapp://myclient/redirect`) 11 | 3. Enable the Authorization code grant 12 | 4. Enable openid scope 13 | 14 | ```js 15 | const config = { 16 | clientId: '', 17 | redirectUrl: 'com.myclientapp://myclient/redirect', 18 | serviceConfiguration: { 19 | authorizationEndpoint: '/oauth2/authorize', 20 | tokenEndpoint: '/oauth2/token', 21 | revocationEndpoint: '/oauth2/revoke', 22 | }, 23 | }; 24 | 25 | // Log in to get an authentication token 26 | const authState = await authorize(config); 27 | 28 | // Refresh token 29 | const refreshedState = await refresh(config, { 30 | refreshToken: authState.refreshToken, 31 | }); 32 | 33 | // Revoke token 34 | await revoke(config, { 35 | tokenToRevoke: refreshedState.refreshToken, 36 | }); 37 | ``` 38 | -------------------------------------------------------------------------------- /docs/src/theme/prism-include-languages.ts: -------------------------------------------------------------------------------- 1 | import siteConfig from '@generated/docusaurus.config'; 2 | import * as PrismNamespace from 'prismjs'; 3 | import { Optional } from 'utility-types'; 4 | import { diffHighlight } from './prism-diff-highlight'; 5 | import './prism-diff-highlight.css'; 6 | 7 | const DIFF_LANGUAGE_REGEX = /^diff-([\w-]+)/i; 8 | 9 | export default function prismIncludeLanguages(PrismObject: typeof PrismNamespace): void { 10 | const { 11 | themeConfig: { prism }, 12 | } = siteConfig; 13 | const { additionalLanguages } = prism as { additionalLanguages: string[] }; 14 | 15 | // Prism components work on the Prism instance on the window, while prism- 16 | // react-renderer uses its own Prism instance. We temporarily mount the 17 | // instance onto window, import components to enhance it, then remove it to 18 | // avoid polluting global namespace. 19 | // You can mutate PrismObject: registering plugins, deleting languages... As 20 | // long as you don't re-assign it 21 | globalThis.Prism = PrismObject; 22 | 23 | additionalLanguages.forEach(lang => { 24 | const langMatch = DIFF_LANGUAGE_REGEX.exec(lang); 25 | if (langMatch) { 26 | // eslint-disable-next-line global-require 27 | if (!PrismObject.languages.diff) { 28 | console.error( 29 | 'prism-include-languages:', 30 | "You need to import 'diff' language first to use 'diff-xxxx' languages" 31 | ); 32 | } 33 | PrismObject.languages[lang] = PrismObject.languages.diff; 34 | } else { 35 | // eslint-disable-next-line global-require, import/no-dynamic-require 36 | require(`prismjs/components/prism-${lang}`); 37 | } 38 | }); 39 | 40 | diffHighlight(PrismObject); 41 | 42 | delete (globalThis as Optional).Prism; 43 | } 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rnaa", 3 | "version": "1.0.0", 4 | "private": true, 5 | "description": "React Native bridge for AppAuth for supporting any OAuth 2 provider", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/FormidableLabs/react-native-app-auth" 9 | }, 10 | "bugs": { 11 | "url": "https://github.com/FormidableLabs/react-native-app-auth/issues" 12 | }, 13 | "homepage": "https://github.com/FormidableLabs/react-native-app-auth", 14 | "keywords": [ 15 | "react", 16 | "react-native", 17 | "auth", 18 | "authentication", 19 | "oauth", 20 | "oauth2", 21 | "appauth" 22 | ], 23 | "license": "MIT", 24 | "scripts": { 25 | "changeset": "changeset", 26 | "lint": "yarn workspace react-native-app-auth lint", 27 | "test": "yarn workspace react-native-app-auth test", 28 | "start:demo:android": "yarn workspace rnaa-demo android", 29 | "start:demo:ios": "yarn workspace rnaa-demo ios" 30 | }, 31 | "devDependencies": { 32 | "@changesets/cli": "^2.26.1", 33 | "@svitejs/changesets-changelog-github-compact": "^0.1.1", 34 | "babel-eslint": "10.0.3", 35 | "eslint": "6.8.0", 36 | "eslint-config-formidable": "4.0.0", 37 | "eslint-config-prettier": "6.9.0", 38 | "eslint-plugin-filenames": "1.3.2", 39 | "eslint-plugin-import": "2.19.1", 40 | "eslint-plugin-jest": "23.3.0", 41 | "eslint-plugin-prettier": "3.1.2", 42 | "eslint-plugin-promise": "4.2.1", 43 | "prettier": "1.19.1" 44 | }, 45 | "workspaces": { 46 | "packages": [ 47 | "packages/*", 48 | "examples/*" 49 | ], 50 | "nohoist": [ 51 | "**/react-native", 52 | "**/react-native/**", 53 | "**/@react-native", 54 | "**/@react-native/**", 55 | "**/react-native-app-auth/**" 56 | ] 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /docs/docs/providers/spotify.md: -------------------------------------------------------------------------------- 1 | # Spotify 2 | 3 | If you don't already have a spotify app, create it [here](https://developer.spotify.com/dashboard/applications). 4 | 5 | Open your app, go to settings and add a redirect uri, e.g. `com.myapp:/oauth`. 6 | 7 | Note: iOS redirect on Spotify only works with one `/`.s 8 | 9 | ```js 10 | const config = { 11 | clientId: '', // available on the app page 12 | clientSecret: '', // click "show client secret" to see this 13 | redirectUrl: 'com.myapp:/oauth', // the redirect you defined after creating the app 14 | scopes: ['user-read-email', 'playlist-modify-public', 'user-read-private'], // the scopes you need to access 15 | serviceConfiguration: { 16 | authorizationEndpoint: 'https://accounts.spotify.com/authorize', 17 | tokenEndpoint: 'https://accounts.spotify.com/api/token', 18 | }, 19 | }; 20 | 21 | const authState = await authorize(config); 22 | ``` 23 | 24 | ## Managing Client Secrets 25 | 26 | In order to avoid storing the `clientSecret` in the client, Spotify has published a token exchange package that can be used to move this step to the backend: 27 | https://github.com/bih/spotify-token-swap-service 28 | 29 | The tokenEndpoint should then point to whereever you are hosting this server, and be sure to remove the secret from your app: 30 | 31 | ```js 32 | const config = { 33 | clientId: '', // available on the app page 34 | redirectUrl: 'com.myapp:/oauth', // the redirect you defined after creating the app 35 | scopes: ['user-read-email', 'playlist-modify-public', 'user-read-private'], // the scopes you need to access 36 | serviceConfiguration: { 37 | authorizationEndpoint: 'https://accounts.spotify.com/authorize', 38 | tokenEndpoint: 'https://my-token-service/api/token', 39 | }, 40 | }; 41 | 42 | const authState = await authorize(config); 43 | ``` 44 | -------------------------------------------------------------------------------- /packages/react-native-app-auth/android/src/main/java/com/rnappauth/utils/RegistrationResponseFactory.java: -------------------------------------------------------------------------------- 1 | package com.rnappauth.utils; 2 | 3 | import com.facebook.react.bridge.Arguments; 4 | import com.facebook.react.bridge.WritableMap; 5 | 6 | import net.openid.appauth.RegistrationResponse; 7 | 8 | public final class RegistrationResponseFactory { 9 | /* 10 | * Read raw registration response into a React Native map to be passed down the bridge 11 | */ 12 | public static final WritableMap registrationResponseToMap(RegistrationResponse response) { 13 | WritableMap map = Arguments.createMap(); 14 | 15 | map.putString("clientId", response.clientId); 16 | map.putMap("additionalParameters", MapUtil.createAdditionalParametersMap(response.additionalParameters)); 17 | 18 | if (response.clientIdIssuedAt != null) { 19 | map.putString("clientIdIssuedAt", DateUtil.formatTimestamp(response.clientIdIssuedAt)); 20 | } 21 | 22 | if (response.clientSecret != null) { 23 | map.putString("clientSecret", response.clientSecret); 24 | } 25 | 26 | if (response.clientSecretExpiresAt != null) { 27 | map.putString("clientSecretExpiresAt", DateUtil.formatTimestamp(response.clientSecretExpiresAt)); 28 | } 29 | 30 | if (response.registrationAccessToken != null) { 31 | map.putString("registrationAccessToken", response.registrationAccessToken); 32 | } 33 | 34 | if (response.registrationClientUri != null) { 35 | map.putString("registrationClientUri", response.registrationClientUri.toString()); 36 | } 37 | 38 | if (response.tokenEndpointAuthMethod != null) { 39 | map.putString("tokenEndpointAuthMethod", response.tokenEndpointAuthMethod); 40 | } 41 | 42 | return map; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /docs/src/components/landing/landing-featured-projects.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { FeaturedBadge } from 'formidable-oss-badges'; 3 | import { NFLinkButton } from './nf-link-button'; 4 | import { Divider } from './divider'; 5 | 6 | type featuredProject = 7 | | 'renature' 8 | | 'spectacle' 9 | | 'urql' 10 | | 'victory' 11 | | 'nuka' 12 | | 'owl' 13 | | 'groqd' 14 | | 'envy' 15 | | 'figlog'; 16 | 17 | export const LandingFeaturedProjects = ({ 18 | heading, 19 | projects, 20 | showDivider, 21 | }: { 22 | heading: string; 23 | projects: { 24 | name: featuredProject; 25 | link: string; 26 | description: string; 27 | title?: string; 28 | }[]; 29 | showDivider?: boolean; 30 | }) => ( 31 |
32 | {showDivider && } 33 |

{heading}

34 |
35 | {projects.map(({ name, link, description, title }) => ( 36 | 41 | 42 | 43 | {title || name} 44 | {description} 45 | 46 | 47 | ))} 48 |
49 | 50 |
51 | 52 |
53 |
54 | ); 55 | -------------------------------------------------------------------------------- /docs/docs/providers/okta.md: -------------------------------------------------------------------------------- 1 | # Okta 2 | 3 | Full support out of the box. 4 | 5 | > If you're using Okta and want to add App Auth to your React Native application, you'll need an application to authorize against. If you don't have an Okta Developer account, [you can signup for free](https://developer.okta.com/signup/). 6 | > 7 | > Log in to your Okta Developer account and navigate to **Applications** > **Applications** > **Create App Integration**. Click **OIDC - OpenID Connect**, then **Native Application**, then click the **Next** button. Give the app integration a name you’ll remember (e.g., `React Native`), select `Refresh Token` as a grant type, in addition to the default `Authorization Code`. Copy the **Sign-in redirect URI** (e.g., `com.oktapreview.dev-158606:/callback`) and save it somewhere. You'll need this value when configuring your app. 8 | > 9 | > Click **Save** and you'll see a client ID on the next screen. Copy the redirect URI and clientId values into your App Auth config. 10 | > 11 | > To end the session, `postLogoutRedirectUrl` has to be one of the **Sign-out redirect URIs** defined in the **General Settings** > **LOGIN** section of the application page previously created. 12 | 13 | ```js 14 | const config = { 15 | issuer: 'https://{yourOktaDomain}.com/oauth2/default', 16 | clientId: '{clientId}', 17 | redirectUrl: 'com.{yourReversedOktaDomain}:/callback', 18 | scopes: ['openid', 'profile'], 19 | }; 20 | 21 | // Log in to get an authentication token 22 | const authState = await authorize(config); 23 | 24 | // Refresh token 25 | const refreshedState = await refresh(config, { 26 | refreshToken: authState.refreshToken, 27 | }); 28 | 29 | // Revoke token 30 | await revoke(config, { 31 | tokenToRevoke: refreshedState.refreshToken, 32 | }); 33 | 34 | // End session 35 | await logout(config, { 36 | idToken: authState.idToken, 37 | postLogoutRedirectUrl: 'com.{yourReversedOktaDomain}:/logout', 38 | }); 39 | ``` 40 | -------------------------------------------------------------------------------- /examples/demo/ios/Example/AppDelegate.mm: -------------------------------------------------------------------------------- 1 | #import "AppDelegate.h" 2 | 3 | #import 4 | 5 | @implementation AppDelegate 6 | 7 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 8 | { 9 | self.moduleName = @"Example"; 10 | // You can add your custom initial props in the dictionary below. 11 | // They will be passed down to the ViewController used by React Native. 12 | self.initialProps = @{}; 13 | 14 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 15 | } 16 | 17 | - (BOOL) application: (UIApplication *)application 18 | openURL: (NSURL *)url 19 | options: (NSDictionary *) options 20 | { 21 | if ([self.authorizationFlowManagerDelegate resumeExternalUserAgentFlowWithURL:url]) { 22 | return YES; 23 | } 24 | return [RCTLinkingManager application:application openURL:url options:options]; 25 | } 26 | 27 | - (BOOL) application: (UIApplication *) application 28 | continueUserActivity: (nonnull NSUserActivity *)userActivity 29 | restorationHandler: (nonnull void (^)(NSArray> * _Nullable))restorationHandler 30 | { 31 | if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) { 32 | if (self.authorizationFlowManagerDelegate) { 33 | BOOL resumableAuth = [self.authorizationFlowManagerDelegate resumeExternalUserAgentFlowWithURL:userActivity.webpageURL]; 34 | if (resumableAuth) { 35 | return YES; 36 | } 37 | } 38 | } 39 | return [RCTLinkingManager application:application continueUserActivity:userActivity restorationHandler:restorationHandler]; 40 | } 41 | 42 | - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge 43 | { 44 | #if DEBUG 45 | return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"]; 46 | #else 47 | return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; 48 | #endif 49 | } 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /docs/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | 3 | const NearFormColors = { 4 | White: 'hsla(0, 0%, 100%, 1)', 5 | Black: 'hsla(0, 0%, 0%, 1)', 6 | Green: 'hsla(163, 100%, 45%, 1)', 7 | Purple: 'hsla(260, 100%, 70%, 1)', 8 | LightPurple: 'hsla(262, 100%, 90%, 1)', 9 | Blue: 'hsla(218, 100%, 64%, 1)', 10 | LightBlue: 'hsla(217, 100%, 92%, 1)', 11 | Grey: 'hsla(0, 0%, 85%, 1)', 12 | LightGrey: '#F4F8FA', 13 | DeepGrey: 'hsla(240, 8%, 29%, 1)', 14 | Navy: 'hsla(205, 78%, 21%, 1)', 15 | LightNavy: 'hsla(222, 25%, 43%, 1)', 16 | DeepNavy: 'hsla(225, 100%, 11%, 1)', 17 | }; 18 | 19 | module.exports = { 20 | corePlugins: { 21 | preflight: false, // disable Tailwind's reset 22 | }, 23 | content: ['./src/**/*.{js,jsx,ts,tsx}', '../docs/**/*.mdx'], 24 | darkMode: ['class', '[data-theme="dark"]'], 25 | theme: { 26 | extend: { 27 | colors: { 28 | transparent: 'transparent', 29 | white: NearFormColors.White, 30 | black: NearFormColors.Black, 31 | grayscale: { 32 | 100: NearFormColors.White, 33 | 200: NearFormColors.LightGrey, 34 | 300: NearFormColors.Grey, 35 | 400: NearFormColors.DeepGrey, 36 | 500: NearFormColors.Black, 37 | 800: '#888888', 38 | }, 39 | 'theme-1': NearFormColors.Green, 40 | 'theme-2': NearFormColors.DeepNavy, 41 | 'theme-3': NearFormColors.DeepNavy, 42 | 'theme-4': NearFormColors.White, 43 | 'header-bg': NearFormColors.White, 44 | 'header-fg': NearFormColors.DeepNavy, 45 | 'button-bg': NearFormColors.Green, 46 | 'button-fg': NearFormColors.DeepNavy, 47 | 'button-bg-hover': NearFormColors.White, 48 | 'button-fg-hover': NearFormColors.DeepNavy, 49 | 'button-border': NearFormColors.Green, 50 | error: '#FF0000', 51 | }, 52 | width: { 53 | prose: '90ch', 54 | }, 55 | fontFamily: { 56 | sans: ['Inter, Helvetica, Arial, sans-serif'], 57 | }, 58 | }, 59 | }, 60 | }; 61 | -------------------------------------------------------------------------------- /examples/demo/android/app/src/main/res/drawable/rn_edit_text_material.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 21 | 22 | 23 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /examples/demo/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 | # Automatically convert third-party libraries to use AndroidX 25 | android.enableJetifier=true 26 | 27 | # Version of flipper SDK to use with React Native 28 | FLIPPER_VERSION=0.182.0 29 | 30 | # Use this property to specify which architecture you want to build. 31 | # You can also override it from the CLI using 32 | # ./gradlew -PreactNativeArchitectures=x86_64 33 | reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 34 | 35 | # Use this property to enable support to the new architecture. 36 | # This will allow you to use TurboModules and the Fabric render in 37 | # your application. You should enable this flag either if you want 38 | # to write custom TurboModules/Fabric components OR use libraries that 39 | # are providing them. 40 | newArchEnabled=false 41 | 42 | # Use this property to enable or disable the Hermes JS engine. 43 | # If set to false, you will be using JSC instead. 44 | hermesEnabled=true 45 | -------------------------------------------------------------------------------- /examples/demo/android/app/src/main/java/com/example/MainApplication.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | import android.app.Application; 4 | import com.facebook.react.PackageList; 5 | import com.facebook.react.ReactApplication; 6 | import com.facebook.react.ReactNativeHost; 7 | import com.facebook.react.ReactPackage; 8 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint; 9 | import com.facebook.react.defaults.DefaultReactNativeHost; 10 | import com.facebook.soloader.SoLoader; 11 | import java.util.List; 12 | 13 | public class MainApplication extends Application implements ReactApplication { 14 | 15 | private final ReactNativeHost mReactNativeHost = 16 | new DefaultReactNativeHost(this) { 17 | @Override 18 | public boolean getUseDeveloperSupport() { 19 | return BuildConfig.DEBUG; 20 | } 21 | 22 | @Override 23 | protected List getPackages() { 24 | @SuppressWarnings("UnnecessaryLocalVariable") 25 | List packages = new PackageList(this).getPackages(); 26 | // Packages that cannot be autolinked yet can be added manually here, for example: 27 | // packages.add(new MyReactNativePackage()); 28 | return packages; 29 | } 30 | 31 | @Override 32 | protected String getJSMainModuleName() { 33 | return "index"; 34 | } 35 | 36 | @Override 37 | protected boolean isNewArchEnabled() { 38 | return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED; 39 | } 40 | 41 | @Override 42 | protected Boolean isHermesEnabled() { 43 | return BuildConfig.IS_HERMES_ENABLED; 44 | } 45 | }; 46 | 47 | @Override 48 | public ReactNativeHost getReactNativeHost() { 49 | return mReactNativeHost; 50 | } 51 | 52 | @Override 53 | public void onCreate() { 54 | super.onCreate(); 55 | SoLoader.init(this, /* native exopackage */ false); 56 | if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { 57 | // If you opted-in for the New Architecture, we load the native entry point for this app. 58 | DefaultNewArchitectureEntryPoint.load(); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /packages/react-native-app-auth/android/build.gradle: -------------------------------------------------------------------------------- 1 | def safeExtGet(prop, fallback) { 2 | rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback 3 | } 4 | 5 | buildscript { 6 | // The Android Gradle plugin is only required when opening the android folder stand-alone. 7 | // This avoids unnecessary downloads and potential conflicts when the library is included as a 8 | // module dependency in an application project. 9 | if (project == rootProject) { 10 | repositories { 11 | mavenCentral() 12 | google() 13 | } 14 | dependencies { 15 | classpath 'com.android.tools.build:gradle:3.5.3' 16 | } 17 | } 18 | } 19 | 20 | apply plugin: 'com.android.library' 21 | 22 | android { 23 | def agpVersion = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION 24 | if (agpVersion.tokenize('.')[0].toInteger() >= 7) { 25 | namespace "com.rnappauth" 26 | } 27 | 28 | compileSdkVersion safeExtGet('compileSdkVersion', 28) 29 | defaultConfig { 30 | minSdkVersion safeExtGet('minSdkVersion', 16) 31 | targetSdkVersion safeExtGet('targetSdkVersion', 26) 32 | versionCode 1 33 | versionName "1.0" 34 | manifestPlaceholders = [ 35 | 'appAuthRedirectScheme': 'please.override.me' 36 | ] 37 | } 38 | lintOptions { 39 | abortOnError false 40 | } 41 | compileOptions { 42 | sourceCompatibility JavaVersion.VERSION_1_8 43 | targetCompatibility JavaVersion.VERSION_1_8 44 | } 45 | } 46 | 47 | repositories { 48 | mavenLocal() 49 | mavenCentral() 50 | maven { 51 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 52 | url "$rootDir/../node_modules/react-native/android" 53 | } 54 | maven { 55 | // Android JSC is installed from npm 56 | url "$rootDir/../node_modules/jsc-android/dist" 57 | } 58 | google() 59 | } 60 | 61 | dependencies { 62 | //noinspection GradleDynamicVersion 63 | implementation 'com.facebook.react:react-native:+' // From node_modules 64 | implementation 'net.openid:appauth:0.11.1' 65 | implementation 'androidx.browser:browser:1.4.0' 66 | } 67 | -------------------------------------------------------------------------------- /examples/demo/ios/ExampleTests/ExampleTests.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | #import 5 | #import 6 | 7 | #define TIMEOUT_SECONDS 600 8 | #define TEXT_TO_LOOK_FOR @"Welcome to React" 9 | 10 | @interface ExampleTests : XCTestCase 11 | 12 | @end 13 | 14 | @implementation ExampleTests 15 | 16 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL (^)(UIView *view))test 17 | { 18 | if (test(view)) { 19 | return YES; 20 | } 21 | for (UIView *subview in [view subviews]) { 22 | if ([self findSubviewInView:subview matching:test]) { 23 | return YES; 24 | } 25 | } 26 | return NO; 27 | } 28 | 29 | - (void)testRendersWelcomeScreen 30 | { 31 | UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController]; 32 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; 33 | BOOL foundElement = NO; 34 | 35 | __block NSString *redboxError = nil; 36 | #ifdef DEBUG 37 | RCTSetLogFunction( 38 | ^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { 39 | if (level >= RCTLogLevelError) { 40 | redboxError = message; 41 | } 42 | }); 43 | #endif 44 | 45 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { 46 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 47 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 48 | 49 | foundElement = [self findSubviewInView:vc.view 50 | matching:^BOOL(UIView *view) { 51 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { 52 | return YES; 53 | } 54 | return NO; 55 | }]; 56 | } 57 | 58 | #ifdef DEBUG 59 | RCTSetLogFunction(RCTDefaultLogFunction); 60 | #endif 61 | 62 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); 63 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); 64 | } 65 | 66 | @end 67 | -------------------------------------------------------------------------------- /examples/demo/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Resolve react_native_pods.rb with node to allow for hoisting 2 | require Pod::Executable.execute_command('node', ['-p', 3 | 'require.resolve( 4 | "react-native/scripts/react_native_pods.rb", 5 | {paths: [process.argv[1]]}, 6 | )', __dir__]).strip 7 | 8 | platform :ios, min_ios_version_supported 9 | prepare_react_native_project! 10 | 11 | # If you are using a `react-native-flipper` your iOS build will fail when `NO_FLIPPER=1` is set. 12 | # because `react-native-flipper` depends on (FlipperKit,...) that will be excluded 13 | # 14 | # To fix this you can also exclude `react-native-flipper` using a `react-native.config.js` 15 | # ```js 16 | # module.exports = { 17 | # dependencies: { 18 | # ...(process.env.NO_FLIPPER ? { 'react-native-flipper': { platforms: { ios: null } } } : {}), 19 | # ``` 20 | flipper_config = FlipperConfiguration.disabled 21 | 22 | linkage = ENV['USE_FRAMEWORKS'] 23 | if linkage != nil 24 | Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green 25 | use_frameworks! :linkage => linkage.to_sym 26 | end 27 | 28 | target 'Example' do 29 | config = use_native_modules! 30 | 31 | # Flags change depending on the env values. 32 | flags = get_default_flags() 33 | 34 | use_react_native!( 35 | :path => config[:reactNativePath], 36 | # Hermes is now enabled by default. Disable by setting this flag to false. 37 | :hermes_enabled => flags[:hermes_enabled], 38 | :fabric_enabled => flags[:fabric_enabled], 39 | # Enables Flipper. 40 | # 41 | # Note that if you have use_frameworks! enabled, Flipper will not work and 42 | # you should disable the next line. 43 | :flipper_configuration => flipper_config, 44 | # An absolute path to your application root. 45 | :app_path => "#{Pod::Config.instance.installation_root}/.." 46 | ) 47 | 48 | target 'ExampleTests' do 49 | inherit! :complete 50 | # Pods for testing 51 | end 52 | 53 | post_install do |installer| 54 | # https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202 55 | react_native_post_install( 56 | installer, 57 | config[:reactNativePath], 58 | :mac_catalyst_enabled => false 59 | ) 60 | __apply_Xcode_12_5_M1_post_install_workaround(installer) 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /examples/demo/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /examples/demo/ios/Example/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | Example 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleSignature 22 | ???? 23 | CFBundleURLTypes 24 | 25 | 26 | CFBundleURLName 27 | com.your.app.identifier 28 | CFBundleURLSchemes 29 | 30 | io.identityserver.demo 31 | 32 | 33 | 34 | CFBundleTypeRole 35 | Editor 36 | CFBundleURLName 37 | com.your.app.identifier 38 | CFBundleURLSchemes 39 | 40 | rnaa-demo 41 | 42 | 43 | 44 | CFBundleVersion 45 | $(CURRENT_PROJECT_VERSION) 46 | LSRequiresIPhoneOS 47 | 48 | NSAppTransportSecurity 49 | 50 | NSExceptionDomains 51 | 52 | localhost 53 | 54 | NSExceptionAllowsInsecureHTTPLoads 55 | 56 | 57 | 58 | 59 | NSLocationWhenInUseUsageDescription 60 | 61 | UILaunchStoryboardName 62 | LaunchScreen 63 | UIRequiredDeviceCapabilities 64 | 65 | armv7 66 | 67 | UISupportedInterfaceOrientations 68 | 69 | UIInterfaceOrientationPortrait 70 | UIInterfaceOrientationLandscapeLeft 71 | UIInterfaceOrientationLandscapeRight 72 | 73 | UIViewControllerBasedStatusBarAppearance 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /docs/docs/token-storage.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # Token Storage 6 | 7 | Once the user has successfully authenticated, you'll have a JWT and possibly a refresh token that should be stored securely. 8 | 9 | ❗️ **Do not use Async Storage for storing sensitive information** 10 | 11 | Async Storage is the simplest method of persisting data across application launches in React Native. However, it is an _unencrypted_ key-value store and should therefore not be used for token storage. 12 | 13 | ✅ **DO use Secure Storage** 14 | 15 | React Native does not come bundled with any way of storing sensitive data, so it is necessary to rely on the underlying platform-specific solutions. 16 | 17 | ### iOS - Keychain Services 18 | 19 | Keychain Services allows you to securely store small chunks of sensitive info for the user. This is an ideal place to store certificates, tokens, passwords, and any other sensitive information that doesn’t belong in Async Storage. 20 | 21 | ### Android - Secure Shared Preferences 22 | 23 | Shared Preferences is the Android equivalent for a persistent key-value data store. Data in Shared Preferences is not encrypted by default. Encrypted Shared Preferences wraps the Shared Preferences class for Android, and automatically encrypts keys and values. 24 | 25 | In order to use iOS's Keychain services or Android's Secure Shared Preferences, you either can write a JS < - > native interface yourself or use a library which wraps them for you. Some even provide a unified API. 26 | 27 | ## Related OSS libraries 28 | 29 | - [react-native-keychain](https://github.com/oblador/react-native-keychain) - we've had good experiences using this on projects 30 | - [react-native-sensitive-info](https://github.com/mCodex/react-native-sensitive-info) - secure for iOS, but uses Android Shared Preferences for Android (which is not secure). There is however a fork that uses [Android Keystore](https://github.com/mCodex/react-native-sensitive-info/tree/keystore) which is secure 31 | - [redux-persist-sensitive-storage](https://github.com/CodingZeal/redux-persist-sensitive-storage) - wraps `react-native-sensitive-info`, see comments above 32 | - [rn-secure-storage](https://github.com/talut/rn-secure-storage) 33 | - [expo-secure-store](https://github.com/expo/expo/tree/master/packages/expo-secure-store) - secure for iOS by using keychain services, secure for Android by using values in SharedPreferences encrypted with Android's Keystore system. This Expo library can be used in "Managed" and "Bare" workflow apps, but note that when using this Expo library with React Native App Auth, only the bare workflow is supported. 34 | -------------------------------------------------------------------------------- /packages/react-native-app-auth/android/src/main/java/com/rnappauth/utils/CustomConnectionBuilder.java: -------------------------------------------------------------------------------- 1 | package com.rnappauth.utils; 2 | 3 | /* 4 | * Copyright 2016 The AppAuth for Android Authors. All Rights Reserved. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed under the 12 | * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 13 | * express or implied. See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | import android.net.Uri; 19 | import androidx.annotation.NonNull; 20 | 21 | import net.openid.appauth.connectivity.ConnectionBuilder; 22 | 23 | import java.io.IOException; 24 | import java.net.HttpURLConnection; 25 | import java.util.concurrent.TimeUnit; 26 | import java.util.Map; 27 | 28 | 29 | /** 30 | * An implementation of {@link ConnectionBuilder} that permits 31 | * to set custom headers on connection use to request endpoints. 32 | * Useful for non-spec compliant oauth providers. 33 | */ 34 | public final class CustomConnectionBuilder implements ConnectionBuilder { 35 | 36 | private Map headers = null; 37 | 38 | private int connectionTimeoutMs = (int) TimeUnit.SECONDS.toMillis(15); 39 | private int readTimeoutMs = (int) TimeUnit.SECONDS.toMillis(10); 40 | private ConnectionBuilder connectionBuilder; 41 | 42 | public CustomConnectionBuilder(ConnectionBuilder connectionBuilderToUse) { 43 | connectionBuilder = connectionBuilderToUse; 44 | } 45 | 46 | public void setHeaders (Map headersToSet) { 47 | headers = headersToSet; 48 | } 49 | 50 | public void setConnectionTimeout (int timeout) { 51 | connectionTimeoutMs = timeout; 52 | readTimeoutMs = timeout; 53 | } 54 | 55 | @NonNull 56 | @Override 57 | public HttpURLConnection openConnection(@NonNull Uri uri) throws IOException { 58 | HttpURLConnection conn = connectionBuilder.openConnection(uri); 59 | 60 | if (headers != null) { 61 | for (Map.Entry header: headers.entrySet()) { 62 | conn.setRequestProperty(header.getKey(), header.getValue()); 63 | } 64 | } 65 | 66 | conn.setConnectTimeout(connectionTimeoutMs); 67 | conn.setReadTimeout(readTimeoutMs); 68 | 69 | return conn; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /docs/docs/providers/azure-active-directory.md: -------------------------------------------------------------------------------- 1 | # Azure Active Directory 2 | 3 | Azure Active directory has two OAuth endpoints - [v1 and v2](https://docs.microsoft.com/en-us/azure/active-directory/develop/azure-ad-endpoint-comparison). Ideally, you'd want to use v2, but it has [some limitations](https://docs.microsoft.com/en-us/azure/active-directory/develop/azure-ad-endpoint-comparison#limitations), e.g. if your application relies on SAML, you'll have to use v1. 4 | 5 | ## V1 6 | 7 | The main difference between v1 and v2 is that v1 uses _resources_ and v2 uses _scopes_ for access management. 8 | 9 | V1 [does not specify a revocation endpoint](https://docs.microsoft.com/en-us/azure/active-directory/active-directory-configurable-token-lifetimes#access-tokens) because the access token are not revokable. Therefore `revoke` functionality doesn't work. 10 | 11 | See the [Azure docs on requesting an access token](https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-protocols-oauth-code#request-an-authorization-code) for more info on additional parameters. 12 | 13 | Please Note: 14 | 15 | - `Scopes` is ignored. 16 | - `additionalParameters.resource` may be required based on the tenant settings. 17 | 18 | ```js 19 | const config = { 20 | issuer: 'https://login.microsoftonline.com/your-tenant-id', 21 | clientId: 'your-client-id', 22 | redirectUrl: 'com.myapp://oauth/redirect/', 23 | additionalParameters: { 24 | resource: 'your-resource', 25 | }, 26 | }; 27 | 28 | // Log in to get an authentication token 29 | const authState = await authorize(config); 30 | 31 | // Refresh token 32 | const refreshedState = await refresh(config, { 33 | refreshToken: authState.refreshToken, 34 | }); 35 | ``` 36 | 37 | ## V2 38 | 39 | The V2 endpoint follows the standard OAuth protocol with scopes. Detailed documentation [here](https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-overview). 40 | 41 | ```js 42 | const config = { 43 | issuer: 'https://login.microsoftonline.com/your-tenant-id/v2.0', 44 | clientId: 'your-client-id', 45 | redirectUrl: 'com.myapp://oauth/redirect/', 46 | scopes: ['openid', 'profile', 'email', 'offline_access'], 47 | }; 48 | 49 | // Log in to get an authentication token 50 | const authState = await authorize(config); 51 | 52 | // Refresh token 53 | const refreshedState = await refresh(config, { 54 | refreshToken: authState.refreshToken, 55 | }); 56 | ``` 57 | 58 | **Important** When you add your app in the azure portal and are given a `redirectUrl` to use, make sure you add a trailing slash when you add it to your config - e.g. `msauth.BUNDLEID://auth/` - failure to add that causes it to fail in IOS. 59 | 60 | **Logout:** To properly implement the `logout` functionality, please refer to the necessary requirements outlined in [this comment](https://github.com/FormidableLabs/react-native-app-auth/issues/715#issuecomment-1057444218). 61 | -------------------------------------------------------------------------------- /docs/src/components/landing/landing-hero.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { ProjectBadge } from 'formidable-oss-badges'; 3 | 4 | export const LandingHero = ({ 5 | body, 6 | copyText, 7 | heading, 8 | navItems, 9 | }: { 10 | body: string; 11 | copyText: string; 12 | heading: string; 13 | navItems: { link: string; title: string }[]; 14 | }) => { 15 | const [buttonText, setButtonText] = useState('Copy'); 16 | 17 | const changeText = text => { 18 | setButtonText(text); 19 | }; 20 | 21 | return ( 22 |
23 |
24 |
25 |
26 | 32 |
33 |
34 |

{heading}

35 |

{body}

36 |
37 | 51 |
52 | 63 |
64 |
65 |
66 |
67 | ); 68 | }; 69 | -------------------------------------------------------------------------------- /docs/src/theme/prism-diff-highlight.ts: -------------------------------------------------------------------------------- 1 | import { EnvConfig, PrismLib } from 'prism-react-renderer'; 2 | import { TokenStream, Token } from 'prismjs'; 3 | 4 | const LANGUAGE_REGEX = /^diff-([\w-]+)/i; 5 | 6 | const tokenStreamToString = (tokenStream: TokenStream): string => { 7 | const result: string[] = []; 8 | const stack: TokenStream[] = [tokenStream]; 9 | 10 | while (stack.length > 0) { 11 | const item = stack.pop(); 12 | 13 | if (typeof item === 'string') { 14 | result.push(item); 15 | } else if (Array.isArray(item)) { 16 | for (let i = item.length - 1; i >= 0; i--) { 17 | stack.push(item[i]); 18 | } 19 | } else { 20 | // If it's a Token, convert it to a string and push it 21 | stack.push(item.content); 22 | } 23 | } 24 | 25 | return result.join(''); 26 | }; 27 | 28 | export function diffHighlight(Prism: PrismLib) { 29 | Prism.hooks.add('after-tokenize', function(env: EnvConfig) { 30 | let diffLanguage; 31 | let diffGrammar; 32 | const language = env.language; 33 | if (language !== 'diff') { 34 | const langMatch = LANGUAGE_REGEX.exec(language); 35 | if (!langMatch) { 36 | return; // not a language specific diff 37 | } 38 | 39 | diffLanguage = langMatch[1]; 40 | diffGrammar = Prism.languages[diffLanguage]; 41 | if (!diffGrammar) { 42 | console.error( 43 | 'prism-diff-highlight:', 44 | `You need to add language '${diffLanguage}' to use '${language}'` 45 | ); 46 | return; 47 | } 48 | } else return; 49 | 50 | const newTokens = []; 51 | env.tokens.forEach(token => { 52 | if (typeof token === 'string') { 53 | newTokens.push(...Prism.tokenize(token, diffGrammar)); 54 | } else if (token.type === 'unchanged') { 55 | newTokens.push(...Prism.tokenize(tokenStreamToString(token), diffGrammar)); 56 | } else if (['deleted-sign', 'inserted-sign'].includes(token.type)) { 57 | token.alias = [ 58 | token.type === 'deleted-sign' ? 'diff-highlight-deleted' : 'diff-highlight-inserted', 59 | ]; 60 | // diff parser always return "deleted" and "inserted" lines with content of type array 61 | if (token.content.length > 1) { 62 | const newTokenContent: Array = []; 63 | // preserve prefixes and don't parse them again 64 | // subTokens from diff parser are of type Token 65 | (token.content as Array).forEach((subToken: Token) => { 66 | if (subToken.type === 'prefix') { 67 | newTokenContent.push(subToken); 68 | } else { 69 | newTokenContent.push(...Prism.tokenize(tokenStreamToString(subToken), diffGrammar)); 70 | } 71 | }); 72 | token.content = newTokenContent; 73 | } 74 | newTokens.push(token); 75 | } else if (token.type === 'coord') { 76 | newTokens.push(token); 77 | } 78 | }); 79 | console.log(newTokens); 80 | env.tokens = newTokens; 81 | }); 82 | } 83 | -------------------------------------------------------------------------------- /docs/docs/usage/register.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | --- 4 | 5 | # Registration 6 | 7 | This will perform [dynamic client registration](https://openid.net/specs/openid-connect-registration-1_0.html) on the given provider. 8 | If the provider supports dynamic client registration, it will generate a `clientId` for you to use in subsequent calls to this library. 9 | 10 | ```js 11 | import { register } from 'react-native-app-auth'; 12 | 13 | const registerConfig = { 14 | issuer: '', 15 | redirectUrls: ['', ''], 16 | }; 17 | 18 | const registerResult = await register(registerConfig); 19 | ``` 20 | 21 | #### registerConfig 22 | 23 | - **issuer** - (`string`) same as in authorization config 24 | - **serviceConfiguration** - (`object`) same as in authorization config 25 | - **redirectUrls** - (`array`) _REQUIRED_ specifies all of the redirect urls that your client will use for authentication 26 | - **responseTypes** - (`array`) an array that specifies which [OAuth 2.0 response types](https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html) your client will use. The default value is `['code']` 27 | - **grantTypes** - (`array`) an array that specifies which [OAuth 2.0 grant types](https://oauth.net/2/grant-types/) your client will use. The default value is `['authorization_code']` 28 | - **subjectType** - (`string`) requests a specific [subject type](https://openid.net/specs/openid-connect-core-1_0.html#SubjectIDTypes) for your client 29 | - **tokenEndpointAuthMethod** (`string`) specifies which `clientAuthMethod` your client will use for authentication. The default value is `'client_secret_basic'` 30 | - **additionalParameters** - (`object`) additional parameters that will be passed in the registration request. 31 | Must be string values! E.g. setting `additionalParameters: { hello: 'world', foo: 'bar' }` would add 32 | `hello=world&foo=bar` to the authorization request. 33 | - **dangerouslyAllowInsecureHttpRequests** - (`boolean`) _ANDROID_ same as in authorization config 34 | - **customHeaders** - (`object`) _ANDROID_ same as in authorization config 35 | - **connectionTimeoutSeconds** - (`number`) configure the request timeout interval in seconds. This must be a positive number. The default values are 60 seconds on iOS and 15 seconds on Android. 36 | 37 | #### registerResult 38 | 39 | This is the result from the auth server 40 | 41 | - **clientId** - (`string`) the assigned client id 42 | - **clientIdIssuedAt** - (`string`) _OPTIONAL_ date string of when the client id was issued 43 | - **clientSecret** - (`string`) _OPTIONAL_ the assigned client secret 44 | - **clientSecretExpiresAt** - (`string`) date string of when the client secret expires, which will be provided if `clientSecret` is provided. If `new Date(clientSecretExpiresAt).getTime() === 0`, then the secret never expires 45 | - **registrationClientUri** - (`string`) _OPTIONAL_ uri that can be used to perform subsequent operations on the registration 46 | - **registrationAccessToken** - (`string`) token that can be used at the endpoint given by `registrationClientUri` to perform subsequent operations on the registration. Will be provided if `registrationClientUri` is provided 47 | -------------------------------------------------------------------------------- /examples/demo/android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /docs/docusaurus.config.ts: -------------------------------------------------------------------------------- 1 | import { themes as prismThemes } from 'prism-react-renderer'; 2 | import { Config } from '@docusaurus/types'; 3 | 4 | const config: Config = { 5 | title: 'React Native App Auth', 6 | tagline: 'React native bridge for AppAuth - an SDK for communicating with OAuth2 providers.', 7 | favicon: 'img/nearform-icon.svg', 8 | url: 'https://commerce.nearform.com/', 9 | baseUrl: '/open-source/react-native-app-auth', 10 | onBrokenLinks: 'throw', 11 | onBrokenMarkdownLinks: 'warn', 12 | i18n: { 13 | defaultLocale: 'en', 14 | locales: ['en'], 15 | }, 16 | presets: [ 17 | [ 18 | 'classic', 19 | /** @type {import('@docusaurus/preset-classic').Options} */ 20 | { 21 | docs: { 22 | sidebarPath: './sidebars.ts', 23 | }, 24 | theme: { 25 | customCss: './src/css/custom.css', 26 | }, 27 | gtag: { 28 | trackingID: "G-M971D063B9", 29 | }, 30 | }, 31 | ], 32 | ], 33 | themes: [ 34 | [ 35 | require.resolve('@easyops-cn/docusaurus-search-local'), 36 | /** @type {import("@easyops-cn/docusaurus-search-local").PluginOptions} */ 37 | { 38 | hashed: true, 39 | indexBlog: false, 40 | }, 41 | ], 42 | ], 43 | plugins: [ 44 | async function myPlugin() { 45 | return { 46 | name: 'tailwind-plugin', 47 | configurePostCss(postcssOptions) { 48 | postcssOptions.plugins = [ 49 | require('postcss-import'), 50 | require('tailwindcss'), 51 | require('autoprefixer'), 52 | ]; 53 | return postcssOptions; 54 | }, 55 | }; 56 | }, 57 | ], 58 | themeConfig: { 59 | /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ 60 | metadata: [ 61 | { name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1' }, 62 | ], 63 | docs: { 64 | sidebar: { 65 | hideable: true, 66 | }, 67 | }, 68 | navbar: { 69 | title: 'React Native App Auth', 70 | logo: { 71 | alt: 'NearForm Logo', 72 | src: 'img/nearform-logo-white.svg', 73 | }, 74 | items: [ 75 | { 76 | type: 'docSidebar', 77 | sidebarId: 'sidebar', 78 | position: 'left', 79 | label: 'Documentation', 80 | }, 81 | { 82 | href: 'https://github.com/FormidableLabs/react-native-app-auth', 83 | 'aria-label': 'GitHub Repository', 84 | className: 'header-github-link', 85 | position: 'right', 86 | }, 87 | ], 88 | }, 89 | footer: { 90 | logo: { 91 | alt: 'Nearform logo', 92 | src: 'img/nearform-logo-white.svg', 93 | href: 'https://commerce.nearform.com', 94 | width: 100, 95 | height: 100, 96 | }, 97 | copyright: `Copyright © 2013-${new Date().getFullYear()} Nearform`, 98 | }, 99 | prism: { 100 | theme: prismThemes.github, 101 | darkTheme: prismThemes.dracula, 102 | additionalLanguages: ['diff', 'diff-ts'], 103 | }, 104 | }, 105 | }; 106 | 107 | export default config; 108 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at kadi.kraman@formidable.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /docs/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; 3 | import Layout from '@theme/Layout'; 4 | 5 | import { LandingHero } from '../components/landing/landing-hero'; 6 | import { LandingBanner } from '../components/landing/landing-banner'; 7 | import { LandingFeaturedProjects } from '../components/landing/landing-featured-projects'; 8 | import { LandingFeatures } from '../components/landing/landing-features'; 9 | 10 | export default function Home(): JSX.Element { 11 | const { siteConfig } = useDocusaurusContext(); 12 | return ( 13 | 14 |
15 | 31 |
32 | 41 | 47 | 80 |
81 | ); 82 | } 83 | -------------------------------------------------------------------------------- /packages/react-native-app-auth/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # react-native-app-auth 2 | 3 | ## 8.0.1 4 | 5 | ### Patch Changes 6 | 7 | - Update AppAuth-iOS to version 1.7.6 ([#1039](https://github.com/FormidableLabs/react-native-app-auth/pull/1039)) 8 | 9 | ## 8.0.0 10 | 11 | ### Major Changes 12 | 13 | - Breaking change (iOS, Mac Catalyst): The boolean values `useNonce`, `usePCKE`, and `prefersEphemeralSession` are now handled correctly. Previously, they were all being interpreted as `false` regardless of their actual values, but now the intended (`true` or `false`) value is correctly marshalled from JavaScript to native. To preserve behaviour from before this breaking change, explicitly set them all to `false`. ([#1000](https://github.com/FormidableLabs/react-native-app-auth/pull/1000)) 14 | 15 | ### Patch Changes 16 | 17 | - fix hard crash if config object was incorrect ([#1010](https://github.com/FormidableLabs/react-native-app-auth/pull/1010)) 18 | 19 | ## 7.2.0 20 | 21 | ### Minor Changes 22 | 23 | - Updated the minimum version of AppAuth-iOS to 1.7.3 to meet the package's requirement, which includes the necessary privacy manifest. ([#971](https://github.com/FormidableLabs/react-native-app-auth/pull/971)) 24 | 25 | ## 7.1.3 26 | 27 | ### Patch Changes 28 | 29 | - Moves '@changesets/cli' from dependencies to devDependencies, so that it isn't downloaded for react-native-app-auth package users ([#945](https://github.com/FormidableLabs/react-native-app-auth/pull/945)) 30 | 31 | ## 7.1.2 32 | 33 | ### Patch Changes 34 | 35 | - Fix iosCustomBrowser not exchanging token ([`cb3b70a`](https://github.com/FormidableLabs/react-native-app-auth/commit/cb3b70a24cc02f46c72805a933ece66726e72213)) 36 | 37 | ## 7.1.1 38 | 39 | ### Patch Changes 40 | 41 | - Fix Android crash with NullPointerException ([`a437123`](https://github.com/FormidableLabs/react-native-app-auth/commit/a4371235f37894e2aede6645efef95cf26e4143f)) 42 | 43 | ## 7.1.0 44 | 45 | ### Minor Changes 46 | 47 | - Added `androidTrustedWebActivity` config to opt-in to EXTRA_LAUNCH_AS_TRUSTED_WEB_ACTIVITY ([#908](https://github.com/FormidableLabs/react-native-app-auth/pull/908)) 48 | 49 | ## 7.0.0 50 | 51 | ### Minor Changes 52 | 53 | - Added support for Chrome Trusted Web Activity ([#897](https://github.com/FormidableLabs/react-native-app-auth/pull/897)) 54 | 55 | ### Patch Changes 56 | 57 | - Fix order of parameters for register on iOS ([#804](https://github.com/FormidableLabs/react-native-app-auth/pull/804)) 58 | 59 | * Readme update for RN 0.68+ setup ([#900](https://github.com/FormidableLabs/react-native-app-auth/pull/900)) 60 | 61 | - Update README to link to Contributing guide ([#887](https://github.com/FormidableLabs/react-native-app-auth/pull/887)) 62 | 63 | * correct swift setup example code ([#775](https://github.com/FormidableLabs/react-native-app-auth/pull/775)) 64 | 65 | - Improve readability of method arguments be renaming `headers` argument to `customHeaders` ([#899](https://github.com/FormidableLabs/react-native-app-auth/pull/899)) 66 | 67 | * Fix support of setAdditionalParameters on logout method on Android ([#765](https://github.com/FormidableLabs/react-native-app-auth/pull/765)) 68 | 69 | - Update the Example app to RN 0.72 ([#896](https://github.com/FormidableLabs/react-native-app-auth/pull/896)) 70 | 71 | * Fix authorization state parameter in iOS when using custom configuration ([#847](https://github.com/FormidableLabs/react-native-app-auth/pull/847)) 72 | 73 | - Adding GitHub release workflow ([#853](https://github.com/FormidableLabs/react-native-app-auth/pull/853)) 74 | 75 | * Added Asgardeo configuration example ([#882](https://github.com/FormidableLabs/react-native-app-auth/pull/882)) 76 | -------------------------------------------------------------------------------- /docs/src/css/custom.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Any CSS included here will be global. The classic template 3 | * bundles Infima by default. Infima is a CSS framework designed to 4 | * work well for content-centric websites. 5 | */ 6 | @tailwind base; 7 | @tailwind components; 8 | @tailwind utilities; 9 | 10 | body { 11 | scroll-behavior: smooth; 12 | text-rendering: optimizeSpeed; 13 | } 14 | 15 | @font-face { 16 | font-family: 'Inter'; 17 | src: url('/font/InterRegular.woff2') format('woff2'); 18 | font-weight: 400; 19 | font-style: normal; 20 | font-display: swap; 21 | } 22 | 23 | @font-face { 24 | font-family: 'Inter'; 25 | src: url('/font/InterMedium.woff2') format('woff2'); 26 | font-weight: 500; 27 | font-style: normal; 28 | font-display: swap; 29 | } 30 | 31 | @font-face { 32 | font-family: 'Inter'; 33 | src: url('/font/InterBold.woff2') format('woff2'); 34 | font-weight: 700; 35 | font-style: normal; 36 | font-display: swap; 37 | } 38 | 39 | .hero-pattern { 40 | background-image: url('/img/hero-pattern.png'); 41 | } 42 | 43 | :root { 44 | --ifm-color-primary: #5abdee; 45 | --ifm-color-primary-dark: #3cb1eb; 46 | --ifm-color-primary-darker: #2dabe9; 47 | --ifm-color-primary-darkest: #1592d0; 48 | --ifm-color-primary-light: #78c9f1; 49 | --ifm-color-primary-lighter: #87cff3; 50 | --ifm-color-primary-lightest: #b3e1f7; 51 | --ifm-code-font-size: 95%; 52 | --ifm-list-item-margin: 0; 53 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); 54 | --ifm-navbar-background-color: #000e38; 55 | --ifm-navbar-link-color: #ffffff; 56 | --ifm-footer-background-color: #000e38; 57 | --ifm-footer-padding-vertical: 1rem; 58 | } 59 | 60 | [data-theme='dark'] { 61 | --ifm-color-primary: #5abdee; 62 | --ifm-color-primary-dark: #3cb1eb; 63 | --ifm-color-primary-darker: #2dabe9; 64 | --ifm-color-primary-darkest: #1592d0; 65 | --ifm-color-primary-light: #78c9f1; 66 | --ifm-color-primary-lighter: #87cff3; 67 | --ifm-color-primary-lightest: #b3e1f7; 68 | --ifm-list-item-margin: 0; 69 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); 70 | --ifm-navbar-background-color: #242526; 71 | --ifm-footer-background-color: #242526; 72 | } 73 | 74 | /* Nav */ 75 | .header-github-link::before { 76 | content: ''; 77 | width: 24px; 78 | height: 24px; 79 | display: flex; 80 | background: url("data:image/svg+xml;charset=utf-8,%3Csvg fill='white' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E") 81 | no-repeat; 82 | } 83 | 84 | .navbar__inner button svg { 85 | color: white; 86 | } 87 | 88 | /* Custom Footer */ 89 | .footer__bottom.text--center { 90 | display: flex; 91 | justify-content: space-between; 92 | align-items: center; 93 | } 94 | 95 | .footer__bottom.text--center a { 96 | opacity: 1 !important; 97 | } 98 | 99 | .footer__copyright { 100 | color: white; 101 | } 102 | -------------------------------------------------------------------------------- /examples/demo/ios/Example.xcodeproj/xcshareddata/xcschemes/Example.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 | -------------------------------------------------------------------------------- /examples/demo/android/app/src/debug/java/com/example/ReactNativeFlipper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | *

This source code is licensed under the MIT license found in the LICENSE file in the root 5 | * directory of this source tree. 6 | */ 7 | package com.example; 8 | 9 | import android.content.Context; 10 | import com.facebook.flipper.android.AndroidFlipperClient; 11 | import com.facebook.flipper.android.utils.FlipperUtils; 12 | import com.facebook.flipper.core.FlipperClient; 13 | import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin; 14 | import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin; 15 | import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin; 16 | import com.facebook.flipper.plugins.inspector.DescriptorMapping; 17 | import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin; 18 | import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor; 19 | import com.facebook.flipper.plugins.network.NetworkFlipperPlugin; 20 | import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin; 21 | import com.facebook.react.ReactInstanceEventListener; 22 | import com.facebook.react.ReactInstanceManager; 23 | import com.facebook.react.bridge.ReactContext; 24 | import com.facebook.react.modules.network.NetworkingModule; 25 | import okhttp3.OkHttpClient; 26 | 27 | /** 28 | * Class responsible of loading Flipper inside your React Native application. This is the debug 29 | * flavor of it. Here you can add your own plugins and customize the Flipper setup. 30 | */ 31 | public class ReactNativeFlipper { 32 | public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) { 33 | if (FlipperUtils.shouldEnableFlipper(context)) { 34 | final FlipperClient client = AndroidFlipperClient.getInstance(context); 35 | 36 | client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults())); 37 | client.addPlugin(new DatabasesFlipperPlugin(context)); 38 | client.addPlugin(new SharedPreferencesFlipperPlugin(context)); 39 | client.addPlugin(CrashReporterPlugin.getInstance()); 40 | 41 | NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin(); 42 | NetworkingModule.setCustomClientBuilder( 43 | new NetworkingModule.CustomClientBuilder() { 44 | @Override 45 | public void apply(OkHttpClient.Builder builder) { 46 | builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin)); 47 | } 48 | }); 49 | client.addPlugin(networkFlipperPlugin); 50 | client.start(); 51 | 52 | // Fresco Plugin needs to ensure that ImagePipelineFactory is initialized 53 | // Hence we run if after all native modules have been initialized 54 | ReactContext reactContext = reactInstanceManager.getCurrentReactContext(); 55 | if (reactContext == null) { 56 | reactInstanceManager.addReactInstanceEventListener( 57 | new ReactInstanceEventListener() { 58 | @Override 59 | public void onReactContextInitialized(ReactContext reactContext) { 60 | reactInstanceManager.removeReactInstanceEventListener(this); 61 | reactContext.runOnNativeModulesQueueThread( 62 | new Runnable() { 63 | @Override 64 | public void run() { 65 | client.addPlugin(new FrescoFlipperPlugin()); 66 | } 67 | }); 68 | } 69 | }); 70 | } else { 71 | client.addPlugin(new FrescoFlipperPlugin()); 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /examples/demo/ios/Example/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 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Thanks for reading this guide and considering to contribute code to the project! 2 | We always welcome contributions to `react-native-app-auth`. 3 | 4 | This document will get you started on how to contribute and things you should know. 5 | So please give it a thorough read. 6 | 7 | This guide will help you to get started setting up the repository, and how to contribute 8 | a change. Please read it carefully. 9 | 10 | If you're reading this, please take a look at our [Code of Conduct](CODE_OF_CONDUCT.md) 11 | as well. 12 | 13 | ## How do I set up the project? 14 | 15 | First of all, you'll need to run `yarn install` to install all dependencies. 16 | 17 | All of the module's JavaScript code is located inside `index.js`, and all of the tests 18 | are located inside `index.spec.js`. 19 | 20 | You can run the tests using `yarn test`, which uses Jest. 21 | 22 | Please note that the project is set up to use our eslint rules. 23 | You can run the linter manually using `yarn lint`. 24 | 25 | 26 | 27 | ## Where is the native code? 28 | 29 | Since most of the code inside `index.js` is just an interface to the native code of this project 30 | you'll most likely be searching for those native modules instead. 31 | 32 | Those are located at: 33 | 34 | - `./android/src/main/java/com/reactlibrary/RNAppAuth{Module,Package}.java` for Android 35 | - `./ios/RNAppAuth.{m,h}` for iOS 36 | 37 | All changes to the behaviour of the Android native module must be replicated for iOS as well, 38 | and vice-versa. 39 | If you don't feel comfortable making changes to both, feel free to contribute 40 | (open a PR) for one of them first, and ask for help. 41 | 42 | ## How do I contribute code? 43 | 44 | 1. Search for something you'd like to change. This can be an open issue, or just a feature 45 | you'd like to implement. Make sure that no one else is already on it, so that you're not 46 | duplicating someone else's effort. 47 | 48 | 2. Fork the repository and then clone it, i.e. `git clone https://github.com/YOUR_NAME/react-native-app-auth.git` 49 | 50 | 3. Checkout a new branch with a descriptive name, e.g. `git checkout -b fix/issue-123` 51 | 52 | 4. Make your changes :sparkles: 53 | 54 | 5. Update the tests if necessary and make sure that the existing ones are still passing using `yarn test` 55 | 56 | 6. Make sure that your code is adhering to the linter's rules using `yarn lint` 57 | 58 | 7. Commit your changes with a short description, `git add -A && git commit -m 'Your meaningful and descriptive message'` 59 | 60 | 8. Push your new branch, `git push -u origin fix/issue-123` 61 | 62 | 9. Finally, open a pull request with a title and a short summary of what has been changed and why. 63 | 64 | 10. Wait for a maintainer to review it and make some changes as they're being recommended and as you see fit. 65 | 66 | 11. Get it merged and make cool a celebratory pose! :dancer: 67 | 68 | ### Using changesets 69 | 70 | Our official release path is to use automation to perform the actual publishing of our packages. The steps are to: 71 | 72 | 1. A human developer adds a changeset. Ideally this is as a part of a PR that will have a version impact on a package. 73 | 2. On merge of a PR our automation system opens a "Version Packages" PR. 74 | 3. On merging the "Version Packages" PR, the automation system publishes the packages. 75 | 76 | Here are more details: 77 | 78 | ### Add a changeset 79 | 80 | When you would like to add a changeset (which creates a file indicating the type of change), in your branch/PR issue this command: 81 | 82 | ```sh 83 | $ yarn changeset 84 | ``` 85 | 86 | to produce an interactive menu. Navigate the packages with arrow keys and hit `` to select 1+ packages. Hit `` when done. Select semver versions for packages and add appropriate messages. From there, you'll be prompted to enter a summary of the change. Some tips for this summary: 87 | 88 | 1. Aim for a single line, 1+ sentences as appropriate. 89 | 2. Include issue links in GH format (e.g. `#123`). 90 | 3. You don't need to reference the current pull request or whatnot, as that will be added later automatically. 91 | 92 | After this, you'll see a new uncommitted file in `.changesets` like: 93 | 94 | ```sh 95 | $ git status 96 | # .... 97 | Untracked files: 98 | (use "git add ..." to include in what will be committed) 99 | .changeset/flimsy-pandas-marry.md 100 | ``` 101 | 102 | Review the file, make any necessary adjustments, and commit it to source. When we eventually do a package release, the changeset notes and version will be incorporated! 103 | 104 | ### Creating versions 105 | 106 | On a merge of a feature PR, the changesets GitHub action will open a new PR titled `"Version Packages"`. This PR is automatically kept up to date with additional PRs with changesets. So, if you're not ready to publish yet, just keep merging feature PRs and then merge the version packages PR later. 107 | 108 | ### Publishing packages 109 | 110 | On the merge of a version packages PR, the changesets GitHub action will publish the packages to npm. 111 | -------------------------------------------------------------------------------- /packages/react-native-app-auth/android/src/main/java/com/rnappauth/utils/TokenResponseFactory.java: -------------------------------------------------------------------------------- 1 | package com.rnappauth.utils; 2 | 3 | import android.text.TextUtils; 4 | 5 | import com.facebook.react.bridge.Arguments; 6 | import com.facebook.react.bridge.WritableArray; 7 | import com.facebook.react.bridge.WritableMap; 8 | 9 | import net.openid.appauth.AuthorizationResponse; 10 | import net.openid.appauth.TokenResponse; 11 | 12 | public final class TokenResponseFactory { 13 | 14 | private static final WritableArray createScopeArray(String scope) { 15 | WritableArray scopeArray = Arguments.createArray(); 16 | if (!TextUtils.isEmpty(scope)) { 17 | String[] scopesArray = scope.split(" "); 18 | 19 | for( int i = 0; i < scopesArray.length; i++) 20 | { 21 | scopeArray.pushString(scopesArray[i]); 22 | } 23 | } 24 | 25 | return scopeArray; 26 | } 27 | 28 | 29 | /* 30 | * Read raw token response into a React Native map to be passed down the bridge 31 | */ 32 | public static final WritableMap tokenResponseToMap(TokenResponse response) { 33 | WritableMap map = Arguments.createMap(); 34 | 35 | map.putString("accessToken", response.accessToken); 36 | map.putMap("additionalParameters", MapUtil.createAdditionalParametersMap(response.additionalParameters)); 37 | map.putString("idToken", response.idToken); 38 | map.putString("refreshToken", response.refreshToken); 39 | map.putString("tokenType", response.tokenType); 40 | 41 | if (response.accessTokenExpirationTime != null) { 42 | map.putString("accessTokenExpirationDate", DateUtil.formatTimestamp(response.accessTokenExpirationTime)); 43 | } 44 | 45 | return map; 46 | } 47 | 48 | /* 49 | * Read raw token response into a React Native map to be passed down the bridge 50 | */ 51 | public static final WritableMap tokenResponseToMap(TokenResponse response, AuthorizationResponse authResponse) { 52 | WritableMap map = Arguments.createMap(); 53 | 54 | map.putString("accessToken", response.accessToken); 55 | map.putMap("authorizeAdditionalParameters", MapUtil.createAdditionalParametersMap(authResponse.additionalParameters)); 56 | map.putMap("tokenAdditionalParameters", MapUtil.createAdditionalParametersMap(response.additionalParameters)); 57 | map.putString("idToken", response.idToken); 58 | map.putString("refreshToken", response.refreshToken); 59 | map.putString("tokenType", response.tokenType); 60 | map.putArray("scopes", createScopeArray(authResponse.scope)); 61 | 62 | if (response.accessTokenExpirationTime != null) { 63 | map.putString("accessTokenExpirationDate", DateUtil.formatTimestamp(response.accessTokenExpirationTime)); 64 | } 65 | 66 | 67 | return map; 68 | } 69 | 70 | 71 | /* 72 | * Read raw authorization into a React Native map to be passed down the bridge 73 | */ 74 | public static final WritableMap authorizationResponseToMap(AuthorizationResponse authResponse) { 75 | WritableMap map = Arguments.createMap(); 76 | map.putString("authorizationCode", authResponse.authorizationCode); 77 | map.putString("accessToken", authResponse.accessToken); 78 | map.putMap("additionalParameters", MapUtil.createAdditionalParametersMap(authResponse.additionalParameters)); 79 | map.putString("idToken", authResponse.idToken); 80 | map.putString("tokenType", authResponse.tokenType); 81 | map.putArray("scopes", createScopeArray(authResponse.scope)); 82 | 83 | if (authResponse.accessTokenExpirationTime != null) { 84 | map.putString("accessTokenExpirationTime", DateUtil.formatTimestamp(authResponse.accessTokenExpirationTime)); 85 | } 86 | 87 | return map; 88 | } 89 | 90 | /* 91 | * Read raw authorization into a React Native map with codeVerifier value added if present to be passed down the bridge 92 | */ 93 | public static final WritableMap authorizationCodeResponseToMap(AuthorizationResponse authResponse, String codeVerifier) { 94 | WritableMap map = Arguments.createMap(); 95 | map.putString("authorizationCode", authResponse.authorizationCode); 96 | map.putString("accessToken", authResponse.accessToken); 97 | map.putMap("additionalParameters", MapUtil.createAdditionalParametersMap(authResponse.additionalParameters)); 98 | map.putString("idToken", authResponse.idToken); 99 | map.putString("tokenType", authResponse.tokenType); 100 | map.putArray("scopes", createScopeArray(authResponse.scope)); 101 | 102 | if (authResponse.accessTokenExpirationTime != null) { 103 | map.putString("accessTokenExpirationTime", DateUtil.formatTimestamp(authResponse.accessTokenExpirationTime)); 104 | } 105 | 106 | if (!TextUtils.isEmpty(codeVerifier)) { 107 | map.putString("codeVerifier", codeVerifier); 108 | } 109 | 110 | return map; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /packages/react-native-app-auth/android/src/main/java/com/rnappauth/utils/MapUtil.java: -------------------------------------------------------------------------------- 1 | package com.rnappauth.utils; 2 | 3 | import android.util.Log; 4 | 5 | import androidx.annotation.Nullable; 6 | 7 | import com.facebook.react.bridge.Arguments; 8 | import com.facebook.react.bridge.ReadableMap; 9 | import com.facebook.react.bridge.ReadableMapKeySetIterator; 10 | import com.facebook.react.bridge.WritableArray; 11 | import com.facebook.react.bridge.WritableMap; 12 | import com.facebook.react.bridge.WritableNativeArray; 13 | import com.facebook.react.bridge.WritableNativeMap; 14 | 15 | import org.json.JSONArray; 16 | import org.json.JSONException; 17 | import org.json.JSONObject; 18 | 19 | import java.util.HashMap; 20 | import java.util.Iterator; 21 | import java.util.Map; 22 | 23 | public class MapUtil { 24 | 25 | public static HashMap readableMapToHashMap(@Nullable ReadableMap readableMap) { 26 | 27 | HashMap hashMap = new HashMap<>(); 28 | if (readableMap != null) { 29 | ReadableMapKeySetIterator iterator = readableMap.keySetIterator(); 30 | while (iterator.hasNextKey()) { 31 | String nextKey = iterator.nextKey(); 32 | hashMap.put(nextKey, readableMap.getString(nextKey)); 33 | } 34 | } 35 | 36 | return hashMap; 37 | } 38 | 39 | public static final WritableMap createAdditionalParametersMap(Map additionalParameters) { 40 | WritableMap additionalParametersMap = Arguments.createMap(); 41 | 42 | if (!additionalParameters.isEmpty()) { 43 | 44 | Iterator iterator = additionalParameters.keySet().iterator(); 45 | 46 | while(iterator.hasNext()) { 47 | String key = iterator.next(); 48 | String value = additionalParameters.get(key); 49 | // Try to parse to JSON 50 | try { 51 | JSONObject jsonObject = new JSONObject(value); 52 | WritableMap json = convertJsonToMap(jsonObject); 53 | additionalParametersMap.putMap(key, json); 54 | continue; 55 | } catch (JSONException ignored) { 56 | 57 | } 58 | additionalParametersMap.putString(key, additionalParameters.get(key)); 59 | } 60 | } 61 | 62 | return additionalParametersMap; 63 | } 64 | 65 | private static WritableMap convertJsonToMap(JSONObject jsonObject) throws JSONException { 66 | WritableMap map = new WritableNativeMap(); 67 | 68 | Iterator iterator = jsonObject.keys(); 69 | while (iterator.hasNext()) { 70 | String key = iterator.next(); 71 | Object value = jsonObject.get(key); 72 | if (value instanceof JSONObject) { 73 | map.putMap(key, convertJsonToMap((JSONObject) value)); 74 | } else if (value instanceof JSONArray) { 75 | map.putArray(key, convertJsonToArray((JSONArray) value)); 76 | } else if (value instanceof Boolean) { 77 | map.putBoolean(key, (Boolean) value); 78 | } else if (value instanceof Integer) { 79 | map.putInt(key, (Integer) value); 80 | } else if (value instanceof Double) { 81 | map.putDouble(key, (Double) value); 82 | } else if (value instanceof Float) { 83 | map.putDouble(key, ((Float) value).doubleValue()); 84 | } else if (value instanceof Long) { 85 | map.putDouble(key, ((Long) value).doubleValue()); 86 | } else if (value instanceof String) { 87 | map.putString(key, (String) value); 88 | } else { 89 | map.putString(key, value.toString()); 90 | } 91 | } 92 | return map; 93 | } 94 | 95 | private static WritableArray convertJsonToArray(JSONArray jsonArray) throws JSONException { 96 | WritableArray array = new WritableNativeArray(); 97 | 98 | for (int i = 0; i < jsonArray.length(); i++) { 99 | Object value = jsonArray.get(i); 100 | if (value instanceof JSONObject) { 101 | array.pushMap(convertJsonToMap((JSONObject) value)); 102 | } else if (value instanceof JSONArray) { 103 | array.pushArray(convertJsonToArray((JSONArray) value)); 104 | } else if (value instanceof Boolean) { 105 | array.pushBoolean((Boolean) value); 106 | } else if (value instanceof Integer) { 107 | array.pushInt((Integer) value); 108 | } else if (value instanceof Double) { 109 | array.pushDouble((Double) value); 110 | } else if (value instanceof Float) { 111 | array.pushDouble(((Float) value).doubleValue()); 112 | } else if (value instanceof Long) { 113 | array.pushDouble(((Long) value).doubleValue()); 114 | } else if (value instanceof String) { 115 | array.pushString((String) value); 116 | } else { 117 | array.pushString(value.toString()); 118 | } 119 | } 120 | return array; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /packages/react-native-app-auth/android/src/main/java/com/rnappauth/utils/UnsafeConnectionBuilder.java: -------------------------------------------------------------------------------- 1 | package com.rnappauth.utils; 2 | 3 | /* 4 | * Copyright 2016 The AppAuth for Android Authors. All Rights Reserved. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed under the 12 | * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 13 | * express or implied. See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | import android.annotation.SuppressLint; 19 | import android.net.Uri; 20 | import androidx.annotation.NonNull; 21 | import androidx.annotation.Nullable; 22 | import android.util.Log; 23 | 24 | import net.openid.appauth.Preconditions; 25 | import net.openid.appauth.connectivity.ConnectionBuilder; 26 | 27 | import java.io.IOException; 28 | import java.net.HttpURLConnection; 29 | import java.net.URL; 30 | import java.security.KeyManagementException; 31 | import java.security.NoSuchAlgorithmException; 32 | import java.security.cert.X509Certificate; 33 | import java.util.concurrent.TimeUnit; 34 | 35 | import javax.net.ssl.HostnameVerifier; 36 | import javax.net.ssl.HttpsURLConnection; 37 | import javax.net.ssl.SSLContext; 38 | import javax.net.ssl.SSLSession; 39 | import javax.net.ssl.TrustManager; 40 | import javax.net.ssl.X509TrustManager; 41 | 42 | /** 43 | * An implementation of {@link ConnectionBuilder} that permits connecting to http 44 | * links, and ignores certificates for https connections. *THIS SHOULD NOT BE USED IN PRODUCTION 45 | * CODE*. It is intended to facilitate easier testing of AppAuth against development servers 46 | * only. 47 | */ 48 | public final class UnsafeConnectionBuilder implements ConnectionBuilder { 49 | 50 | public static final UnsafeConnectionBuilder INSTANCE = new UnsafeConnectionBuilder(); 51 | 52 | private static final String TAG = "ConnBuilder"; 53 | 54 | private static final int CONNECTION_TIMEOUT_MS = (int) TimeUnit.SECONDS.toMillis(15); 55 | private static final int READ_TIMEOUT_MS = (int) TimeUnit.SECONDS.toMillis(10); 56 | 57 | private static final String HTTP = "http"; 58 | private static final String HTTPS = "https"; 59 | 60 | @SuppressLint("TrustAllX509TrustManager") 61 | private static final TrustManager[] ANY_CERT_MANAGER = new TrustManager[] { 62 | new X509TrustManager() { 63 | public X509Certificate[] getAcceptedIssuers() { 64 | return null; 65 | } 66 | 67 | public void checkClientTrusted(X509Certificate[] certs, String authType) {} 68 | 69 | public void checkServerTrusted(X509Certificate[] certs, String authType) {} 70 | } 71 | }; 72 | 73 | @SuppressLint("BadHostnameVerifier") 74 | private static final HostnameVerifier ANY_HOSTNAME_VERIFIER = new HostnameVerifier() { 75 | public boolean verify(String hostname, SSLSession session) { 76 | return true; 77 | } 78 | }; 79 | 80 | @Nullable 81 | private static final SSLContext TRUSTING_CONTEXT; 82 | 83 | static { 84 | SSLContext context; 85 | try { 86 | context = SSLContext.getInstance("SSL"); 87 | } catch (NoSuchAlgorithmException e) { 88 | Log.e("ConnBuilder", "Unable to acquire SSL context"); 89 | context = null; 90 | } 91 | 92 | SSLContext initializedContext = null; 93 | if (context != null) { 94 | try { 95 | context.init(null, ANY_CERT_MANAGER, new java.security.SecureRandom()); 96 | initializedContext = context; 97 | } catch (KeyManagementException e) { 98 | Log.e(TAG, "Failed to initialize trusting SSL context"); 99 | } 100 | } 101 | 102 | TRUSTING_CONTEXT = initializedContext; 103 | } 104 | 105 | private UnsafeConnectionBuilder() { 106 | // no need to construct new instances 107 | } 108 | 109 | @NonNull 110 | @Override 111 | public HttpURLConnection openConnection(@NonNull Uri uri) throws IOException { 112 | Preconditions.checkNotNull(uri, "url must not be null"); 113 | Preconditions.checkArgument(HTTP.equals(uri.getScheme()) || HTTPS.equals(uri.getScheme()), 114 | "scheme or uri must be http or https"); 115 | HttpURLConnection conn = (HttpURLConnection) new URL(uri.toString()).openConnection(); 116 | conn.setConnectTimeout(CONNECTION_TIMEOUT_MS); 117 | conn.setReadTimeout(READ_TIMEOUT_MS); 118 | conn.setInstanceFollowRedirects(false); 119 | 120 | if (conn instanceof HttpsURLConnection && TRUSTING_CONTEXT != null) { 121 | HttpsURLConnection httpsConn = (HttpsURLConnection) conn; 122 | httpsConn.setSSLSocketFactory(TRUSTING_CONTEXT.getSocketFactory()); 123 | httpsConn.setHostnameVerifier(ANY_HOSTNAME_VERIFIER); 124 | } 125 | 126 | return conn; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /examples/demo/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "com.android.application" 2 | apply plugin: "com.facebook.react" 3 | 4 | /** 5 | * This is the configuration block to customize your React Native Android app. 6 | * By default you don't need to apply any configuration, just uncomment the lines you need. 7 | */ 8 | react { 9 | /* Folders */ 10 | // The root of your project, i.e. where "package.json" lives. Default is '..' 11 | // root = file("../") 12 | // The folder where the react-native NPM package is. Default is ../node_modules/react-native 13 | // reactNativeDir = file("../node_modules/react-native") 14 | // The folder where the react-native Codegen package is. Default is ../node_modules/@react-native/codegen 15 | // codegenDir = file("../node_modules/@react-native/codegen") 16 | // The cli.js file which is the React Native CLI entrypoint. Default is ../node_modules/react-native/cli.js 17 | // cliFile = file("../node_modules/react-native/cli.js") 18 | 19 | /* Variants */ 20 | // The list of variants to that are debuggable. For those we're going to 21 | // skip the bundling of the JS bundle and the assets. By default is just 'debug'. 22 | // If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants. 23 | // debuggableVariants = ["liteDebug", "prodDebug"] 24 | 25 | /* Bundling */ 26 | // A list containing the node command and its flags. Default is just 'node'. 27 | // nodeExecutableAndArgs = ["node"] 28 | // 29 | // The command to run when bundling. By default is 'bundle' 30 | // bundleCommand = "ram-bundle" 31 | // 32 | // The path to the CLI configuration file. Default is empty. 33 | // bundleConfig = file(../rn-cli.config.js) 34 | // 35 | // The name of the generated asset file containing your JS bundle 36 | // bundleAssetName = "MyApplication.android.bundle" 37 | // 38 | // The entry file for bundle generation. Default is 'index.android.js' or 'index.js' 39 | // entryFile = file("../js/MyApplication.android.js") 40 | // 41 | // A list of extra flags to pass to the 'bundle' commands. 42 | // See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle 43 | // extraPackagerArgs = [] 44 | 45 | /* Hermes Commands */ 46 | // The hermes compiler command to run. By default it is 'hermesc' 47 | // hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc" 48 | // 49 | // The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map" 50 | // hermesFlags = ["-O", "-output-source-map"] 51 | } 52 | 53 | /** 54 | * Set this to true to Run Proguard on Release builds to minify the Java bytecode. 55 | */ 56 | def enableProguardInReleaseBuilds = false 57 | 58 | /** 59 | * The preferred build flavor of JavaScriptCore (JSC) 60 | * 61 | * For example, to use the international variant, you can use: 62 | * `def jscFlavor = 'org.webkit:android-jsc-intl:+'` 63 | * 64 | * The international variant includes ICU i18n library and necessary data 65 | * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that 66 | * give correct results when using with locales other than en-US. Note that 67 | * this variant is about 6MiB larger per architecture than default. 68 | */ 69 | def jscFlavor = 'org.webkit:android-jsc:+' 70 | 71 | android { 72 | ndkVersion rootProject.ext.ndkVersion 73 | 74 | compileSdkVersion rootProject.ext.compileSdkVersion 75 | 76 | namespace "com.example" 77 | defaultConfig { 78 | applicationId "com.example" 79 | minSdkVersion rootProject.ext.minSdkVersion 80 | targetSdkVersion rootProject.ext.targetSdkVersion 81 | versionCode 1 82 | versionName "1.0" 83 | } 84 | signingConfigs { 85 | debug { 86 | storeFile file('debug.keystore') 87 | storePassword 'android' 88 | keyAlias 'androiddebugkey' 89 | keyPassword 'android' 90 | } 91 | } 92 | buildTypes { 93 | debug { 94 | signingConfig signingConfigs.debug 95 | } 96 | release { 97 | // Caution! In production, you need to generate your own keystore file. 98 | // see https://reactnative.dev/docs/signed-apk-android. 99 | signingConfig signingConfigs.debug 100 | minifyEnabled enableProguardInReleaseBuilds 101 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" 102 | } 103 | } 104 | } 105 | 106 | dependencies { 107 | // The version of react-native is set by the React Native Gradle Plugin 108 | implementation("com.facebook.react:react-android") 109 | 110 | debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") 111 | debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") { 112 | exclude group:'com.squareup.okhttp3', module:'okhttp' 113 | } 114 | 115 | debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") 116 | if (hermesEnabled.toBoolean()) { 117 | implementation("com.facebook.react:hermes-android") 118 | } else { 119 | implementation jscFlavor 120 | } 121 | } 122 | 123 | apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project) 124 | -------------------------------------------------------------------------------- /examples/demo/App.js: -------------------------------------------------------------------------------- 1 | import React, {useState, useCallback, useMemo} from 'react'; 2 | import {Alert} from 'react-native'; 3 | import { 4 | authorize, 5 | refresh, 6 | revoke, 7 | prefetchConfiguration, 8 | } from 'react-native-app-auth'; 9 | import { 10 | Page, 11 | Button, 12 | ButtonContainer, 13 | Form, 14 | FormLabel, 15 | FormValue, 16 | Heading, 17 | } from './components'; 18 | 19 | const configs = { 20 | identityserver: { 21 | issuer: 'https://demo.duendesoftware.com', 22 | clientId: 'interactive.public', 23 | redirectUrl: 'io.identityserver.demo:/oauthredirect', 24 | additionalParameters: {}, 25 | scopes: ['openid', 'profile', 'email', 'offline_access'], 26 | 27 | // serviceConfiguration: { 28 | // authorizationEndpoint: 'https://demo.duendesoftware.com/connect/authorize', 29 | // tokenEndpoint: 'https://demo.duendesoftware.com/connect/token', 30 | // revocationEndpoint: 'https://demo.duendesoftware.com/connect/revoke' 31 | // } 32 | }, 33 | auth0: { 34 | issuer: 'https://rnaa-demo.eu.auth0.com', 35 | clientId: 'VtXdAoGFcYzZ3IJaNy4UIS5RNHhdbKbU', 36 | redirectUrl: 'rnaa-demo://oauthredirect', 37 | additionalParameters: {}, 38 | scopes: ['openid', 'profile', 'email', 'offline_access'], 39 | 40 | // serviceConfiguration: { 41 | // authorizationEndpoint: 'https://samples.auth0.com/authorize', 42 | // tokenEndpoint: 'https://samples.auth0.com/oauth/token', 43 | // revocationEndpoint: 'https://samples.auth0.com/oauth/revoke' 44 | // } 45 | }, 46 | }; 47 | 48 | const defaultAuthState = { 49 | hasLoggedInOnce: false, 50 | provider: '', 51 | accessToken: '', 52 | accessTokenExpirationDate: '', 53 | refreshToken: '', 54 | }; 55 | 56 | const App = () => { 57 | const [authState, setAuthState] = useState(defaultAuthState); 58 | React.useEffect(() => { 59 | prefetchConfiguration({ 60 | warmAndPrefetchChrome: true, 61 | connectionTimeoutSeconds: 5, 62 | ...configs.auth0, 63 | }); 64 | }, []); 65 | 66 | const handleAuthorize = useCallback(async provider => { 67 | try { 68 | const config = configs[provider]; 69 | const newAuthState = await authorize({ 70 | ...config, 71 | connectionTimeoutSeconds: 5, 72 | iosPrefersEphemeralSession: true, 73 | }); 74 | 75 | setAuthState({ 76 | hasLoggedInOnce: true, 77 | provider: provider, 78 | ...newAuthState, 79 | }); 80 | } catch (error) { 81 | Alert.alert('Failed to log in', error.message); 82 | } 83 | }, []); 84 | 85 | const handleRefresh = useCallback(async () => { 86 | try { 87 | const config = configs[authState.provider]; 88 | const newAuthState = await refresh(config, { 89 | refreshToken: authState.refreshToken, 90 | }); 91 | 92 | setAuthState(current => ({ 93 | ...current, 94 | ...newAuthState, 95 | refreshToken: newAuthState.refreshToken || current.refreshToken, 96 | })); 97 | } catch (error) { 98 | Alert.alert('Failed to refresh token', error.message); 99 | } 100 | }, [authState]); 101 | 102 | const handleRevoke = useCallback(async () => { 103 | try { 104 | const config = configs[authState.provider]; 105 | await revoke(config, { 106 | tokenToRevoke: authState.accessToken, 107 | sendClientId: true, 108 | }); 109 | 110 | setAuthState({ 111 | provider: '', 112 | accessToken: '', 113 | accessTokenExpirationDate: '', 114 | refreshToken: '', 115 | }); 116 | } catch (error) { 117 | Alert.alert('Failed to revoke token', error.message); 118 | } 119 | }, [authState]); 120 | 121 | const showRevoke = useMemo(() => { 122 | if (authState.accessToken) { 123 | const config = configs[authState.provider]; 124 | if (config.issuer || config.serviceConfiguration.revocationEndpoint) { 125 | return true; 126 | } 127 | } 128 | return false; 129 | }, [authState]); 130 | 131 | return ( 132 | 133 | {authState.accessToken ? ( 134 |

135 | accessToken 136 | {authState.accessToken} 137 | accessTokenExpirationDate 138 | {authState.accessTokenExpirationDate} 139 | refreshToken 140 | {authState.refreshToken} 141 | scopes 142 | {authState.scopes.join(', ')} 143 |
144 | ) : ( 145 | 146 | {authState.hasLoggedInOnce ? 'Goodbye.' : 'Hello, stranger.'} 147 | 148 | )} 149 | 150 | 151 | {!authState.accessToken ? ( 152 | <> 153 |