capacitor-community/generic-oauth2

Azure B2C

fredbjork opened this issue · 24 comments

Description

I'm trying to use this plugin with Azure B2C in my Angular project, but haven't been able to make it work.

If anyone here has done this already, some help would be much appreciated.

I get sent to the Azure Policy where I can sign in, but on redirect I get 2 types of errors:

  1. If I set responseType: "code token":
    ERR_GENERAL: See client logs. It might be CORS. Status text:
    "Access to XMLHttpRequest at 'https://{tenant}b2c.b2clogin.com/{tenant}b2c.onmicrosoft.com/oauth2/v2.0/token' from origin 'http://localhost:8100' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource."

  2. If I set responseType: "token":
    "OAuth rejected Error: ERR_NO_AUTHORIZATION_CODE"

Capacitor version:

@capacitor/cli 2.0.1
@capacitor/ios 2.0.1
@capacitor/core 2.0.1

Library version:

  • 2.0.0

OAuth Provider:

  • Azure AD
  • Azure App Registration

Your Plugin Configuration

{
    appId: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    authorizationBaseUrl: "https://{tenant}b2c.b2clogin.com/{tenant}b2c.onmicrosoft.com/oauth2/v2.0/authorize",
    accessTokenEndpoint: "https://{tenant}b2c.b2clogin.com/{tenant}b2c.onmicrosoft.com/oauth2/v2.0/token",    
//"https://{tenant}b2c.b2clogin.com/{tenant}b2c.onmicrosoft.com/B2C_1_SignUpAndSignIn/oauth2/v2.0/{authorize/token}",
    scope: "openid https://{tenant}b2c.onmicrosoft.com/capacitor-api/demo.read",
    responseType: "code token",
    web: {
        redirectUrl: "http://localhost:8100/auth",
    },
    ios: {
        responseType: "code token",
        redirectUrl: "msalxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx://auth"
    },
    additionalParameters: {
      p: "B2C_1_B2C_1_SignUpAndSignIn",
      response_mode: "query"
    }
}

Thanks!

Hi,
We have a B2B app and this is the config we used to connect to Microsoft AD
{
authorizationBaseUrl:
"https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
accessTokenEndpoint: "http://localhost:8100/",
scope:
"User.ReadBasic.All Calendars.Read Calendars.ReadWrite Contacts.Read",
resourceUrl: "http://localhost:8100/",
web: {
appId: "",
responseType: "token",
accessTokenEndpoint: "", // clear the tokenEndpoint as we know that implicit flow gets the accessToken from the authorizationRequest
redirectUrl: "http://localhost:8100/",
windowOptions: "height=600,left=0,top=0",
},
android: {
appId: "",
responseType: "code", // if you configured a android app in google dev console the value must be "code"
redirectUrl:
"", // package name from google dev console
},
ios: {
appId: "",
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
},
};

Thanks, it works on the web when I clear the accessTokenEndpoint.

I now have the following config:

appId: "xxxxxx-xxxx-xxxxx-xxx-xxxxxxxx",
authorizationBaseUrl: "https://tenant.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: {
        redirectUrl: "msalxxxxxxx-xxxxx-xxx-xxxx-xxxxxxxx://auth"   
},
ios: {
        redirectUrl: "msauth.com.xxxxx://auth",
        resourceUrl: "https://graph.microsoft.com/"
}

I now get the following errors when redirected back to the apps:

Android:
D/Capacitor: Sending plugin error: {"save":false,"callbackId":"53281380","pluginId":"OAuth2Client","methodName":"authenticate","success":false,"error":{"message":"ERR_ANDROID_NO_INTENT"}}
D/Capacitor: App restarted
D/Capacitor: App started
V/Capacitor/Plugin/App: Notifying listeners for event appUrlOpen
D/Capacitor/Plugin/App: No listeners found for event appUrlOpen
D/Capacitor/Plugin/App: Firing change: true
V/Capacitor/Plugin/App: Notifying listeners for event appStateChange
D/Capacitor/Plugin/App: No listeners found for event appStateChange
D/Capacitor: App resumed
D/EGL_emulation: eglMakeCurrent: 0xe5646560: ver 3 0 (tinfo 0xc618b9d0)
D/eglCodecCommon: setVertexArrayObject: set vao to 1 (1) 0 4
D/eglCodecCommon: setVertexArrayObject: set vao to 0 (0) 9 8
E/Capacitor/Console: File: http://localhost/main-es2015.js - Line 1029 - Msg: OAuth rejected
V/Capacitor/Plugin/Network: Notifying listeners for event networkStatusChange
D/Capacitor/Plugin/Network: No listeners found for event networkStatusChange

