• Stars
    star
    232
  • Rank 172,847 (Top 4 %)
  • Language
    Java
  • License
    MIT License
  • Created about 6 years ago
  • Updated about 2 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Generic Capacitor OAuth 2 client plugin. Stop the war in Ukraine!

Capacitor OAuth 2 client plugin

This is a generic OAuth 2 client plugin. It let you configure the oauth parameters yourself instead of using SDKs. Therefore it is usable with various providers. See identity providers the community has already used this plugin with.

How to install

For Capacitor v4

npm i @byteowls/capacitor-oauth2
npx cap sync

For Capacitor v3 use 3.0.1

npm i @byteowls/[email protected]
npx cap sync

For Capacitor v2 use 2.1.0

npm i @byteowls/[email protected]
npx cap sync

Versions

Plugin For Capacitor Docs Notes
4.x 4.x.x README Breaking changes see Changelog. XCode 12.0 needs this version
3.x 3.x.x README Breaking changes see Changelog. XCode 12.0 needs this version
2.x 2.x.x README Breaking changes see Changelog. XCode 11.4 needs this version
1.x 1.x.x README

For further details on what has changed see the CHANGELOG.

Sponsors

I would like to especially thank some people and companies for supporting my work on this plugin and therefore improving it for everybody.

Maintainers

Maintainer GitHub Social
Michael Oberwasserlechner moberwasserlechner @michaelowl_web

Actively maintained: YES

Supported flows

See the excellent article about OAuth2 response type combinations.

https://medium.com/@darutk/diagrams-of-all-the-openid-connect-flows-6968e3990660

The plugin on the other will behave differently depending on the existence of certain config parameters:

These parameters are:

  • accessTokenEndpoint
  • resourceUrl

e.g.

If responseType=code, pkceDisable=true and accessTokenEndpoint is missing the authorizationCode will be resolve along with the whole authorization response. This only works for the Web and Android. On iOS the used lib does not allows to cancel after the authorization request see #13.

If you just need the id_token JWT you have to set accessTokenEndpoint and resourceUrl to null.

Tested / working flows

These flows are already working and were tested by me.

Implicit flow

responseType: "token"

Code flow + PKCE

...
responseType: "code"
pkceEnable: true
...

Please be aware that some providers (OneDrive, Auth0) allow Code Flow + PKCE only for native apps. Web apps have to use implicit flow.

Important

For security reasons this plugin does/will not support Code Flow without PKCE.

That would include storing your client secret in client code which is highly insecure and not recommended. That flow should only be used on the backend (server).

Configuration

Starting with version 3.0.0, the plugin is registered automatically on all platforms.

Use it

import {OAuth2Client} from "@byteowls/capacitor-oauth2";

@Component({
  template: '<button (click)="onOAuthBtnClick()">Login with OAuth</button>' +
   '<button (click)="onOAuthRefreshBtnClick()">Refresh token</button>' +
   '<button (click)="onLogoutClick()">Logout OAuth</button>'
})
export class SignupComponent {
    accessToken: string;
    refreshToken: string;

    onOAuthBtnClick() {
        OAuth2Client.authenticate(
            oauth2Options
        ).then(response => {
            this.accessToken = response["access_token"]; // storage recommended for android logout
            this.refreshToken = response["refresh_token"];

            // only if you include a resourceUrl protected user values are included in the response!
            let oauthUserId = response["id"];
            let name = response["name"];

            // go to backend
        }).catch(reason => {
            console.error("OAuth rejected", reason);
        });
    }

    // Refreshing tokens only works on iOS/Android for now
    onOAuthRefreshBtnClick() {
      if (!this.refreshToken) {
        console.error("No refresh token found. Log in with OAuth first.");
      }

      OAuth2Client.refreshToken(
        oauth2RefreshOptions
      ).then(response => {
        this.accessToken = response["access_token"]; // storage recommended for android logout
        // Don't forget to store the new refresh token as well!
        this.refreshToken = response["refresh_token"];
        // Go to backend
      }).catch(reason => {
          console.error("Refreshing token failed", reason);
      });
    }

    onLogoutClick() {
            OAuth2Client.logout(
                oauth2LogoutOptions,
                this.accessToken // only used on android
            ).then(() => {
                // do something
            }).catch(reason => {
                console.error("OAuth logout failed", reason);
            });
        }
}

Options

See the oauth2Options and oauth2RefreshOptions interfaces at https://github.com/moberwasserlechner/capacitor-oauth2/blob/main/src/definitions.ts for details.

Example:

{
      authorizationBaseUrl: "https://accounts.google.com/o/oauth2/auth",
      accessTokenEndpoint: "https://www.googleapis.com/oauth2/v4/token",
      scope: "email profile",
      resourceUrl: "https://www.googleapis.com/userinfo/v2/me",
      logsEnabled: true,
      web: {
        appId: environment.oauthAppId.google.web,
        responseType: "token", // implicit flow
        accessTokenEndpoint: "", // clear the tokenEndpoint as we know that implicit flow gets the accessToken from the authorizationRequest
        redirectUrl: "http://localhost:4200",
        windowOptions: "height=600,left=0,top=0"
      },
      android: {
        appId: environment.oauthAppId.google.android,
        responseType: "code", // if you configured a android app in google dev console the value must be "code"
        redirectUrl: "com.companyname.appname:/" // package name from google dev console
      },
      ios: {
        appId: environment.oauthAppId.google.ios,
        responseType: "code", // if you configured a ios app in google dev console the value must be "code"
        redirectUrl: "com.companyname.appname:/" // Bundle ID from google dev console
      }
    }

