GithubHelp home page GithubHelp logo

moberwasserlechner / capacitor-oauth2 Goto Github PK

View Code? Open in Web Editor NEW
211.0 17.0 105.0 1.26 MB

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

License: MIT License

Java 45.18% JavaScript 0.29% TypeScript 30.66% Ruby 0.94% Objective-C 0.78% Swift 22.14%
capacitor oauth2 oauth2-client capacitor-plugin social-login authentication

capacitor-oauth2's Introduction

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 v5

npm i @byteowls/capacitor-oauth2
npx cap sync

For Capacitor v4

npm i @byteowls/capacitor-oauth2@4
npx cap sync

For Capacitor v3

npm i @byteowls/capacitor-oauth2@3
npx cap sync

Versions

Plugin For Capacitor Docs Notes
5.x 5.x.x README Breaking changes see Changelog. XCode 14.1 needs this version
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.

Disclaimer

We have no business relation to Ionic.

capacitor-oauth2's People

Contributors

0x4amiller avatar anthbs avatar dejan9393 avatar dennisameling avatar dependabot[bot] avatar doatech avatar jhurley-bd avatar jvartanian avatar macdja38 avatar moberwasserlechner avatar mrbatista avatar natalya-semenova avatar nicksteenstra avatar sanjaywadhwani avatar suchorski avatar svzi avatar vmdominguez avatar webflo avatar ynunez avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

capacitor-oauth2's Issues

Android Google success shows 2 duplicate apps to return to on success

I set up Facebook and Google for Android. Facebook works as expected. However, with Google, after I've selected which account to connect, it then asks me to choose which app to go back to. It lists the correct app, but it lists it twice for some reason. Selecting one completely reopens the app, and so the connection doesn't work. Selecting the other takes you back to the open app and it DOES work.

The screenshot shows it split based on which I had previously selected but the first time it asked me it showed both in a list, both with app.temployee.app as the package name

Do you have any idea what I could be doing wrong?

screen shot 2018-12-20 at 12 47 10 pm

Sign in with Apple

Hi,

Is it possible to make this plugin work with Sign in with Apple, that is expected to come this fall with iOS13?

iOS issues with Okta

I'm trying to use this plugin to add OAuth 2.0 authentication to an Ionic 4 app. I'm using Okta as my provider and I'm able to get "web" login to work. When I try to make it work in iOS Simulator, I get an error. It does redirect back to my app successfully.

Here's the full log from Xcode:

