active-directory-b2c-xamarin-native
active-directory-b2c-xamarin-native copied to clipboard
MSAL B2C token refresh flow - multiple APIs
Hello team! This is a great sample, easy to understand and works well obtaining a token from B2C and calling an API. There is a known limitation with B2C in the sense that the token refresh flow does not allow obtain tokens for a second, different API. Doing so yields an id token, refresh token and a null access token!! To obtain a second access token, AcquireTokenInteractive will need to be called; which may require a users to enter credentials (not an ideal user experience)
Additionally, this means, calling AcquireTokenSilent() and sending a second set of scopes runs … but does not returned an access token, nor does it generate any errors, or throw exceptions. The error is surfaced later when a call to an API is make, failing with an invalidTokenError.
In my attempt to use MSAL to workaround this, I modified AcquireTokenSilent by adding a scope parameter; I also checked to see if the access token was null; if it was, I then called AcquireTokenInteractive.
public async Task<UserContext> AcquireTokenSilent(string[] scopes)
{
IEnumerable<IAccount> accounts = await _pca.GetAccountsAsync();
AuthenticationResult authResult = await _pca.AcquireTokenSilent(scopes, GetAccountByPolicy(accounts, B2CConstants.PolicySignUpSignIn))
.WithB2CAuthority(B2CConstants.AuthoritySignInSignUp)
.ExecuteAsync();
if (authResult.AccessToken == null)
{
//acquire token interactive ...
authResult = await _pca.AcquireTokenInteractive(scopes)
.WithPrompt(Prompt.NoPrompt)
.WithAuthority(B2CConstants.AuthoritySignInSignUp)
.ExecuteAsync();
}
var newContext = UpdateUserInfo(authResult);
return newContext;
}
This “gets the job done” in the sense that a second access token is obtained, but briefly pops up a blank screen.
The ask: Can this sample be modified to take into account this B2C limitation? Either:
- Throw an exception when a null token is returned
- Once detected, could a different request be made, such as AcquireTokenInteractive?
- Can anything be done to make this seamless, so that there are no prompts?
Thank you
@Kasenga : we are tracking this work as part of our MSAL releases currently scheduled for 4.20. There is currently no ETA.
Hello team! This is a great sample, easy to understand and works well obtaining a token from B2C and calling an API. There is a known limitation with B2C in the sense that the token refresh flow does not allow obtain tokens for a second, different API. Doing so yields an id token, refresh token and a null access token!! To obtain a second access token, AcquireTokenInteractive will need to be called; which may require a users to enter credentials (not an ideal user experience)
Additionally, this means, calling AcquireTokenSilent() and sending a second set of scopes runs … but does not returned an access token, nor does it generate any errors, or throw exceptions. The error is surfaced later when a call to an API is make, failing with an invalidTokenError.
In my attempt to use MSAL to workaround this, I modified AcquireTokenSilent by adding a scope parameter; I also checked to see if the access token was null; if it was, I then called AcquireTokenInteractive.
public async Task<UserContext> AcquireTokenSilent(string[] scopes) { IEnumerable<IAccount> accounts = await _pca.GetAccountsAsync(); AuthenticationResult authResult = await _pca.AcquireTokenSilent(scopes, GetAccountByPolicy(accounts, B2CConstants.PolicySignUpSignIn)) .WithB2CAuthority(B2CConstants.AuthoritySignInSignUp) .ExecuteAsync(); if (authResult.AccessToken == null) { //acquire token interactive ... authResult = await _pca.AcquireTokenInteractive(scopes) .WithPrompt(Prompt.NoPrompt) .WithAuthority(B2CConstants.AuthoritySignInSignUp) .ExecuteAsync(); } var newContext = UpdateUserInfo(authResult); return newContext; }
This “gets the job done” in the sense that a second access token is obtained, but briefly pops up a blank screen.
The ask: Can this sample be modified to take into account this B2C limitation? Either:
- Throw an exception when a null token is returned
- Once detected, could a different request be made, such as AcquireTokenInteractive?
- Can anything be done to make this seamless, so that there are no prompts?
Thank you
What second set of "scopes" did you pass in? (Also, can you confirm the first, as I too am having the NULL AccessToken problem, and need to call another API [also secured by the same B2C]).
. . .
"What second set of "scopes" did you pass in? (Also, can you confirm the first, as I too am having the NULL AccessToken problem, and need to call another API [also secured by the same B2C])."
Hey George! I was using MsGraph and a custom API. The first access token for MsGraph worked just fine - I was seeing an issue when attempting to obtain a second access token for my custom API. Turns out this results in a null token if you call acquireTokenSilent. To get the second token, you will need to call acquireTokenInteractive.
@jennyf19 @jmprieur : I think we should explore with the B2C team if we can catch the error and throw an interaction required exception, ensuring customers can use our regular call patterns. From my understanding this happens when the scope to the refresh token flow is not the same as originally used. Thoughts?
@henrik-me : Would we want to change MSAL.NET so that a UIRequiredException is thrown in the B2C case, when no access token is returned?
@henrik-me : Would we want to change MSAL.NET so that a UIRequiredException is thrown in the B2C case, when no access token is returned?
Yes, @jmprieur, this would be ideal!!
I think a better idea would be for the service to return the exception (to do like AAD?) Cc: @nickgmicrosoft
Hi all, I've had the same problem I have and application that needs to call 3 apis. 1st call I do it like this:
try
{
IEnumerable<IAccount> accounts = await _pca.GetAccountsAsync();
AuthenticationResult authResult = await _pca.AcquireTokenSilent(B2CConstants.ScopesApi1, GetAccountByPolicy(accounts, B2CConstants.PolicySignUpSignIn))
.WithB2CAuthority(B2CConstants.AuthoritySignInSignUp)
.ExecuteAsync();
var newContext = UpdateUserInfo(authResult);
return newContext;
}
catch (MsalUiRequiredException)
{
// acquire token interactive
AuthenticationResult authResult = await _pca.AcquireTokenInteractive(B2CConstants.ScopesApi1)
.WithPrompt(Prompt.SelectAccount) //This for 1st call
.ExecuteAsync();
var newContext = UpdateUserInfo(authResult);
return newContext;
}
then the other 2 times I do the same but in the interactive part instead of calling it with .WithPrompt(Prompt.SelectAccount) I call it with .WithPrompt(Prompt.Consent) like this:
try
{
IEnumerable<IAccount> accounts = await _pca.GetAccountsAsync();
AuthenticationResult authResult = await _pca.AcquireTokenSilent(B2CConstants.ScopesApi2, GetAccountByPolicy(accounts, B2CConstants.PolicySignUpSignIn))
.WithB2CAuthority(B2CConstants.AuthoritySignInSignUp)
.ExecuteAsync();
var newContext = UpdateUserInfo(authResult);
return newContext;
}
catch (MsalUiRequiredException)
{
// acquire token interactive
AuthenticationResult authResult = await _pca.AcquireTokenInteractive(B2CConstants.ScopesApi2)
.WithPrompt(Prompt.Consent) //This for the 2nd and 3rd call
.ExecuteAsync();
var newContext = UpdateUserInfo(authResult);
return newContext;
}
The result in the UI is: 1st call user enter credentials 2nd and 3rd call user does not do anything but the app open the credential site and close it without any user interaction