authenticate() and logout()

Overrideable Base Parameter

These parameters are overrideable in every platform

parameter default required description since
appId yes aka clientId, serviceId, ...
authorizationBaseUrl yes
responseType yes
redirectUrl yes 2.0.0
accessTokenEndpoint If empty the authorization response incl code is returned. Known issue: Not on iOS!
resourceUrl If empty the tokens are return instead. If you need just the id_token you have to set both accessTokenEndpoint and resourceUrl to null or empty ``.
additionalResourceHeaders Additional headers for the resource request 3.0.0
pkceEnabled false Enable PKCE if you need it. Note: On iOS because of #111 boolean values are not overwritten. You have to explicitly define the param in the subsection.
logsEnabled false Enable extensive logging. All plugin outputs are prefixed with I/Capacitor/OAuth2ClientPlugin: across all platforms. Note: On iOS because of #111 boolean values are not overwritten. You have to explicitly define the param in the subsection. 3.0.0
scope
state The plugin always uses a state.
If you don't provide one we generate it.
additionalParameters Additional parameters for anything you might miss, like none, response_mode.

Just create a key value pair.
{ "key1": "value", "key2": "value, "response_mode": "value"}

Platform Web

parameter default required description since
windowOptions e.g. width=500,height=600,left=0,top=0
windowTarget _blank
windowReplace 3.0.0

Platform Android

parameter default required description since
customHandlerClass Provide a class name implementing com.byteowls.capacitor.oauth2.handler.OAuth2CustomHandler
handleResultOnNewIntent false Alternative to handle the activity result. The onNewIntent method is only call if the App was killed while logging in.
handleResultOnActivityResult true

Platform iOS

parameter default required description since
customHandlerClass Provide a class name implementing ByteowlsCapacitorOauth2.OAuth2CustomHandler
siwaUseScope SiWA default scope is name email if you want to use the configured one set this param true 2.1.0

refreshToken()

parameter default required description since
appId yes aka clientId, serviceId, ...
accessTokenEndpoint yes
refreshToken yes
scope

Error Codes

authenticate()

  • ERR_PARAM_NO_APP_ID ... The appId / clientId is missing. (web, android, ios)
  • ERR_PARAM_NO_AUTHORIZATION_BASE_URL ... The authorization base url is missing. (web, android, ios)
  • ERR_PARAM_NO_RESPONSE_TYPE ... The response type is missing. (web, android, ios)
  • ERR_PARAM_NO_REDIRECT_URL ... The redirect url is missing. (web, android, ios)
  • ERR_STATES_NOT_MATCH ... The state included in the authorization code request does not match the one in the redirect. Security risk! (web, android, ios)
  • ERR_AUTHORIZATION_FAILED ... The authorization failed.
  • ERR_NO_ACCESS_TOKEN ... No access_token found. (web, android)
  • ERR_NO_AUTHORIZATION_CODE ... No authorization code was returned in the redirect response. (web, android, ios)
  • USER_CANCELLED ... The user cancelled the login flow. (web, android, ios)
  • ERR_CUSTOM_HANDLER_LOGIN ... Login through custom handler class failed. See logs and check your code. (android, ios)
  • ERR_CUSTOM_HANDLER_LOGOUT ... Logout through custom handler class failed. See logs and check your code. (android, ios)
  • ERR_ANDROID_NO_BROWSER ... No suitable browser could be found! (Android)
  • ERR_ANDROID_RESULT_NULL ... The auth result is null. The intent in the ActivityResult is null. This might be a valid state but make sure you configured Android part correctly! See Platform Android
  • ERR_GENERAL ... A unspecific error. Check the logs to see want exactly happened. (web, android, ios)

refreshToken()

  • ERR_PARAM_NO_APP_ID ... The appId / clientId is missing. (android, ios)
  • ERR_PARAM_NO_ACCESS_TOKEN_ENDPOINT ... The access token endpoint url is missing. It is only needed on refresh, on authenticate it is optional. (android, ios)
  • ERR_PARAM_NO_REFRESH_TOKEN ... The refresh token is missing. (android, ios)
  • ERR_NO_ACCESS_TOKEN ... No access_token found. (web, android)
  • ERR_GENERAL ... A unspecific error. Check the logs to see want exactly happened. (android, ios)

Platform: Web/PWA

This implementation just opens a browser window to let users enter their credentials.

As there is no provider SDK used to accomplish OAuth, no additional javascript files must be loaded and so there is no performance impact using this plugin in a web application.

Register plugin

On Web/PWA the plugin is registered automatically by Capacitor.

Platform: Android

Prerequisite: Capacitor Android Docs

Register plugin

On Android the plugin is registered automatically by Capacitor.

Android Default Config

Skip this, if you use a OAuth2CustomHandler. See below.

android/app/src/main/res/AndroidManifest.xml

The AndroidManifest.xml in your Capacitor Android project already contains

    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="@string/custom_url_scheme" />
    </intent-filter>

Find the following line in your AndroidManifest.xml

<data android:scheme="@string/custom_url_scheme" />

and change it to

<data android:scheme="@string/custom_url_scheme" android:host="oauth" />

Note: Actually any value for android:host will do. It does not has to be oauth.

This will fix an issues within the oauth workflow when the application is shown twice. See Issue #15 for details what happens.

android/app/src/main/res/values/strings.xml

In your strings.xml change the custom_url_scheme string to your actual scheme value. Do NOT include ://oauth/redirect or other endpoint urls here!

<string name="custom_url_scheme">com.example.yourapp</string>

<!-- wrong -->
<!-- <string name="custom_url_scheme">com.example.yourapp://endpoint/path</string> -->

android/app/build.gradle

android.defaultConfig.manifestPlaceholders = [
  // change to the 'custom_url_scheme' value in your strings.xml. They need to be the same. e.g.
  "appAuthRedirectScheme": "com.example.yourapp"
]

Troubleshooting

  1. If your appAuthRedirectScheme does not get recognized because you are using a library that replaces it (e.g.: onesignal-cordova-plugin), you will have to add it to your buildTypes like the following:
android.buildTypes.debug.manifestPlaceholders =  [
  'appAuthRedirectScheme': '<@string/custom_url_scheme from string.xml>' // e.g. com.companyname.appname
]
android.buildTypes.release.manifestPlaceholders = [
  'appAuthRedirectScheme': '<@string/custom_url_scheme from string.xml>' // e.g. com.companyname.appname
]
  1. "ERR_ANDROID_RESULT_NULL": See Issue #52 for details. I cannot reproduce this behaviour. Moreover, there might be situation this state is valid. In other cases e.g. in the linked issue a configuration tweak fixed it.

  2. To prevent some logout issues on certain OAuth2 providers (like Salesforce for example), you should provide the id_token parameter on the logout(...) function. This ensures that not only the cookies are deleted, but also the logout link is called from the OAuth2 provider. Also, it uses the system browser that the plugin uses (and not the user's default browser) to call the logout URL. This additionally ensures that the cookies are deleted in the correct browser.

Custom OAuth Handler

Some OAuth provider (Facebook) force developers to use their SDK on Android.

This plugin should be as generic as possible so I don't want to include provider specific dependencies.

Therefore, I created a mechanism which let developers integrate custom SDK features in this plugin. Simply configure a full qualified classname in the option property android.customHandlerClass. This class has to implement com.byteowls.capacitor.oauth2.handler.OAuth2CustomHandler.

See a full working example below!

Platform: iOS

Register plugin

On iOS the plugin is registered automatically by Capacitor.

iOS Default Config

Skip this, if you use a OAuth2CustomHandler. See below.

Open ios/App/App/Info.plist in XCode (Context menu -> Open as -> Source) and add the value of redirectUrl from your config without :/ like that

	<key>CFBundleURLTypes</key>
	<array>
		<dict>
			<key>CFBundleURLSchemes</key>
			<array>
				<string>com.companyname.appname</string>
			</array>
		</dict>
	</array>

Custom OAuth Handler

Some OAuth provider (e.g., Facebook) force developers to use their SDK on iOS.

This plugin should be as generic as possible, so I don't want to include provider specific dependencies.

Therefore, I created a mechanism which let developers integrate custom SDK features in this plugin. Simply configure the class name in the option property ios.customHandlerClass. This class has to implement ByteowlsCapacitorOauth2.OAuth2CustomHandler.

See a full working example below!

Platform: Electron

  • No timeline.

Where to store access tokens?

You can use the capacitor-secure-storage plugin for this.

This plugin stores data in secure locations for natives devices.

List of Providers

These are some of the providers that can be configured with this plugin. I'm happy to add others ot the list, if you let me know.

Name Example (config,...) Notes
Google see below
Facebook see below
Azure see below
Apple see below ios only

Examples

Apple

iOS 13+

Minimum config

appleLogin() {
  OAuth2Client.authenticate({
    appId: "xxxxxxxxx",
    authorizationBaseUrl: "https://appleid.apple.com/auth/authorize",
  });
}

The plugin requires authorizationBaseUrl as it triggers the native support and because it is needed for other platforms anyway. Those platforms are not supported yet.

appId is required as well for internal, generic reasons and any not blank value is fine.

It is also possible to control the scope although Apple only supports email and/or fullName. Add siwaUseScope: true to the ios section. Then you can use scope: "fullName", scope: "email" or both but the latter is the default one if siwaUseScope is not set or false.

appleLogin() {
  OAuth2Client.authenticate({
    appId: "xxxxxxxxx",
    authorizationBaseUrl: "https://appleid.apple.com/auth/authorize",
    ios: {
      siwaUseScope: true,
      scope: "fullName"
    }
  });
}

As "Signin with Apple" is only supported since iOS 13 you should show the according button only in that case.

In Angular do sth like

import {Component, OnInit} from '@angular/core';
import {Device, DeviceInfo} from "@capacitor/device";
import {OAuth2Client} from "@byteowls/capacitor-oauth2";

@Component({
  templateUrl: './siwa.component.html'
})
export class SiwaComponent implements OnInit {

  ios: boolean;
  siwaSupported: boolean;
  deviceInfo: DeviceInfo;

  async ngOnInit() {
      this.deviceInfo = await Device.getInfo();
      this.ios = this.deviceInfo.platform === "ios";
      if (this.ios) {
          const majorVersion: number = +this.deviceInfo.osVersion.split(".")[0];
          this.siwaSupported = majorVersion >= 13;
      }
  }
}

And show the button only if siwaSupported is true.

The response contains these fields:

"id"
"given_name"
"family_name"
"email"
"real_user_status"
"state"
"id_token"
"code"

iOS <12

not supported

PWA

not supported

Android

not supported

Azure Active Directory / Azure AD B2C

It's important to use the urls you see in the Azure portal for the specific platform.

Note: Don't be confused by the fact that the Azure portal shows "Azure Active Directory" and "Azure AD B2C" services. They share the same core features and therefore the plugin should work either way.

PWA

import {OAuth2AuthenticateOptions, OAuth2Client} from "@byteowls/capacitor-oauth2";

export class AuthService {

  getAzureB2cOAuth2Options(): OAuth2AuthenticateOptions {
    return {
        appId: environment.oauthAppId.azureBc2.appId,
        authorizationBaseUrl: `https://login.microsoftonline.com/${environment.oauthAppId.azureBc2.tenantId}/oauth2/v2.0/authorize`,
        scope: "https://graph.microsoft.com/User.Read", // See Azure Portal -> API permission
        accessTokenEndpoint: `https://login.microsoftonline.com/${environment.oauthAppId.azureBc2.tenantId}/oauth2/v2.0/token`,
        resourceUrl: "https://graph.microsoft.com/v1.0/me/",
        responseType: "code",
        pkceEnabled: true,
        logsEnabled: true,
        web: {
            redirectUrl: environment.redirectUrl,
            windowOptions: "height=600,left=0,top=0",
        },
        android: {
            redirectUrl: "msauth://{package-name}/{url-encoded-signature-hash}" // See Azure Portal -> Authentication -> Android Configuration "Redirect URI"
        },
        ios: {
            pkceEnabled: true, // workaround for bug #111
            redirectUrl: "msauth.{package-name}://auth"
        }
    };
  }
}
Custom Scopes

If you need to use custom scopes configured in "API permissions" and created in "Expose an API" in Azure Portal you might need to remove the resourceUrl parameter if your scopes are not included in the response. I can not give a clear advise on those Azure specifics. Try to experiment with the config until Azure includes everything you need in the response.

A configuration with custom scopes might look like this:
import {OAuth2Client} from "@byteowls/capacitor-oauth2";

  getAzureB2cOAuth2Options(): OAuth2AuthenticateOptions {
    return {
        appId: environment.oauthAppId.azureBc2.appId,
        authorizationBaseUrl: `https://login.microsoftonline.com/${environment.oauthAppId.azureBc2.tenantId}/oauth2/v2.0/authorize`,
        scope: "api://uuid-created-by-azure/scope.name1 api://uuid-created-by-azure/scope.name2", // See Azure Portal -> API permission / Expose an API
        accessTokenEndpoint: `https://login.microsoftonline.com/${environment.oauthAppId.azureBc2.tenantId}/oauth2/v2.0/token`,
        // no resourceURl!
        responseType: "code",
        pkceEnabled: true,
        logsEnabled: true,
        web: {
            redirectUrl: environment.redirectUrl,
            windowOptions: "height=600,left=0,top=0",
        },
        android: {
            redirectUrl: "msauth://{package-name}/{url-encoded-signature-hash}" // See Azure Portal -> Authentication -> Android Configuration "Redirect URI"
        },
        ios: {
            pkceEnabled: true, // workaround for bug #111
            redirectUrl: "msauth.{package-name}://auth"
        }
    };
  }
}
Prior configs
Other configs that works in prior versions
import {OAuth2Client} from "@byteowls/capacitor-oauth2";

azureLogin() {
  OAuth2Client.authenticate({
    appId: "xxxxxxxxx",
    authorizationBaseUrl: "https://tenantb2c.b2clogin.com/tfp/tenantb2c.onmicrosoft.com/B2C_1_SignUpAndSignIn/oauth2/v2.0/authorize",
    accessTokenEndpoint: "",
    scope: "openid offline_access https://tenantb2c.onmicrosoft.com/capacitor-api/demo.read",
    responseType: "token",
    web: {
        redirectUrl: "http://localhost:8100/auth"
    },
    android: {
        pkceEnabled: true,
        responseType: "code",
        redirectUrl: "com.tenant.app://oauth/auth", // Use the value from Azure config. Platform "Android"
        accessTokenEndpoint: "https://tenantb2c.b2clogin.com/tfp/tenantb2c.onmicrosoft.com/B2C_1_SignUpAndSignIn/oauth2/v2.0/token",
        handleResultOnNewIntent: true,
        handleResultOnActivityResult: true
    },
    ios: {
        pkceEnabled: true,
        responseType: "code",
        redirectUrl: "msauth.BUNDLE_ID://oauth", // Use the value from Azure config. Platform "iOS/Mac"
        accessTokenEndpoint: "https://tenantb2c.b2clogin.com/tfp/tenantb2c.onmicrosoft.com/B2C_1_SignUpAndSignIn/oauth2/v2.0/token",
    }
  });
}
import {OAuth2Client} from "@byteowls/capacitor-oauth2";

azureLogin() {
  OAuth2Client.authenticate({
    appId: 'XXXXXXXXXX-XXXXXXXXXX-XXXXXXXXX',
    authorizationBaseUrl: 'https://TENANT.b2clogin.com/tfp/TENANT.onmicrosoft.com/B2C_1_policy-signin-signup-web/oauth2/v2.0/authorize',
    accessTokenEndpoint: '',
    scope: 'https://XXXXXXX.onmicrosoft.com/TestApi4/demo.read',
    responseType: 'token',
    web: {
      redirectUrl: 'http://localhost:8100/'
    },
    android: {
      pkceEnabled: true,
      responseType: 'code',
      redirectUrl: 'com.company.project://oauth/redirect',
      accessTokenEndpoint: 'https://TENANT.b2clogin.com/TENANT.onmicrosoft.com/B2C_1_policy-signin-signup-web',
      handleResultOnNewIntent: true,
      handleResultOnActivityResult: true
    },
    ios: {
      pkceEnabled: true,
      responseType: 'code',
      redirectUrl: 'com.company.project://oauth',
      accessTokenEndpoint: 'https://TENANT.b2clogin.com/TENANT.onmicrosoft.com/B2C_1_policy-signin-signup-web',
    }
  });
}

Android

If you have only Azure B2C as identity provider you have to add a new intent-filter to your main activity in AndroidManifest.xml.

<!-- azure ad b2c -->
<intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="@string/azure_b2c_scheme" android:host="@string/package_name" android:path="@string/azure_b2c_signature_hash" />
</intent-filter>

If you have multiple identity providers or your logins always ends in a USER_CANCELLED error like in #178 you have to create an additional Activity in AndroidManifest.xml.

These are both activities! Make sure to replace com.company.project.MainActivity with your real qualified class path!

<activity
      android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"
      android:name="com.company.project.MainActivity"
      android:label="@string/title_activity_main"
      android:launchMode="singleTask"
      android:theme="@style/AppTheme.NoActionBarLaunch">

      <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>

      <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="@string/custom_url_scheme" android:host="@string/custom_host" />
      </intent-filter>

    </activity>

    <activity android:name="net.openid.appauth.RedirectUriReceiverActivity" android:exported="true">
      <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="@string/custom_url_scheme" android:host="@string/custom_host" />
      </intent-filter>

      <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="@string/azure_b2c_scheme" android:host="@string/package_name" android:path="@string/azure_b2c_signature_hash" />
      </intent-filter>
    </activity>

Values for android/app/src/main/res/values/string.xml. Replace the example values!

  <string name="title_activity_main">Your Project's Name/string>
  <string name="custom_url_scheme">com.company.project</string>
  <string name="custom_host">foo</string><!-- any value is fine -->
  <string name="package_name">com.company.project</string>
  <string name="azure_b2c_scheme">msauth</string>
  <string name="azure_b2c_signature_hash">/your-signature-hash</string><!-- The leading slash is required. Copied from Azure Portal Android Config "Signature hash" field -->

See Android Default Config

iOS

Open Info.plist in XCode by clicking right on that file -> Open as -> Source Code. Note: XCode does not "like" files opened and changed externally.

	<key>CFBundleURLTypes</key>
	<array>
		<dict>
			<key>CFBundleURLSchemes</key>
			<array>
				<!-- msauth.BUNDLE_ID -->
				<string>msauth.com.yourcompany.yourproject</string>
			</array>
		</dict>
	</array>

Important:

  • Do not enter :// as part of your redirect url
  • Make sure the msauth. prefix is present

Troubleshooting

In case of problems please read #91 and #96

See this example repo by @loonix.

Google

PWA

import {OAuth2Client} from "@byteowls/capacitor-oauth2";

googleLogin() {
    OAuth2Client.authenticate({
      authorizationBaseUrl: "https://accounts.google.com/o/oauth2/auth",
      accessTokenEndpoint: "https://www.googleapis.com/oauth2/v4/token",
      scope: "email profile",
      resourceUrl: "https://www.googleapis.com/userinfo/v2/me",
      web: {
        appId: environment.oauthAppId.google.web,
        responseType: "token", // implicit flow
        accessTokenEndpoint: "", // clear the tokenEndpoint as we know that implicit flow gets the accessToken from the authorizationRequest
        redirectUrl: "http://localhost:4200",
        windowOptions: "height=600,left=0,top=0"
      },
      android: {
        appId: environment.oauthAppId.google.android,
        responseType: "code", // if you configured a android app in google dev console the value must be "code"
        redirectUrl: "com.companyname.appname:/" // package name from google dev console
      },
      ios: {
        appId: environment.oauthAppId.google.ios,
        responseType: "code", // if you configured a ios app in google dev console the value must be "code"
        redirectUrl: "com.companyname.appname:/" // Bundle ID from google dev console
      }
    }).then(resourceUrlResponse => {
      // do sth e.g. check with your backend
    }).catch(reason => {
      console.error("Google OAuth rejected", reason);
    });
  }

Android

See Android Default Config

iOS

See iOS Default Config

Facebook

PWA

import {OAuth2Client} from "@byteowls/capacitor-oauth2";

facebookLogin() {
    let fbApiVersion = "2.11";
    OAuth2Client.authenticate({
      appId: "YOUR_FACEBOOK_APP_ID",
      authorizationBaseUrl: "https://www.facebook.com/v" + fbApiVersion + "/dialog/oauth",
      resourceUrl: "https://graph.facebook.com/v" + fbApiVersion + "/me",
      web: {
        responseType: "token",
        redirectUrl: "http://localhost:4200",
        windowOptions: "height=600,left=0,top=0"
      },
      android: {
        customHandlerClass: "com.companyname.appname.YourAndroidFacebookOAuth2Handler",
      },
      ios: {
        customHandlerClass: "App.YourIOsFacebookOAuth2Handler",
      }
    }).then(resourceUrlResponse => {
      // do sth e.g. check with your backend
    }).catch(reason => {
      console.error("FB OAuth rejected", reason);
    });
  }

Android and iOS

Since October 2018 Strict Mode for Redirect Urls is always on.

Use Strict Mode for Redirect URIs

Only allow redirects that use the Facebook SDK or that exactly match the Valid OAuth Redirect URIs. Strongly recommended.

Before that it was able to use fb<your_app_id>:/authorize in a Android or iOS app and get the accessToken.

Unfortunately now we have to use the SDK for Facebook Login.

I don't want to have a dependency to facebook for users, who don't need Facebook OAuth.

To address this problem I created a integration with custom code in your app customHandlerClass

Android

See https://developers.facebook.com/docs/facebook-login/android/ for more background on how to configure Facebook in your Android app.

  1. Add implementation 'com.facebook.android:facebook-login:4.36.0' to android/app/build.gradle as dependency.

  2. Add to string.xml

    <string name="facebook_app_id"><YOUR_FACEBOOK_APP_ID></string>
    <string name="fb_login_protocol_scheme">fb<YOUR_FACEBOOK_APP_ID></string>
  1. Add to AndroidManifest.xml
<meta-data android:name="com.facebook.sdk.ApplicationId" android:value="@string/facebook_app_id"/>

<activity android:name="com.facebook.FacebookActivity"
  android:configChanges=
    "keyboard|keyboardHidden|screenLayout|screenSize|orientation"
  android:label="@string/app_name" />

<activity android:name="com.facebook.CustomTabActivity" android:exported="true">
  <intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="@string/fb_login_protocol_scheme" />
  </intent-filter>
</activity>
  1. Create a custom handler class
package com.companyname.appname;

import android.app.Activity;

import com.byteowls.capacitor.oauth2.handler.AccessTokenCallback;
import com.byteowls.capacitor.oauth2.handler.OAuth2CustomHandler;
import com.companyname.appname.MainActivity;
import com.facebook.AccessToken;
import com.facebook.FacebookCallback;
import com.facebook.FacebookException;
import com.facebook.login.DefaultAudience;
import com.facebook.login.LoginBehavior;
import com.facebook.login.LoginManager;
import com.facebook.login.LoginResult;
import com.getcapacitor.PluginCall;

import java.util.Collections;

public class YourAndroidFacebookOAuth2Handler implements OAuth2CustomHandler {

  @Override
  public void getAccessToken(Activity activity, PluginCall pluginCall, final AccessTokenCallback callback) {
    AccessToken accessToken = AccessToken.getCurrentAccessToken();
    if (AccessToken.isCurrentAccessTokenActive()) {
      callback.onSuccess(accessToken.getToken());
    } else {
      LoginManager l = LoginManager.getInstance();
      l.logInWithReadPermissions(activity, Collections.singletonList("public_profile"));
      l.setLoginBehavior(LoginBehavior.WEB_ONLY);
      l.setDefaultAudience(DefaultAudience.NONE);
      LoginManager.getInstance().registerCallback(((MainActivity) activity).getCallbackManager(), new FacebookCallback<LoginResult>() {
        @Override
        public void onSuccess(LoginResult loginResult) {
          callback.onSuccess(loginResult.getAccessToken().getToken());
        }

        @Override
        public void onCancel() {
          callback.onCancel();
        }

        @Override
        public void onError(FacebookException error) {
          callback.onCancel();
        }
      });
    }

  }

  @Override
  public boolean logout(Activity activity, PluginCall pluginCall) {
    LoginManager.getInstance().logOut();
    return true;
  }
}
  1. Change your MainActivity like
public class MainActivity extends BridgeActivity {

  private CallbackManager callbackManager;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // Initialize Facebook SDK
    FacebookSdk.sdkInitialize(this.getApplicationContext());
    callbackManager = CallbackManager.Factory.create();
  }

  @Override
  protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (callbackManager.onActivityResult(requestCode, resultCode, data)) {
      return;
    }
  }

  public CallbackManager getCallbackManager() {
    return callbackManager;
  }

}