Loading network plugin
2019-04-08 10:51:21.596427-0600 App[28645:712874] CAPKeyboard: resize mode - native
โšก๏ธ  Loading app at capacitor://localhost...
Reachable via WiFi
APP ACTIVE
โšก๏ธ  [log] - Angular is running in the development mode. Call enableProdMode() to enable the production mode.
โšก๏ธ  [log] - Ionic Native: deviceready event fired after 286 ms
โšก๏ธ  [warn] - Native: tried calling StatusBar.styleDefault, but the StatusBar plugin is not installed.
โšก๏ธ  [warn] - Install the StatusBar plugin: 'ionic cordova plugin add cordova-plugin-statusbar'
โšก๏ธ  [warn] - Native: tried calling SplashScreen.hide, but the SplashScreen plugin is not installed.
โšก๏ธ  [warn] - Install the SplashScreen plugin: 'ionic cordova plugin add cordova-plugin-splashscreen'
โšก๏ธ  [log] - Register custom capacitor plugins
โšก๏ธ  To Native ->  App addListener 100427320
โšก๏ธ  WebView loaded
SplashScreen.hideSplash: SplashScreen was automatically hidden after default timeout. You should call `SplashScreen.hide()` as soon as your web app is loaded (or increase the timeout). Read more at https://capacitor.ionicframework.com/docs/apis/splash-screen/#hiding-the-splash-screen
โšก๏ธ  To Native ->  OAuth2Client authenticate 100427321
2019-04-08 10:51:27.030836-0600 App[28645:712874] [MC] System group container for systemgroup.com.apple.configurationprofiles path is /Users/mraible/Library/Developer/CoreSimulator/Devices/5E87F505-456A-44B3-A682-9E1943F56211/data/Containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles
2019-04-08 10:51:27.031220-0600 App[28645:712874] [MC] Reading from private effective user settings.
2019-04-08 10:51:34.804082-0600 App[28645:712956] [BoringSSL] nw_protocol_boringssl_get_output_frames(1301) [C1.1:2][0x7fc75e501000] get output frames failed, state 8196
2019-04-08 10:51:34.804220-0600 App[28645:712956] [BoringSSL] nw_protocol_boringssl_get_output_frames(1301) [C1.1:2][0x7fc75e501000] get output frames failed, state 8196
2019-04-08 10:51:34.804645-0600 App[28645:712956] TIC Read Status [1:0x0]: 1:57
2019-04-08 10:51:34.804785-0600 App[28645:712956] TIC Read Status [1:0x0]: 1:57
@byteowls/capacitor-oauth2: Access resource request failed with The operation couldnโ€™t be completed. (OAuthSwiftError error -11.).
ERROR MESSAGE:  {"errorMessage":"","message":"ERR_GENERAL"}
2019-04-08 10:51:35.123647-0600 App[28645:713548] [BoringSSL] nw_protocol_boringssl_get_output_frames(1301) [C2.1:2][0x7fc7ce409c60] get output frames failed, state 8196
2019-04-08 10:51:35.123779-0600 App[28645:713548] [BoringSSL] nw_protocol_boringssl_get_output_frames(1301) [C2.1:2][0x7fc7ce409c60] get output frames failed, state 8196
โšก๏ธ  [error] - OAuth rejected {"errorMessage":"","message":"ERR_GENERAL"}
2019-04-08 10:51:35.124128-0600 App[28645:713548] TIC Read Status [2:0x0]: 1:57
2019-04-08 10:51:35.124256-0600 App[28645:713548] TIC Read Status [2:0x0]: 1:57

I've published my app to https://github.com/mraible/ionic-4-oauth2. You should be able to login with demo/Password1 as credentials.

I have some questions after making this attempt:

  1. Is it possible to use PKCE with "web" login?
  2. Would it be possible to use OIDC discovery so a developer can specify issuer instead of authorizationBaseUrl, accessTokenEndpoint, and resourceUrl? With OIDC discovery, these values can be looked up. For example, you can find the values for my Okta tenant at https://dev-737523.oktapreview.com/oauth2/default/.well-known/openid-configuration

In Ionic 3, I used https://github.com/manfredsteyer/angular-oauth2-oidc, and wrote a blog post to explain how I did it. I'm hoping to do the same for Ionic 4.

Android ERR_STATES_NOT_MATCH

Hi

Ionic:

ionic: 4.12.0
@ionic-native/core: ^5.0.0
@ionic/angular: ^4.1.0

Capacitor:

@capacitor/android: ^1.0.0-beta.22
@capacitor/cli: 1.0.0-beta.22
@capacitor/core: 1.0.0-beta.22

System:

node : v10.15.3
npm : 6.9.0
OS : Deepin 15.9.3

Error

I've got an error ERR_STATES_NOT_MATCH on Android, but on Browser everything works as expected. In Browser I see an URL like

localhost:8100/#access_token=...&expires_in=...&token_type=Bearer&state=SAME_AS_IN_REQUEST

And everything is works

Detect when user cancels authentication

It is possible for the user to close the popup window and not authenticate, this results in a failed login attempt without any means to catch it.

The workaround that I have found is to set an interval and watch the windowHandle.close of OAuth2Client class, but this property is not public meaning that in TypeScript I have to extend the interface.

It's quite a bit of extra work and could easily be part of the source.

Base-64 Plugin is not browser native/compatible without build step

Hey Michael, I hope all is well, I have a pretty urgent request that will need to be addressed to continue to allow Capacitor to work out of the box for browsers.

Import * as base64 from 'base64-js' uses UMD exports and causes the browser to throw an error when ran natively. This is found in your web-utils.js file.

I would recommend using This Base64 package from NPM which will allow you to use native es6 imports and then leave it up to the end user to transpile/build. It will also support code splitting/tree shaking to ensure that you only include what you use from the package.