iOS:
@byteowls/capacitor-oauth2: Invalid json in resource response The data couldn’t be read because it isn’t in the correct format..
ERROR MESSAGE: {"errorMessage":"","message":"ERR_GENERAL"}
⚡️ [error] - OAuth rejected {"errorMessage":"","message":"ERR_GENERAL","line":224,"column":23,"sourceURL":"user-script:2"}

In string.xml:
<string name="custom_url_scheme">msalxxxx-xxxxx-xxxx-xxx-xxxxxxx</string>
AppManifest:
<data android:scheme="@string/custom_url_scheme" android:host="auth">

info.plist:
<string>msauth.com.xxxxxxx</string>

Does anyone have any idea what I should change?

A colleague of mine got IOS working. I haven't got Android working yet but try this config for IOS
ios: {
redirectUrl: "msauth.com.xxxxx://auth",
resourceUrl: "https://graph.microsoft.com/"
pkceEnabled: true,
responseType: "code",
accessTokenEndpoint:
"https://login.microsoftonline.com/xxx/oauth2/v2.0/token",
}
PKCE is used for encryption and is ideally needed both for web and native apps. We don't have resourceUrl though and it worked. There was also I change for xcode plist, I will find out and keep yu posted.
Let me know if you make any progress with android

It worked on ios with your config, thanks.

Still the same error on Android. I'll let you know if I figure it out.

  1. Maybe try those Android specific options, they were introduced because of #52 and #55.
{
   ...
   android: {
      handleResultOnNewIntent = false
      handleResultOnActivityResult = true
   }
}

You have to play with those options. I could not reproduce the issue myself but added these params so developers have some options to work around it.

  1. See Android setup: https://github.com/moberwasserlechner/capacitor-oauth2#platform-android

  2. Which Android versions and devices did you use for testing?

I made it work on Android as well. I created a new Custom redirect URI on my b2c app with the following structure: "com.tenant.app://oauth/auth".

Then added "handleResultOnNewIntent: true" in the config.

Thanks, I have used pixel 2 Android version 9 & 10 for testing
In AndroidManifest.xml, if I have

<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="msauth"
                    android:host="com.xxx" />
</intent-filter>

The control gets redirected to my app but Plugins.OAuth2Client.authenticate(this.oauth2Options) callback is not hit. If I use
<data android:scheme="oauth" android:host="com.xxx" />
the control does not come back to my app

oauth2Options: OAuth2AuthenticateOptions = {
    appId: "xxx",
    authorizationBaseUrl:
      "https://login.microsoftonline.com/xxx/oauth2/v2.0/authorize",
    responseType: "code",
    redirectUrl: "http://localhost:8100",
    scope:
      "openid User.ReadBasic.All Calendars.Read Calendars.ReadWrite Contacts.Read",
    accessTokenEndpoint:
      "https://login.microsoftonline.com/xxx/oauth2/v2.0/token",
    logoutUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/logout",
    pkceEnabled: true,
    web: {
      redirectUrl: "http://localhost:8100",
      accessTokenEndpoint: "",
      responseType: "token",
    },
    android: {
      responseType: "token",
      redirectUrl:
        "msauth://xxx",
      resourceUrl: "https://graph.microsoft.com/",
      handleResultOnNewIntent: true,
      handleResultOnActivityResult: false,
    },
    ios: {
      redirectUrl: "xxx",
    },
  };

is my configuration

Try switching your scheme and host.

<data android:scheme="com.xxx" android:host="oauth" />

Thanks for helping each other. I guess this task is now ready to be closed?

If you could be so nice to post a full config for Azure B2C I'll add it as a example to the readme for others having the same provider.

Almost ready to be closed...so I've followed the steps outlined by these amazing developers in this thread and I finally get the popup, signup/signin and all is wonderful....until.....I either finish signing up or finish signing in and I'm getting the following in my console:
OAuth rejected Error: ERR_NO_ACCESS_TOKEN