iOS

See https://developers.facebook.com/docs/swift/getting-started and https://developers.facebook.com/docs/swift/login

  1. Add Facebook pods to ios/App/Podfile and run pod install afterwards
platform :ios, '13.0'
use_frameworks!

# workaround to avoid Xcode caching of Pods that requires
# Product -> Clean Build Folder after new Cordova plugins installed
# Requires CocoaPods 1.6 or newer
install! 'cocoapods', :disable_input_output_paths => true

def capacitor_pods
  pod 'Capacitor', :path => '../../node_modules/@capacitor/ios'
  pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios'
  pod 'ByteowlsCapacitorOauth2', :path => '../../node_modules/@byteowls/capacitor-oauth2'
  # core plugins
  pod 'CapacitorApp', :path => '../../node_modules/@capacitor/app'
  pod 'CapacitorDevice', :path => '../../node_modules/@capacitor/device'
  pod 'CapacitorKeyboard', :path => '../../node_modules/@capacitor/keyboard'
  pod 'CapacitorSplashScreen', :path => '../../node_modules/@capacitor/splash-screen'
  pod 'CapacitorStatusBar', :path => '../../node_modules/@capacitor/status-bar'
end

target 'App' do
  capacitor_pods
  # Add your Pods here
  pod 'FacebookCore'
  pod 'FacebookLogin'
