Azure AD B2C: Customizing the Refresh Token Flow

metalheart 411 Reputation points
2023-12-29T16:06:23.9233333+00:00

As I am trying to customize the way B2C issues refresh tokens in custom policies without success, and some questions come into my mind:

  1. I am using the TrustFrameworkBase.xml from the SocialAndLocalAccounts starter pack. Here, the Sign In/Sign Up relying party comes with referencing the RedeemRefreshToken user journey in the Token endpoint. I added a password reset policy without referencing RedeemRefreshToken but using this policy I'm still able to refresh the tokens, which seems suspect to me. Why is this?
  2. I am deliberately trying to throw an OAuth exception from within the RedeemRefreshToken user journey using below profile, but no error happens and I'm still able to acquire the refresh token. Why?
<TechnicalProfile Id="ReturnOAuth2Error">            
  <DisplayName>Return OAuth2 error</DisplayName>
  <Protocol Name="OAuth2" />          
  <OutputTokenFormat>OAuth2Error</OutputTokenFormat>
  <CryptographicKeys>
    <Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
  </CryptographicKeys>
  <InputClaims> 
    <InputClaim ClaimTypeReferenceId="errorCode" /> 
    <InputClaim ClaimTypeReferenceId="errorMessage" />
  </InputClaims>
</TechnicalProfile>
  1. I've noted that as soon as I'm using two orchestration steps of type SendClaims (one being the default JwtIssuer set as DefaultCpimIssuerTechnicalProfileReferenceId and the other one the error profile) I'm getting the JSON object in the refresh token response twice, which is an invalid JSON. Is that a bug in B2C?
{
  "access_token": ...
  "id_token": ...
  ...
}{
  "access_token": ...
  "id_token": ...
  ...
}

Note: I am requesting the refresh token manually (rather than using MSAL or similar) by

a. Acquiring the authorization code from:

https://<b2c-tenant>/<b2c-tenant>/<policy>/oauth2/v2.0/authorize?client_id=<client-id>&redirect_uri=https%3A%2F%2Fjwt.ms&response_type=code&scope=openid%20profile%20offline_access%20<api-scope>&response_mode=query

b. Exchanging the code and getting the refresh token:

curl --request POST \
  --url https://<b2c-tenant>/<b2c-tenant>/<policy>/oauth2/v2.0/token \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --header 'Origin: http://localhost:44321' \ 
  --data client_id=<client-id> \
  --data code=<authorization-code> \
  --data 'scope=openid profile offline_access <api-scope>' \
  --data grant_type=authorization_code \
  --data redirect_uri=https://jwt.ms \
  --data 'client_secret=<client-secret>'

curl --request POST \
  --url https://<b2c-tenant>/<b2c-tenant>/<policy>/oauth2/v2.0/token \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --header 'Origin: http://localhost:44321' \ 
  --data client_id=<client-id> \
  --data 'scope=openid profile offline_access <api-scope>' \
  --data grant_type=refresh_token \
  --data redirect_uri=https://jwt.ms \
  --data refresh_token=<refresh-token> \
  --data 'client_secret=<client-secret>'
Microsoft Security Microsoft Entra Microsoft Entra External ID
0 comments No comments
{count} votes

1 answer

Sort by: Most helpful
  1. Akshay-MSFT 17,951 Reputation points Microsoft Employee Moderator
    2024-01-03T10:55:32.45+00:00

    @metalheart

    Thank you for posting your query on Microsoft Q&A, from above description I could understand that you are concerned about why user is able to get a new refresh token when you have already skipped RedeemRefreshToken user journey.

    Please do correct me if this is not the ask by responding in the comments.

    As per Token types documentation.

    The only way for your application to know if a refresh token is valid is to attempt to redeem it by making a token request to Azure AD B2C. When you redeem a refresh token for a new token, you receive a new refresh token in the token response.

    For above mentioned validation only you have RedeemRefreshToken user journey. This user journey will validate that the refresh exiting token has not been revoked and not revoke existing refresh token or stop B2C from issuing a new refresh token along with access token.

    <!--
    <UserJourneys>-->
      <UserJourney Id="RedeemRefreshToken">
        <PreserveOriginalAssertion>false</PreserveOriginalAssertion>
        <OrchestrationSteps>
          <OrchestrationStep Order="1" Type="ClaimsExchange">
            <ClaimsExchanges>
              <ClaimsExchange Id="RefreshTokenSetupExchange" TechnicalProfileReferenceId="RefreshTokenReadAndSetup" />
            </ClaimsExchanges>
          </OrchestrationStep>
          <OrchestrationStep Order="2" Type="ClaimsExchange">
            <ClaimsExchanges>
              <ClaimsExchange Id="CheckRefreshTokenDateFromAadExchange" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId-CheckRefreshTokenDate" />
            </ClaimsExchanges>
          </OrchestrationStep>
          <OrchestrationStep Order="3" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
        </OrchestrationSteps>
      </UserJourney>
    <!--
    </UserJourneys>-->
    
    
    

    If you don't mention it, then token validation won't happen while resetting the password but whenever the user is authenticated a refresh token would still be issued.

    The workaround to your problem is that you could invalidate the issued refresh token with the user: invalidateAllRefreshTokens graph query.


    Please "Accept the answer (Yes)" and "share your feedback ". This will help us and others in the community as well.

    Thanks,

    Akshay Kaushik

    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.