It also is one of the most widely used Base64 packages on NPM at around 5 million per week downloads.

I can gladly make a PR using the more up to date base64 package. Please let me know how we can move forward on this as quickly as possible or how I can help in any way.

All the best,
-Tsavo

Screen Shot 2019-03-11 at 3 13 34 PM

Screen Shot 2019-03-11 at 3 13 26 PM

Add function for silently refreshing refresh token

We'd like to let our users log in with the code flow + PKCE, then store the refresh token as well client-side (only for the Android/iOS version), in our case from Azure AD B2C. In short, we are looking for the following behavior:

  • Android/iOS version: code flow + PKCE, with refresh token stored using the Capacitor Secure Storage Plugin
  • PWA version: implicit flow (for now, see this article by Auth0 for details as to why it's still not considered safe to store a refresh token from the implicit flow in the browser)

Would be great if we could add a function to this library called refresh() or similar which gets a fresh access token from the server (no iFrames/etc. needed). See this Microsoft Azure B2C example for refreshing the access token.

I think it would be enough if this library provided a function refresh(refresh_token: string) {} to get a new access token based on the given refresh token. Combined with Capacitor's background fetch operations (coming soon), the token can be refreshed at regular intervals, so that the user doesn't have to log in over and over again.

One major point to emphasize here, is that this should only be done with the Android/iOS version in combination with the Secure Storage Plugin because we can't safely store refresh tokens securely in the browser (yet).

I'm happy to provide a PR for this, but would like to ask what you think of this approach and if you think it's feasible and secure enough.

Support Authorization Code Flow with PKCE

The web/pwa implementation retrieves the token by using response_type=token on the authorization request.

Thats differs how its done in Android.

Within this task I will use response_type=code with a code_challenge in the web implementation as well.

Plugin does not send resource url response to app (resolves the promise) after specific steps

On Android I have a weird problem:

  1. Login using Google as provider with the plugins standard features. Login successful. โœ”๏ธ
  2. Logout. Logout successful. โœ”๏ธ
  3. Login again using Google oauth with the plugins standard features. Login fails. โŒ

The login does not fail because the the authentification fails. The flow is successfully executed and the response from the resource url queried but the when the plugin calls call.resolve(response) it is not passed to my application code.

I have no error or warning. The promise is simply not resolved. If I restart the app the first login works but the problem persists.

On the web and on IOS there is no problem.

System Information:

  • Capacitor: beta.17
  • Plugin: beta.2
  • Framework: Angular 6
  • OS: Android 8.0

OAuth2Client.authenticate() not working on Android - native plugin code not called?

Setup:

  • Ionic 5.2.3
  • Capacitor 1.1.1
  • Capacitor OAuth 2 client plugin 1.0.0

Scenario:

  • React app
  • Added Capacitor
  • Added iOS and Android platforms
  • Configured OAuth 2 client in the app (in Android: edited MainActivity)
  • Created an app on Azure B2C, obtained Client ID and all necessary parameters

On iOS and web:

  • Calling โ€œOAuth2Client.authenticate(authOptions)โ€ launches a successful flow where I am able to grab the token from the callback (Web) or from the custom app scheme after the app is launched (iOS).

On iOS, the link opens in an external browser window, but the redirect correctly points to the app itself (as the custom scheme is register) so I am able to get the token from the URLs callback.

On Android I encounter the following problem::

  • Calling โ€œOAuth2Client.authenticate(authOptions)โ€ opens the authentication page in the app itself (not in the browser), and once I enter the correct username and password I am prompted for a redirect to Chrome, where the โ€œBad requestโ€ error is shown.

image

I observed that the native code of the plugin is not executed on Android (breakpoints not reached) and I am concerned this could be the cause, but the MainActivity seems properly configured so I do not know where else to look:

public class MainActivity extends BridgeActivity {
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Initializes the Bridge
    this.init(savedInstanceState, new ArrayList<Class<? extends Plugin>>() {{
      // Additional plugins you've installed go here
      // Ex: add(TotallyAwesomePlugin.class);
      add(OAuth2ClientPlugin.class);
    }});
  }
}

Note: this issue looks similar to #47 but opening a new issue for clarity and also because using different Id provider.