end
  1. Add some Facebook configs to your Info.plist
<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>fb{your-app-id}</string>
    </array>
  </dict>
</array>
<key>FacebookAppID</key>
<string>{your-app-id}</string>
<key>FacebookDisplayName</key>
<string>{your-app-name}</string>
<key>LSApplicationQueriesSchemes</key>
<array>
  <string>fbapi</string>
  <string>fb-messenger-share-api</string>
  <string>fbauth2</string>
  <string>fbshareextension</string>
</array>
  1. Create a custom handler class
import Foundation
import FacebookCore
import FacebookLogin
import Capacitor
import ByteowlsCapacitorOauth2

@objc class YourIOsFacebookOAuth2Handler: NSObject, OAuth2CustomHandler {

    required override init() {
    }

    func getAccessToken(viewController: UIViewController, call: CAPPluginCall, success: @escaping (String) -> Void, cancelled: @escaping () -> Void, failure: @escaping (Error) -> Void) {
        if let accessToken = AccessToken.current {
            success(accessToken.tokenString)
        } else {
            DispatchQueue.main.async {
                let loginManager = LoginManager()
                // I only need the most basic permissions but others are available
                loginManager.logIn(permissions: [ .publicProfile ], viewController: viewController) { result in
                    switch result {
                    case .success(_, _, let accessToken):
                        success(accessToken.tokenString)
                    case .failed(let error):
                        failure(error)
                    case .cancelled:
                        cancelled()
                    }
                }
            }
        }
    }

