Can I use Azure B2C to get an id token *and* get access tokens for my Azure-deployed services?
Asked Answered
R

2

7

I've only been able to figure out how to get an id token using B2C - but then I lose all the benefits of regular AAD apps (specifically access tokens, scopes and user consent)

Below I'll describe a simplified scenario, and what I've tried.

Scenario

Imagine I am developing a client (javascript SPA) and two services (WebAPI):

enter image description here

  • A is a WebAPI-based service with two scopes (ReadA and WriteA), registered and hosted in Azure
  • B is another WebAPI-based service with two scopes (ReadB and WriteB), registered and hosted in Azure
  • C is a Javascript SPA

Now I want the user to sign in using Azure B2C, in a way which yields my client C the following tokens:

  • an id token, so the client C can address me by name
  • an access token for service A, with scopes ReadA and WriteA (issued after the usual user consent)
  • an access token for service B, with scopes ReadB (issued after the usual user consent)

Can this be done? And how? Any examples out there?

All the examples I've been able to find shows one client authenticating the user, and a few show how to get a single access token (no scope support)

What I've tried sofar

My experiments sofar have been carried out using

  • My own B2C tenant registered using recipe Azure Active Directory B2C: Create an Azure AD B2C tenant (with just local accounts for initial simplicity) - let's call it fooplanner.onmicrosoft.com
  • Service A registered and deployed in that tenant using recipe Create an API app in Azure and deploy code to it (with scopes ReadA and WriteA defined) - with application ID AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA
  • Service B registered and deployed in that tenant using recipe Create an API app in Azure and deploy code to it (with scopes ReadB defined) - with application ID BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBBBBBB
  • The application FooPlanner registered with B2C - with ID FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF
  • Client C uses oidc-client.js - I would have used adal.js, but it doesn't suppport B2C (and besides, it's apparently being superceded by MSA, which doesn't even support Javascript yet...)

To avoid confusion relating to client-side libraries, my experiments below will be described in terms of the requests and responses sent and received, as reported by Fiddler.

Single client - ID token only

This is the baseline scenario, shown by most of the how-tos I've found.

Client C sends the following request to B2C:

https://login.microsoftonline.com/fooplanner.onmicrosoft.com/oauth2/v2.0/authorize?
   p=b2c_1_fooplanner-signuporsignin&
   client_id=ffffffff-ffff-ffff-ffff-ffffffffffff&
   response_type=id_token&
   scope=openid email profile

After prompting me for my credentials, B2C then eventually returns a single id_token (where uuu...is the GUID for my user entry in B2C):

id-token:
{
  "ver": "1.0",
  "iss": "https://login.microsoftonline.com/08de3e5f-6a10-4d7c-a0e3-fc4a627a712b/v2.0/",
  "sub": "uuuuuuuu-uuuu-uuuu-uuuu-uuuuuuuuuuuu",
  "aud": "ffffffff-ffff-ffff-ffff-ffffffffffff",
  "oid": "uuuuuuuu-uuuu-uuuu-uuuu-uuuuuuuuuuuu",
  "name": "Thomas"
  "tfp": "B2C_1_fooplanner-signuporsignin"
}