It never appears to hit the "then" which I have starting with a log:
).then(response => { console.log("AccessToken: " + response["access_token"]); console.log("RefreshToken: " + response["refresh_token"]); let accessToken = response["access_token"]; this.refreshToken = response["refresh_token"];

but goes right to the catch. What am I missing? Here's my config:
{ authorizationBaseUrl: "https://{{tenant}}.b2clogin.com/{{tenant}}.onmicrosoft.com/B2C_1_csa_signupsignin/oauth2/v2.0/authorize", accessTokenEndpoint: "http://localhost:8100/", scope: "openid offline_access", resourceUrl: "http://localhost:8100/", web: { appId: "{{appId}}", responseType: "token", accessTokenEndpoint: "", // clear the tokenEndpoint as we know that implicit flow gets the accessToken from the authorizationRequest redirectUrl: "http://localhost:8100/home", windowOptions: "height=600,left=0,top=0", }

Make sure your Azure App Config is setup correctly with allowImplicitFlow: true and Grant admin consent for your permissions.

Here's my config for B2C:

    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",
        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.com.tenant://oauth",
        accessTokenEndpoint: "https://tenantb2c.b2clogin.com/tfp/tenantb2c.onmicrosoft.com/B2C_1_SignUpAndSignIn/oauth2/v2.0/token",
    }

@fredbjork - Thanks for replying. It looks as though I have the allow implicit flow set, but unsure about the Grant Admin consent would be located on the Azure ADB2C blade. I'm assuming that is what's tied to the last scope item you have on your reply?

Yes, go to App Reg. (preview) > YourApp > API permissions > Add your permissions and "Grant...".
Pop

@fredbjork - ok I got it, after granting those permissions I was still getting an error. Turns out if I had paid closer attention to your config, I would have noticed you didn't have a few of the items I had in mine. Once I removed those items and made it match yours, it worked like a charm. Thanks again!!

I added the config to the README. @fredbjork thx for sharing.

@fredbjork did you face the issue I have posted #96 ?
Are you able to share a sample of azure b2c working on android and ios? that would be amazing if you could, struggling to make mine to work.

@fredbjork

You said above:

I made it work on Android as well. I created a new Custom redirect URI on my b2c app with the following structure: "com.tenant.app://oauth/auth".

Then added "handleResultOnNewIntent: true" in the config.

How did you add this custom redirect URI. When I add a new URI for the Android Platform, it is locked down. The redirect url always begins with msauth://.

@NishaBhat did you ever get your android version working. I am having the same issue that you described. My config is identical to what you have with the msauth:// and I am getting the exact same behavior you describe where control returns to my app but callback is not hit because of the ERR_ANDROID_NO_INTENT. @fredbjork stated that he solved it by adding a custom redirect URI on his b2c app as I mentioned just above, but I see no way of adding a custom redirect url as he describes. I do have it working on ios. I have tried all of the suggestions in this chain but still can't get it to work.

@moberwasserlechner does the plugin handle the msauth:// android redirectUrl format?

In the azure portal for android the RedirectUrl has to be in the following format:
msauth://com.mydomain.myapp/SIGNATURE_HASH

So in my config it has:
android: { pkceEnabled: true, responseType: "code", redirectUrl: "msauth://com.mydomain.myapp/SIGNATURE_HASH_ENCODED", // THIS IS THE VALUE FROM AZURE accessTokenEndpoint:${this.appSettings.GetMsalTenantAuthorityUri()}/oauth2/v2.0/token, handleResultOnNewIntent: true, handleResultOnActivityResult: false },

And in my AndroidManifest.xml I have:
<data android:scheme="msauth" android:host="com.mydomain.myapp" android:path="/SIGNATURE_HASH_NO_ENCODE" />

Again I get the same exact bahavior as @NishaBhat described.

Thanks,

Hi Billy,
Yes I did get Android working. Let me share my config with you. In the azure portal, for android, the RedirectUrl has to be in the following format:
Package name: com.mydomain.myapp
Signature: <SIGNATURE_HASH>
Redirect URI: msauth://<Package_name>/<SIGNATURE_HASH>
Looks like you have this configured correctly in Azure.But change your redirect URL to com.mydomain.myapp://oauth/auth.
This is my JSON configuration

{
                    "appId" : "<APPID>",
                    "authorizationBaseUrl" : "https://login.microsoftonline.com/<domain>/oauth2/v2.0/authorize",
                    "scope" : "openid profile ...",
                    "accessTokenEndpoint" : "https://login.microsoftonline.com/<domain>/oauth2/v2.0/token",
                    "pkceEnabled" : true,
                    "additionalParameters" : {
                        "nonce" : "1"
                    },
                    "web" : {
                        "redirectUrl" : "https://<REDIRECT_URL>/",
                        "accessTokenEndpoint" : "",
                        "responseType" : "token id_token",
                        "windowTarget" : "_self"
                    },
                    "android" : {
                        "responseType" : "code",
                        "redirectUrl" : "com.mydomain.myapp://oauth/auth",
                        "handleResultOnNewIntent" : true,
                        "handleResultOnActivityResult" : true
                    },
                    "ios" : {
                        "responseType" : "code",
                        "redirectUrl" : "msauth.com.mydomain.myapp://auth"
                    }
                }

Hi @NishaBhat, I changed my redirect uri as you suggested in the android config section but no matter what, when I run the app I get the error in the screen shot below.

Couple of questions:

  1. In Azure, do you have yours redirectUrl as the following: msauth://com.domain.app/SIGNATURE_HASH?

  2. In Android config (above) do you have your redirectUrl set to: "redirectUrl" : "com.domain.app://oauth/auth"?

  3. In AndroidManifest.xml what do you have there? I have:
    <data android:scheme="msauth" android:host="com.domain.app" android:path="/SIGNATURE_HASH" />
    I also tried
    <data android:scheme="msauth" android:host="com.domain.app" />
    and
    <data android:scheme="com.domain.app" android:host="oauth" />

  4. What do you have in your build.gradle file for the manifestPlaceholder?
    I have: manifestPlaceholders = ["appAuthRedirectScheme": "com.domain.app"]

Do you see anything I have misconfigured? I don't see how I can get past the error below using the redirectUrl you suggested, since it is not registered that way in Azure.

Thanks, I really appreciate the help.

image

Hmm, I might know what's wrong. Have you added
com.domain.app://oauth/auth to
Mobile and desktop applications -> Redirect URIs in Azure Portal?

Yes, I do have msauth://com.domain.app/SIGNATURE_HASH as redirect_url in Azure

In AndroidManifest.xml I have

<intent-filter android:autoVerify="true">
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:scheme="com.domain.app" android:host="foo" />
                </intent-filter>

In build.gradel I have
android.defaultConfig.manifestPlaceholders = [
appAuthRedirectScheme:"com.domain.app"
]

Hi @NishaBhat

I really appreciate your help. I think I am close.

I did what you said and setup the com.domain.app://oauth/auth in azure where you said. That did partially work. I now get to the login with no problems and then once I login I am redirected back to my android app. However I get the following error:
ERR_ANDROID_NO_INTENT. My AndroidManifest.xml has

<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="com.domain.app" android:host="oauth"  />
            </intent-filter>

Here is my entire capacitor-oauth2 config:

{
      appId: this.appSettings.GetMsalClientId(),
      authorizationBaseUrl: `${this.appSettings.GetMsalTenantAuthorityUri()}/oauth2/v2.0/authorize`,
      accessTokenEndpoint: "",
      scope: `${this.appSettings.GetMsalClientScope()} offline_access`, 
      responseType: "token",
      web: {
          redirectUrl: this.appSettings.GetLoginRedirectUri()
      },
      android: {
          pkceEnabled: true,
          responseType: "code",
          redirectUrl: "com.domain.app://oauth/auth",
          accessTokenEndpoint: `${this.appSettings.GetMsalTenantAuthorityUri()}/oauth2/v2.0/token`,
          handleResultOnNewIntent: true,
          handleResultOnActivityResult: true
      },
      ios: {
          pkceEnabled: true,
          responseType: "code",
          redirectUrl: "msauth.com.domain.app://auth",
          accessTokenEndpoint: `${this.appSettings.GetMsalTenantAuthorityUri()}/oauth2/v2.0/token`,
      }
    }

I debugged the android code to see what is happening and this is what I found:

The handleAuthorizationRequestActivity gets call 2 times because I have handleResultOnNewIntent and handleResultOnActivityResult set to true.

The first time handleAuthorizationRequestActivity is called, the intent is null, which I think is ok according to the comments. This causes it to call savedCall.reject(ERR_ANDROID_RESULT_NULL) which is defined as ERR_ANDROID_NO_INTENT.

The second time handleAuthorizationRequestActivity is called, the intent has a value. It then tries to create an AuthorizationResponse from the intent by checking to see if the extras on the intent has an extra with the following key: net.openid.appauth.AuthorizationResponse defined by the constant EXTRA_RESPONSE. An Extra with that key is not found so it returns null. Next it checks to see if there is an error on the Intent by checking to see if there is an Extra with the key: net.openid.appauth.AuthorizationException defined by EXTRA_EXCEPTION. The extra does not exist so it returns null for the error.

Since both of these are null, when it next calls this.authState.update(authorizationResponse, error) an exception is thrown stating that one of them should be non-null. Finally it calls savedCall.reject(ERR_GENERAL, e) and control is returned back to the Android app with error(s).

In the dev console I see the 2 errors. The first error, I assume, is from when the intent is null on the first call to handleAuthorizationRequestActivity. The error is ERR_ANDROID_NO_INTENT which seems to be a handled exception and the second which is an unhandled exception ERR_GENERAL created when it could not find the authorizationresult and no error in the extras of the intent.

@billyjacobs2014 Hey could you fix the problem with the Intent, I am really stuck at this point. I debug the app and I see what you told. Did you find a solution?
Thank you :)

HI @NishaBhat @billyjacobs2014 @moberwasserlechner @CarlosDez23 "The control gets redirected to my app but Plugins.OAuth2Client.authenticate(this.oauth2Options) callback is not hit" Facing the same issue. could you guys help me fix it. TIA