    func logout(viewController: UIViewController, call: CAPPluginCall) -> Bool {
        let loginManager = LoginManager()
        loginManager.logOut()
        return true
    }
}

This handler will be automatically discovered up by the plugin and handles the login using the Facebook SDK. See https://developers.facebook.com/docs/swift/login/#custom-login-button for details.

  1. The users that have redirect problem after success grant add the following code to ios/App/App/AppDelegate.swift. This code correctly delegate the FB redirect url to be managed by Facebook SDK.
import UIKit
import FacebookCore
import FacebookLogin
import Capacitor

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    // other methods

    func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
      // Called when the app was launched with a url. Feel free to add additional processing here,
      // but if you want the App API to support tracking app url opens, make sure to keep this call

      if let scheme = url.scheme, let host = url.host {
        let appId: String = Settings.appID!
        if scheme == "fb\(appId)" && host == "authorize" {
          return ApplicationDelegate.shared.application(app, open: url, options: options)
        }
      }

      return CAPBridge.handleOpenUrl(url, options)
    }

    // other methods
}

Contribute

See Contribution Guidelines.

Changelog

See CHANGELOG.

License

MIT. See LICENSE.

BYTEOWLS Software & Consulting

This plugin is powered by BYTEOWLS Software & Consulting.

If you need extended support for this project like critical changes or releases ahead of schedule. Feel free to contact us for a consulting offer.