(for brevity, I've omitted all the OAuth2 redirects, the Base64 JWT decoding etc. - I've even left out timestamps, nonces etc from the tokens. If they're relevant, I can supply full details)

This is received and handled as expected by oidc-client.js: I end up with an ID token, and no access token.

Single client - ID token + access token

After a little digging, I found a way to get an access token too: include the B2C application ID in the scopes, and ask for both token and id_token response types.

In this variant, client C sends the following request to B2C:

https://login.microsoftonline.com/fooplanner.onmicrosoft.com/oauth2/v2.0/authorize?
   p=b2c_1_fooplanner-signuporsignin&
   client_id=ffffffff-ffff-ffff-ffff-ffffffffffff&
   response_type=token id_token&
   scope=openid email profile ffffffff-ffff-ffff-ffff-ffffffffffff

B2C then eventually returns an id_token and an access_token:

id_token:
{
  "ver": "1.0",
  "iss": "https://login.microsoftonline.com/08de3e5f-6a10-4d7c-a0e3-fc4a627a712b/v2.0/",
  "sub": "uuuuuuuu-uuuu-uuuu-uuuu-uuuuuuuuuuuu",
  "aud": "ffffffff-ffff-ffff-ffff-ffffffffffff",
  "oid": "uuuuuuuu-uuuu-uuuu-uuuu-uuuuuuuuuuuu",
  "name": "Thomas"
  "tfp": "B2C_1_fooplanner-signuporsignin"
}

access_token: 
{
  "iss": "https://login.microsoftonline.com/08de3e5f-6a10-4d7c-a0e3-fc4a627a712b/v2.0/",
  "aud": "ffffffff-ffff-ffff-ffff-ffffffffffff",
  "oid": "uuuuuuuu-uuuu-uuuu-uuuu-uuuuuuuuuuuu",
  "sub": "uuuuuuuu-uuuu-uuuu-uuuu-uuuuuuuuuuuu",
  "name": "Thomas",
  "tfp": "B2C_1_fooplanner-signuporsignin",
}

This is again received and handled as expected by oidc-client.js: I end up with an ID token and an access token.

Notice however how suspiciously familiar the two tokens are - but then again, I'm asking for an access token for a B2C application, not a properly registered (AAD) application.

Single client, single service

So I thought: let's follow the previous approach - only this time, ask for an access token for one of the two real services.

Request:

https://login.microsoftonline.com/fooplanner.onmicrosoft.com/oauth2/v2.0/authorize?
   p=b2c_1_fooplanner-signuporsignin&
   client_id=ffffffff-ffff-ffff-ffff-ffffffffffff&
   response_type=token id_token&
   scope=openid email profile aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa

This however complains about an unknown scope (aaa...) - so either I'm misusing the protocol, or B2C doesn't know about regular AAD apps (in the same tenant, mind you).

Speculation: use the authorization endpoint?

I've read somewhere (the OpenID spec?) that an IdP (i.e. B2C) has an authorization endpoint that you can use to exchange an id_token for an access_token.

Would this be the way to approach this? And are there any client-side libraries out there supporting this?

Roxana answered 9/1, 2017 at 7:3 Comment(0)
D
1

Azure AD B2C launched support for access tokens and custom defined scopes: https://azure.microsoft.com/en-us/blog/azure-ad-b2c-access-tokens-now-in-public-preview/ https://learn.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-access-tokens

However, there is still a limitation that you can only request an access token for one application at a time so it would be two requests. (But since the user authenticated with B2C, the second request would include a cookie and B2C would silently redirect back to the relying party application)

https://login.microsoftonline.com/fooplanner.onmicrosoft.com/oauth2/v2.0/authorize?
    p=b2c_1_fooplanner-signuporsignin&
    client_id=ffffffff-ffff-ffff-ffff-ffffffffffff&
    response_type=id_token token&
    scope=openid https://fooplanner.onmicrosoft.com/webapi1/readscopeA https://fooplanner.onmicrosoft.com/webapi1/writescopeA profile

https://login.microsoftonline.com/fooplanner.onmicrosoft.com/oauth2/v2.0/authorize?
    p=b2c_1_fooplanner-signuporsignin&
    client_id=ffffffff-ffff-ffff-ffff-ffffffffffff&
    response_type=id_token token&
    scope=openid https://fooplanner.onmicrosoft.com/webapi2/readscopeB https://fooplanner.onmicrosoft.com/webapi2/writescopeB profile

If the response_type parameter in a authorize request includes “token”, the “scope” parameter must include at least one resource permission (other than “openid” and “offline_access”) that will be granted. Otherwise, the authorize request will terminate with a failure.

Diffuse answered 20/7, 2017 at 0:6 Comment(0)
O
0

I am able to reproduce the issue as you described(the scope ... is not supported).

Based on the investigation, the B2C app doesn't support to get the access token from other application at present. We are not able to grant the other application's permission to the B2C app.

Is it helpful using the Client Credential flow instead of authorization code grant flow in your scenario? If yes, we can register a another app from Azure classic portal at the B2C tenant and grant the web API's permission to this app. Then we can use the request below to acquire the token without users sign-in:

POST:https://login.microsoftonline.com/{tenantId}/oauth2/token

resource={appIdURI of web API}&client_id={clientId}&client_secret={client_secret}&grant_type=client_credentials

Then we can acquire the token like below:

enter image description here

Here is the manifest changing to add the application permissions:

 "appRoles": [
        {
          "allowedMemberTypes":["Application"],
          "description":"Allow the application to write EasyAuthB2CAppWebAPI on behalf of the application.",
          "displayName":"Write EasyAuthB2CAppWebAPI",
          "id":"150c93f9-5d1a-4de2-821f-f69e8915dff7",
        "isEnabled":true,
          "value":"writeAppAll"
      },
        {
          "allowedMemberTypes":["Application"],
          "description":"Allow the application to read EasyAuthB2CAppWebAPI on behalf of the application.",
          "displayName":"Read EasyAuthB2CAppWebAPI",
          "id":"250c93f9-5d1a-4de2-821f-f69e8915dff7",
          "isEnabled":true,
          "value":"readAppAll"
      }
  ],

And if you want the Azure B2C app to support to acquire the access token for other applicaiton, you can try to submit the feedback from here.

Output answered 10/1, 2017 at 5:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.