Search code examples
azure-ad-b2copenid-connect.net-4.6.2

Azure AD B2C UseOpenIdConnectAuthentication - Can't find Refresh Token


Got a somewhat convoluted and outdated workflow that we're trying to "somewhat" modernize. We are using a very old set of code (.NET 4.6.2), and are migrating an Azure AD B2C ROPC flow to an Authorization Code flow. We have the basic setup working - we capture the auth endpoint using IAppBuilder.Map, and then use IAppBuilder.Run to call Authentication.Challenge with the specified auth type.

In terms of the auth provider registration, we use UseOpenIdConnectAuthentication, with the following options:

AuthenticationType = AuthenticationType.Storefront,
ClientId = clientId,
Authority = authority,
SignInAsAuthenticationType = AuthenticationType.Storefront,
Scope = OpenIdConnectScopes.OpenId,
ResponseType = OpenIdConnectResponseTypes.CodeIdToken,
PostLogoutRedirectUri = "/",
TokenValidationParameters = new TokenValidationParameters
{
    ValidateIssuer = false,
    NameClaimType = "name",
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
    AuthenticationFailed = context =>
    {
    context.HandleResponse();
    return Task.CompletedTask;
    },
    RedirectToIdentityProvider = RedirectToSameDomain,
    SecurityTokenValidated = OnOrgUserSecurityTokenValidated,
    AuthorizationCodeReceived = OnOrgAuthorizationCodeReceived
}

This works perfectly for getting the browser to redirect the user to Azure AD B2C, and then capturing the response with the SecurityTokenValidated callback. The problem is that in that response, the refresh token is always missing. We've tried several different places:

  • AuthenticationTicket.Properties.AllowRefresh is always false, despite setting AuthenticationProperties.AllowRefresh to true during the Authentication.Challenge step
  • ProtocolMessage.AccessToken contains a valid access token, however ProtocolMessage.RefreshToken is always null
  • Both of the above hold true regardless of whether we look at SecurityTokenValidated or at AuthorizationCodeReceived

On top of all of the above, there's one more question which we're unsure about. Currently we use ROPC to refresh the access token. Will that work even if we use the Authorization Code flow for signing in?

Any suggestions would be appreciated. Thanks!

EDIT

Going to mark the answer from Rukmini (https://stackoverflow.com/a/76578895/1289046) as correct, but I wanted to elaborate a bit on the specific steps I needed to take, to get this working.

First things first - in terms of the setup info for what gets sent over to Azure AD B2C, the first authorize call is sent using scope=openid and response_type=code id_token. I then hook into the SecurityTokenValidated message that Azure AD B2C sends back when authentication has occurred successfully.

In there, I modified the overall flow significantly. From the response I get from Azure AD B2C, I take only the ProtocolMessage.Code value, and I use that to make another call to Azure AD B2C. This time, though, I call it using grant_type=authorization_code and I set the code parameter to the aforementioned ProtocolMessage.Code value. I make this call using a client_id and client_secret registered in Azure AD B2C.

The response of this second call properly contains the refresh_token, alongside the id_token and an expires_in value for both tokens.

Last, but not least, I rewired the refresh token behaviour - as long as the refresh_token hasn't expired, I use it to get a new id_token if it has expired or will soon do so. If the refresh_token has expired, I log the user out.


Solution

  • I created an Azure AD B2C Application and granted API permissions like below:

    enter image description here

    Now, I generated access token using ROPC flow using below parameters via Postman:

    https://b2ctenant.b2clogin.com/b2ctenant.onmicrosoft.com/<policy-name>/oauth2/v2.0/token
    
    client_id:ClientID
    scope:openid 
    username:[email protected]
    password:*****
    grant_type:password
    client_secret:ClientSecret
    

    enter image description here

    Note that: To generate refresh token, the offline_access scope is required.

    To generate the refresh token, grant offline_access API permission:

    enter image description here

    Now while generating the token, pass offline_access scope like below:

    scope:openid offline_access
    

    enter image description here

    To get refresh token, modify the code like below:

    AuthenticationType = AuthenticationType.Storefront,
    ClientId = clientId,
    Authority = authority, SignInAsAuthenticationType = AuthenticationType.Storefront,
    Scope = OpenIdConnectScopes.OpenId + "offline_access",
    ResponseType = OpenIdConnectResponseTypes.CodeIdToken,
    

    On top of all of the above, there's one more question which we're unsure about. Currently we use ROPC to refresh the access token. Will that work even if we use the Authorization Code flow for signing in?

    https://b2ctenant.b2clogin.com/b2ctenant.onmicrosoft.com/B2C_1_Signinsignup/oauth2/v2.0/token
    
    client_id:xxxx
    grant_type:authorization_code
    scope:openid offline_access
    code:code
    redirect_uri:https://jwt.ms
    client_secret:ClientSecret
    

    enter image description here

    Yes, you can use ROPC to refresh the access token even if Authorization Code flow is used for signing in.

    I am able to refresh the access token generated by authorization code and using refresh token of ROPC:

    enter image description here