Disclaimer

We have no business relation to Ionic.

More Repositories

1

sqlite

Community plugin for native & electron SQLite databases
Swift
463
star
2

barcode-scanner

A fast and efficient (QR) barcode scanner for Capacitor
Java
435
star
3

electron

Deploy your Capacitor apps to Linux, Mac, and Windows desktops, with the Electron platform! 🖥️
TypeScript
327
star
4

bluetooth-le

Capacitor plugin for Bluetooth Low Energy
TypeScript
277
star
5

react-hooks

⚡️ React hooks for Capacitor ⚡️
TypeScript
244
star
6

fcm

Enable Firebase Cloud Messaging for Capacitor apps
TypeScript
240
star
7

admob

Community plugin for using Google AdMob
Java
209
star
8

http

Community plugin for native HTTP
Java
209
star
9

camera-preview

Capacitor plugin that allows camera interaction from HTML code
Java
188
star
10

stripe

Stripe Mobile SDK wrapper for Capacitor
Java
188
star
11

background-geolocation

A Capacitor plugin that sends you geolocation updates, even while the app is in the background.
Java
187
star
12

google-maps

Capacitor Plugin using native Google Maps SDK for Android and iOS.
Java
152
star
13

in-app-review

Let users rate your app using native review app dialog for both Android and iOS.
TypeScript
144
star
14