Any idea on what I could be doing wrong/missing?

iOS customHandlerClass example from README

Hey Michael, first of all thank you for creating and maintaining this great plugin!

I was following the docs to create the iOS Facebook login and had problems with the YourIOsFacebookOAuth2Handler class. I got following two issues and did some changes to make them work again:

Value of type 'AccessToken' has no member 'authenticationToken':

...
if let accessToken = AccessToken.current {
    success(accessToken.authenticationToken)
} else {
...

changed to

if let accessToken = AccessToken.current {
    success(accessToken.tokenString)
} else {
...

Use of unresolved identifier 'ReadPermission'

...
self.loginManager!.logIn(readPermissions: [ ReadPermission.publicProfile ],
...

changed to

self.loginManager!.logIn(permissions: [ Permission.publicProfile ],

I'm new to Capacitor, I'm new to swift. If I'd be more confident in it would have turned into a PR right away. ๐Ÿ™ˆ
What do you think?

Logout feature

New method for handling Logout especially for customHandlerClass (Facebook).

Because the SDK caches the access token.

Handle the storing of "access token" in the plugin

I introduce a option to disable this but by default this should be handled by the plugin. Although I thougth in #3 that it is not required.

But if the plugin handles the access_token as well. Authentication and access should be faster. The alternative would be to hand the access_token to the plugin as the client user stores it.

Issue authenticating with Auht0 via the iOS plugin

  • xcode 10.2.1
  • swift 4.2
  • angular 7.2.2
  • ionic 4.4.2
  • capacitor 1.0.0-beta.19

So I've been able to prove out authenticating with Auth0 via the web plugin (although there's a CORS issue with the request headers, I'll create a seperate issue for that) however, when I go to authenticate with the iOS plugin it's throwing the following error...

To Native ->  OAuth2Client authenticate 50216884
2019-06-21 09:59:34.058638+1200 App[870:157200] [MC] System group container for systemgroup.com.apple.configurationprofiles path is /private/var/containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles
2019-06-21 09:59:34.064168+1200 App[870:157200] [MC] Reading from public effective user settings.
2019-06-21 09:59:37.683308+1200 App[870:157302] [BoringSSL] nw_protocol_boringssl_get_output_frames(1301) [C1.1:2][0x100e3f060] get output frames failed, state 8196
2019-06-21 09:59:37.684177+1200 App[870:157302] [BoringSSL] nw_protocol_boringssl_get_output_frames(1301) [C1.1:2][0x100e3f060] get output frames failed, state 8196
2019-06-21 09:59:37.685975+1200 App[870:157302] TIC Read Status [1:0x0]: 1:57
2019-06-21 09:59:37.686141+1200 App[870:157302] TIC Read Status [1:0x0]: 1:57

The promise from the authenticate call doesn't ever seem to resolve/reject and I'm at a bit of a loss to figure out the issue. Any ideas what I might be dealing with here? I can provide authentication options if needed.

Android: App crashes if Intent data is null

OAuth2ClientPlugin.java line 222 < response = AuthorizationResponse.fromIntent(data);>
Will crash the application when data is null.

When chrome custom tabs(used by AppAuth) redirect back to the application using the uri scheme, the data provided to handleOnActivityResult can be null.
Passing this to the AuthorizationReponse.fromIntent causes the application to crash.

Edit:
The same sort of issue can be found here

Recommended flow for getting refresh_token for server side usage

In our app a significant portion of the flow will involve the server taking action on behalf of the user, to accomplish this we need to get a refresh token when the user logs in, and be able to put it on the server. What's the recommended flow using this library to do something like that?

I've gotten the access token locally on the device, but I'm unsure of how to get the refresh token.

So far I've been thinking of changing the customScheme to be a URL on the remote server then having the user login using that, then redirecting back to the app by redirecting to the actual local customScheme URL once that's done. Not sure what to do to pass the cookie from the server / some other auth method to the actual app code at that point though.

Google-Login with capacitor-oauth2/Plugins.OAuth2Client.authenticate () does not work on Android device

Initial Situation:

  • I need to implement a Google login with Ionic which works on a web platform as well as on an android device.

Therefore I use:

  • Ionic 5.2.2
  • Capacitor 1.1.1
  • Capacitor OAuth 2 client plugin 1.0.0

With that setup I achieved already:

  • Web-Login workes perfectly

Problem:

  • Login in from an Android device doesn't work

I followed the steps in the readme from https://github.com/moberwasserlechner/capacitor-oauth2/blob/master/README.md

  • I registered the plugin OAuth2Client in my app.component.ts
  • I implemented a method googleLogin() where I call Plugins.OAuth2Client.authenticate() with OAuth2AuthenticateOptions

app.component.ts

import { Component, OnInit } from '@angular/core';

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

@Component({
  selector: 'app-root',
  templateUrl: 'app.component.html'
})
export class AppComponent implements OnInit {
  constructor() {}

  ngOnInit() {
    console.log("Register custom capacitor plugins");
    registerWebPlugin(OAuth2Client);
  }
}

home.page.ts

import { Component } from '@angular/core';
import { Plugins } from '@capacitor/core';

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})
export class HomePage {

  constructor() { }

  async googleLogin() {

    try {
      const resourceUrlResponse = await Plugins.OAuth2Client.authenticate({
        appId: "XXX.apps.googleusercontent.com",
        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: {
          redirectUrl: "http://localhost:8100",
          windowOptions: "height=600,left=0,top=0"
        },
        android: {
          appId: "XXX.apps.googleusercontent.com",
          responseType: "code",
          customScheme: "com.xxx.playground.googleLogin07"
        }
      })
    }
    catch (err) {
      console.error(err);
    }
  }

}

On an device this code results in an error-message from Google:

googleError

This is plausible. It seems to be that the method Plugins.OAuth2Client.authenticate() tries to do a web-based login where an android login is needed. Am I right?

If I make a call without the "web"-parameter like this...

const resourceUrlResponse = await Plugins.OAuth2Client.authenticate({
        appId: "XXX.apps.googleusercontent.com",
        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",
        android: {
          appId: "XXX.apps.googleusercontent.com", //--> I tried both, android- and web-client key from the google clout platform console.
          responseType: "code",
          customScheme: "com.xxx.playground.googleLogin07"
        }
      })

...the method Plugins.OAuth2Client.authenticate() returns a blank error object --> {}

What am I doing wrong?

Check if iOS implizit flow can handle url parameter starting with hash

As stated in #31 Microsoft (Azure AD) supports implicit flow for iOS but the access token is returned as url parameter starting with # instead of ?

On Webservers # url params get removed because they are client side only but on the native app it should/could work.

See https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-implicit-grant-flow#get-access-tokens for details.

In the browser impl of this plugin # started url params are supported see

let idx = urlString.indexOf("#");

I created a new task because the user closed the other one.

Integrating LINE Login?

Hi,

Have you considered integrating LINE messenger login flow?
LINE is the extremely popular messenger app in Japan and South East Asia (e.g. Thailand). Curious how difficult would be to make it work with Capacitor on iOS and Android...

"id_token token" as responseType

I am currenty working on a mobile app for both ios and android, which is going to need oauth for its authoriztion.

While registering client-ids in our backend and implementing this plugin I came across an issue, the plugin doesn't seem to support "id_token token" as valid responseType, though our service does expect this repsonseType.

According to the AppAuth documentation, the sdk's also support OpenID Connect, which specifies the usage of alternative responseTypes such as "id_token token" and "id_token code".

Would it be possible for this plugin to also support these specs / responseTypes? or atleast the "id_token token" as a first step to supporting more of the OpenID Connect extentions?

extras:
Linkto openId connect specs
AppAuth stating it supports OpenID Connect.
oauth 2.0 on Extensibility

Error Java Compiler

Hello I have the following problem:
error: cannot access AppCompatActivity
class file for android.support.v7.app.AppCompatActivity not found
File: OAuth2ClientPlugin.java

Ionic:

ionic (Ionic CLI) : 4.12.0
Ionic Framework : @ionic/angular 4.3.0
@angular-devkit/build-angular : 0.13.8
@angular-devkit/schematics : 7.1.4
@angular/cli : 7.1.4
@ionic/angular-toolkit : 1.2.3

Capacitor:

capacitor (Capacitor CLI) : 1.0.0-beta.19
@capacitor/core : 1.0.0-beta.19

Cordova:

cordova (Cordova CLI) : not installed
Cordova Platforms : not available
Cordova Plugins : not available

System:

NodeJS : v10.15.3 (C:\Program Files\nodejs\node.exe)
npm : 6.4.1
OS : Windows 10

More info:
Wrong 1st argument type. Found: 'android.support.v7.app.AppCompatActivity', required: 'android.app.Activity'

In this line: handler.getAccessToken(getActivity(), call, new AccessTokenCallback()

Thanks

Issues while using manifestPlaceholders on Android

Hi there!
Great job with this library, been using it and I love it. However, I've been having some issues with the docs on manifestPlaceholders (Google implementation).

When implementing it in default it wouldn't parse or it would get replaced. I end up with an error and can't get over it. I found this comment openid/AppAuth-Android#323 (comment) that explained it.

Can you help me?

Can I put that string inside the AndroidManifest.xml somehow?

Support OpenId

Check if and how this plugin, which was intended to only support OAuth2 is extendable for OpenId.

[iOS] file ios/App/public/cordova.js does not exist

Installing the plugin and running npx cap update works well for all platforms except iOS:

โœ– update ios: ENOENT: no such file or directory, open 'ios/App/public/cordova.js'
[error] Error running update: { [Error: ENOENT: no such file or directory, open 'ios/App/public/cordova.js']
  errno: -2,
  code: 'ENOENT',
  syscall: 'open',
  path: 'ios/App/public/cordova.js' }

Argument of type 'OAuth2ClientPluginWeb' is not assignable to parameter of type 'WebPlugin'

Hello at the time of writing
registerWebPlugin(OAuth2Client);
Get the following message:
Argument of type 'OAuth2ClientPluginWeb' is not assignable to parameter of type 'WebPlugin'.
Types have separate declarations of a private property 'addWindowListener'.

Ionic:

ionic (Ionic CLI) : 4.9.0 (...AppData\Roaming\nvm\v11.8.0\node_modules\ionic)
Ionic Framework : @ionic/angular 4.0.0-rc.1
@angular-devkit/build-angular : 0.12.1
@angular-devkit/schematics : 7.1.4
@angular/cli : 7.1.4
@ionic/angular-toolkit : 1.2.2

Capacitor:

capacitor (Capacitor CLI) : 1.0.0-beta.17
@capacitor/core : 1.0.0-beta.17

Cordova:

cordova (Cordova CLI) : not installed
Cordova Platforms : not available
Cordova Plugins : not available

System:

NodeJS : v11.8.0 (C:\Program Files\nodejs\node.exe)
npm : 6.5.0
OS : Windows 10

missing AccessToken response (IOS)

Hi
could really use some help , using capacitor with capacitor-oauth2 and VueJS

I am trying to get an accessToken from AzureAD

it works fine on web, but not on IOS

I get an info message in console with

To Native -> OAuth2Client authenticate 127389189

the Token its returned from native project I am not receiving it back
Screenshot 2019-03-13 at 19 21 06

here is my config

const config = {

  additionalParameters: {

    'prompt':'login',

    'login_hint':'[email protected]',

    'nonce': 'jahsdjahsdk',

  }

  authorizationBaseUrl: "https://login.microsoftonline.com/{tenant id}/oauth2/v2.0/authorize",

  pkceDisabled: true,

  appId: '777777-777777-777777-77777',

  scope: "user.readwrite",

  accessTokenEndpoint: "https://login.microsoftonline.com/{tenant id}/oauth2/v2.0/token",
                                                                                                                  
                                                                                                                  
  web: {                                                                                                          
    redirectUrl: "http://localhost:8080/start",                                                                   
    windowOptions: "height=600,left=0,top=0",                                                                     
    responseType: "token",                                                                                        
  },                                                                                                                                                                                                                           
  ios: {                                                                                                                                          
    appId: '777777-777777-7777-7777-77777777',                                                                
    customScheme: "capacitor://localhost",                               
    responseType: "token"                                                                                         
  }                                                                                                               
};                                                                                                                ` 

  login(){                                                                              
   return  await this.OAuth2Client.authenticate(                                        
      config                                                                            
    ).then(resourceUrlResponse => {                                                     
      let accessToken = resourceUrlResponse["access_token"];                            
                                                                                             
      return accessToken;                                                               
                                                                                        
    }).catch(reason => {                                                                
      console.error("OAuth rejected", reason);                                          
    });                                                                                 
                                                                                        
  }

Thanks for the support !

/Ibrahim

Facebook (iOS): documentation issue?

Hi,

With the provided documentation for Facebook, I always get "Login cancelled by user". Don't we have to add this code:

if let scheme = url.scheme, let host = url.host {
  if scheme == "fb\(SDKSettings.appId)" && host == "authorize" {
    return SDKApplicationDelegate.shared.application(app, open: url, options: options)
  }
}

to the

func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool { }

in AppDelegate.swift?

For me, this fixed the issue...

Code + PKCE

Thanks for a great plugin! In our situation we need to provide oAuth flow with code, but we can't store client_id and secrets on the client app. We are using Java spring oauth on the backend, all we need to use is Capacitor Browser plugin. I was able to get everything working but couldn't close the Browser upon browserPageLoaded event upon looking for specific client side redirect url. What I get from this event is empty.

I understand this is not the forum to ask this question, since we are on the oAuth topic, any direction would be highly appreciated. We need to get this accomplished both on ios, android and web.

Thanks in advance

IdToken is not available when doing Google OAuth

I am trying to migrate from cordova-plugin-googleplus. In the previous plugin the response I was getting from logging in with google included an idToken which I sent to my server and obtained a refresh_token and the issuer additionally to all the other info I am getting now if I sent just the access_token that is returned from using this plugin.

Plugins.OAuth2Client.authenticate(
      {
        appId: this._appConfig.thirdPartyAPIs.googleOAuth.clientId,
        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: {
          redirectUrl: 'http://localhost:4200',
          windowOptions: 'height=600,left=0,top=0'
        },
        android: {
          appId: 'xxxx.apps.googleusercontent.com',
          responseType: 'code', // if you configured a android app in google dev console the value must be "code"
          customScheme: 'com.example.android:/'
        },
        ios: {
          appId: 'xxxx.apps.googleusercontent.com',
          responseType: 'code',
          customScheme: 'com.example.app:/'
        }
      }
    )

The response I get contains these fields:

  email: string;
  name: string;
  family_name: string;
  given_name: string;
  verified_email: boolean;
  picture: string;
  locale: string;
  id: string;
  access_token: string;

Low-level support in Capacitor

Hello!

Just curious, would it be helpful to this plugin if Capacitor would support SFAuthenticationSession and Chrome Custom Tabs, as a part of their Browser API?

I'm thinking those two would be good additions to the Capacitor Browser API.

Capacitor version compatibility?

I am trying to use [email protected], but I could not find documentation about which version of capacitor it is compatible with. Could you clarify?

I keep getting the issue #36, and I have tried the following capacitor versions: beta.17, beta.19, 1.0.0 but the issue persists.

Android crash with NullPointerException

In google console I see a handful of crashes with the following stacktrace attached

Caused by: java.lang.NullPointerException: 
 
  at com.byteowls.capacitor.oauth2.OAuth2ClientPlugin.handleOnActivityResult (OAuth2ClientPlugin.java:224)
  at com.getcapacitor.Bridge.onActivityResult (Bridge.java:748)
  at com.getcapacitor.BridgeActivity.onActivityResult (BridgeActivity.java:209)
  at android.app.Activity.dispatchActivityResult (Activity.java:7634)
  at android.app.ActivityThread.deliverResults (ActivityThread.java:4622)

Any idea what might be causing this?

Using version 1.0.1 with the following invocation:

return from(Plugins.OAuth2Client.authenticate(
      {
        appId: this._appConfig.thirdPartyAPIs.googleOAuth.clientId.web,
        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: {
          redirectUrl: 'http://localhost:4200',
          windowOptions: 'height=600,left=0,top=0'
        },
        android: {
          appId: this._appConfig.thirdPartyAPIs.googleOAuth.clientId.android,
          responseType: 'code', 
          customScheme: 'com.myapp.android:/'
        },
        ios: {
          appId: this._appConfig.thirdPartyAPIs.googleOAuth.clientId.ios,
          responseType: 'code', 
          customScheme: 'com.myapp.myapp:/'
        }
      }
    )

Full Screen On Web Issue Redirecting

Full-Screen MacOS Redirect "Not Found" Error.

Hello Michael! Tsavo Here,

I released a beta out yesterday to a few test groups and they seem to be running into an issue in fullscreen mode on OSX, which also seems to be an issue in the browser on Mobile for iOS and Android.

Error: Error: Not found at intervalId.setInterval
File: http://localhost:8081/node_modules/@byteowls/capacitor-oauth2/dist/esm/web.js

I am trying to do further debugging but it seems to be a vague error. I believe it is due to that fact that in fullscreen mode the redirect launches a new tab whereas in a non-fullscreen window the redirect creates a new window allowing for proper resolution back to the previous window.

Do you have any initial thoughts on this/have you encountered this situation?

Additionally, is there any way that we can further and formally collaborate on this. Am more than willing to help fund development/help develop myself.

Please let me know what I can do to help with this process, much appreciated - Tsavo Knott

Attached is a screenshot of the two types of views I am describing.

Full Window: "Blue dot thrown on Origin Tab"
screen shot 2019-02-06 at 7 06 26 am

This is the redirected Tab.

screen shot 2019-02-06 at 7 06 26 am

Final Console Log:

screen shot 2019-02-06 at 7 06 42 am

Save authState

Web: localStorage or cookie
Android: shared preferences

AppAuth leak message causes Capapcitor reload and failed authentication

MainActivity has leaked ServiceConnection net.openid.appauth.browser.CustomTabManager$1@2f048d4 that was originally bound here
                  android.app.ServiceConnectionLeaked: Activity com.byteowls.teamconductor.MainActivity has leaked ServiceConnection net.openid.appauth.browser.CustomTabManager$1@2f048d4 that was originally bound here
                      at android.app.LoadedApk$ServiceDispatcher.<init>(LoadedApk.java:1336)
                      at android.app.LoadedApk.getServiceDispatcher(LoadedApk.java:1231)
                      at android.app.ContextImpl.bindServiceCommon(ContextImpl.java:1450)
                      at android.app.ContextImpl.bindService(ContextImpl.java:1422)
                      at android.content.ContextWrapper.bindService(ContextWrapper.java:636)
                      at android.support.customtabs.CustomTabsClient.bindCustomTabsService(CustomTabsClient.java:71)
                      at net.openid.appauth.browser.CustomTabManager.bind(CustomTabManager.java:95)
                      at net.openid.appauth.AuthorizationService.<init>(AuthorizationService.java:116)
                      at net.openid.appauth.AuthorizationService.<init>(AuthorizationService.java:94)
                      at net.openid.appauth.AuthorizationService.<init>(AuthorizationService.java:83)
                      at com.byteowls.capacitor.oauth2.OAuth2ClientPlugin.authenticate(OAuth2ClientPlugin.java:145)
                      at java.lang.reflect.Method.invoke(Native Method)
                      at com.getcapacitor.PluginHandle.invoke(PluginHandle.java:99)
                      at com.getcapacitor.Bridge$2.run(Bridge.java:473)
                      at android.os.Handler.handleCallback(Handler.java:751)
                      at android.os.Handler.dispatchMessage(Handler.java:95)
                      at android.os.Looper.loop(Looper.java:154)
                      at android.os.HandlerThread.run(HandlerThread.java:61)

Causes the App to reload / restart Capacitor

Get only authorizationCode on iOS

I need to support cloud storage providers like OneDrive, Google Drive, DropBox and so on.

The actual access to resources is handled by the backend, so only the authentication is done by to user on the client this results in the authorizationCode, which is sent to the backend. The backend performs the retrieval of accessToken and most important the refreshToken

Unfortunately Code Flow with PKCE is no option because various OAuth providers support only native apps for this flow and web apps/PWAs are forced to use Implizit Flow. I need the refreshToken to access resources without the users interaction.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.