Keycloak OAuth2 Authorization in React Native

Gilshaan Jabbar
4 min readAug 19, 2020

Getting started

npm install react-native-app-auth --save

authorize

This is the main function to use for authentication. Invoking this function will do the whole login flow and returns the access token, refresh token and access token expiry date when successful, or it throws an error when not successful.

import { authorize } from 'react-native-app-auth';const config = {issuer: '<YOUR_ISSUER_URL>',clientId: '<YOUR_CLIENT_ID>',redirectUrl: '<YOUR_REDIRECT_URL>',scopes: ['<YOUR_SCOPES_ARRAY>'],};const result = await authorize(config);

config

This is your configuration object for the client. The config is passed into each of the methods with optional overrides.

  • issuer — (string) base URI of the authentication server.
  • clientId — (string) REQUIRED your client id on the auth server
  • clientSecret — (string) client secret to pass to token exchange requests. ⚠️ Read more about client secrets
  • redirectUrl — (string) REQUIRED the url that links back to your app with the auth code. character. E.g. for redirect uri is com.myapp://oauth
  • scopes — (array<string>) the scopes for your token, e.g. ['email', 'offline_access'].

result

This is the result from the auth server

  • accessToken — (string) the access token
  • accessTokenExpirationDate — (string) the token expiration date
  • authorizeAdditionalParameters — (Object) additional url parameters from the authorizationEndpoint response.
  • tokenAdditionalParameters — (Object) additional url parameters from the tokenEndpoint response.
  • idToken — (string) the id token
  • refreshToken — (string) the refresh token
  • tokenType — (string) the token type, e.g. Bearer
  • scopes — ([string]) the scopes the user has agreed to be granted

refresh

This method will refresh the accessToken using the refreshToken. Some auth providers will also give you a new refreshToken

import { refresh } from 'react-native-app-auth';const config = {issuer: '<YOUR_ISSUER_URL>',clientId: '<YOUR_CLIENT_ID>',redirectUrl: '<YOUR_REDIRECT_URL>',scopes: ['<YOUR_SCOPES_ARRAY>'],};const result = await refresh(config, {refreshToken: `<REFRESH_TOKEN>`,});

revoke

This method will revoke a token. The tokenToRevoke can be either an accessToken or a refreshToken

import { revoke } from 'react-native-app-auth';const config = {issuer: '<YOUR_ISSUER_URL>',clientId: '<YOUR_CLIENT_ID>',redirectUrl: '<YOUR_REDIRECT_URL>',scopes: ['<YOUR_SCOPES_ARRAY>'],};const result = await revoke(config, {tokenToRevoke: `<TOKEN_TO_REVOKE>`,includeBasicAuth: true,sendClientId: true,});

Setup

iOS Setup

To setup the iOS project, you need to perform three steps:

CocoaPods

  • cd ios
  • pod install

Register redirect URL scheme

If you intend to support iOS 10 and older, you need to define the supported redirect URL schemes in your Info.plist as follows:

<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>com.your.app.identifier</string>
<key>CFBundleURLSchemes</key>
<array>
<string>io.identityserver.demo</string>
</array>
</dict>
</array>
  • CFBundleURLName is any globally unique string. A common practice is to use your app identifier.
  • CFBundleURLSchemes is an array of URL schemes your app needs to handle. The scheme is the beginning of your OAuth Redirect URL, up to the scheme separator (:) character. E.g. if your redirect uri is com.myapp://oauth, then the url scheme will is com.myapp.

Define openURL callback in AppDelegate

You need to retain the auth session, in order to continue the authorization flow from the redirect. Follow these steps:

RNAppAuth will call on the given app's delegate via [UIApplication sharedApplication].delegate. Furthermore, RNAppAuth expects the delegate instance to conform to the protocol RNAppAuthAuthorizationFlowManager. Make AppDelegate conform to RNAppAuthAuthorizationFlowManager with the following changes to AppDelegate.h:

+ #import "RNAppAuthAuthorizationFlowManager.h"- @interface AppDelegate : UIResponder <UIApplicationDelegate, RCTBridgeDelegate>+ @interface AppDelegate : UIResponder <UIApplicationDelegate, RCTBridgeDelegate, RNAppAuthAuthorizationFlowManager>+ @property(nonatomic, weak)id<RNAppAuthAuthorizationFlowManagerDelegate>authorizationFlowManagerDelegate;

Add the following code to AppDelegate.m (to support iOS 10 and below)

+ - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString *, id> *) options {+  return [self.authorizationFlowManagerDelegate resumeExternalUserAgentFlowWithURL:url];+ }

Android Setup

Note: for RN >= 0.57, you will get a warning about compile being obsolete. To get rid of this warning, use patch-package to replace compile with implementation as in this PR — we’re not deploying this right now, because it would break the build for RN < 57.

To setup the Android project, you need to add redirect scheme manifest placeholder:

To capture the authorization redirect, add the following property to the defaultConfig in android/app/build.gradle:

android {
defaultConfig {
manifestPlaceholders = [
appAuthRedirectScheme: 'io.identityserver.demo'
]
}
}

The scheme is the beginning of your OAuth Redirect URL, up to the scheme separator (:) character. E.g. if your redirect uri is com.myapp://oauth, then the url scheme will is com.myapp.

Error messages

  • service_configuration_fetch_error - could not fetch the service configuration
  • authentication_failed - user authentication failed
  • token_refresh_failed - could not exchange the refresh token for a new JWT
  • registration_failed - could not register
  • browser_not_found (Android only) - no suitable browser installed

Note about client secrets

Some authentication providers, including examples cited below, require you to provide a client secret. The authors of the AppAuth library

strongly recommend 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.

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.

--

--