apple-sign-in

Sign in with Apple Support
Swift
137
star
15

vue-cli-plugin-capacitor

A Vue CLI 3/4 Plugin for Capacitor
JavaScript
131
star
16

keep-awake

⚡️ Capacitor plugin to prevent devices from dimming or locking the screen.
Java
125
star
17

firebase-analytics

Enable Firebase Analytics for Capacitor Apps
Java
122
star
18

contacts

Contacts Plugin for Capacitor
Java
114
star
19

tauri

Deploy your Capacitor apps to Linux, Mac, and Windows desktops, with the Tauri platform! 🖥️
TypeScript
108
star
20

native-audio

Java
104
star
21

facebook-login

Facebook Login support
Java
101
star
22

media

Capacitor plugin for saving and retrieving photos and videos, and managing photo albums.
TypeScript
100
star
23

text-to-speech

⚡️ Capacitor plugin for synthesizing speech from text.
Java
93
star
24

speech-recognition

Java
84
star
25

date-picker

Native DateTime Picker Plugin for Capacitor Apps
Swift
84
star
26

privacy-screen

⚡️ Capacitor plugin that protects your app from displaying a screenshot in Recents screen/App Switcher.
Swift
77
star
27

proposals

Plugin and platform requests ✋
74
star
28

app-icon

