Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 872
Thomas Schaumburg Avatar asked Jan 09 '17 07:01

Thomas Schaumburg


1 Answers

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.

like image 132
Saeed Akhter Avatar answered Oct 26 '22 00:10

Saeed Akhter