Capacitor plugin to programmatically change the app icon.
Java
74
star
29

firebase-crashlytics

⚡️ Capacitor plugin for Firebase Crashlytics.
Java
70
star
30

file-opener

Capacitor File Opener. The plugin is able to open a file given the mimeType and the file uri. This plugin is similar to cordova-plugin-file-opener2 without installation support.
Swift
64
star
31

intercom

Enable Intercom for Capacitor apps
TypeScript
57
star
32

photoviewer

PhotoViewer table images with fullscreen and sharing capabilities
Swift
49
star
33

examples

Examples of using Capacitor with popular web frameworks and libraries
JavaScript
46
star
34

safe-area

Capacitor Plugin that exposes the safe area insets from the native iOS/Android device to your web project.
Kotlin
46
star
35

welcome

Introduction to the Capacitor Community org 👋
37
star
36

appcenter-sdk-capacitor

Capacitor Plugin for Microsoft's Visual Studio App Center SDK.
TypeScript
35
star
37

in-app-purchases

WIP: In App Purchases plugin for Capacitor
Java
27
star
38

native-market

Java
26
star
39

realm

Java
25
star
40

firebase-remote-config

TypeScript
23
star
41

screen-brightness

Java
23
star
42

.github

Template repo for new community plugins
17
star
43

twitter

Capacitor plugin to enable TwitterKit
TypeScript
11
star
44

card-scanner

Simple card scanner for Capacitor Applications.
Swift
11
star
45

flipper

Java
10
star
46

auth0

TypeScript
9
star
47

volume-buttons

Capacitor Volume Buttons. The plugin enables to listen to hardware volume button presses. This plugin is based on https://github.com/thiagobrez/capacitor-volume-buttons
Swift
7
star
48

google-maps-examples

Vue
5
star
49

uxcam

UXCam and FullStory app analytics
Java
5
star
50

advertising-id

Allows access to the IDFA (iOS) and AAID (Android)
Swift
5
star
51

android-security-provider

Capacitor plugin with method to check and update the Android Security Provider.
Java
3
star
52

tap-jacking

Capacitor plugin to prevent tap jacking on Android devices
Java
2
star
53

mdm-appconfig

Capacitor community plugin for reading app configurations written by a MDM (see appconfig.org) such as VMWare Workspace One.
Java
2
star
54

exif

This plugin offers utility functions for interacting with image exif metadata
Swift
2
star
55

play-integrity

A Capacitor plugin to use the Play Integrity API
Java
1
star
56

device-check

A Capacitor plugin to use Apple's DeviceCheck API
Java
1
star