Revoking the signin session for Azure AD B2c users is not working for Native applications

Viewed 2721

I have a solution which has mobile(android & Ios) and web UIs(SPA), both talks to common apis. We are using the AD B2C for authenticating the users. We have a business need where system administrators deletes the users. We are using the graph api to delete the users in the backend services. This is being done in two steps.

    1) revoke the sign in session to kill the active sessions. Document(https://docs.microsoft.com/en-us/graph/api/user-revokesigninsessions?view=graph-rest-1.0&tabs=http)

    2) Deleting the user account from b2c using graph api.

Once the lifetime of the accesstoken is expired the deleted user is getting signed out from the web ui(SPA), but not from the mobile application.

for the mobile application we are using MSALandroid 0.2.2 and for SPA we are using MSAL angular 0.1.4. Also tried with configuring single and two different applications in ad b2c for mobile and web application.

Am I missing anything mobile application. how to revoke the session from mobile apps as well?

1 Answers

In order to invalidate a user token you need to do things :

  1. Call 'invalidate refresh Token' custom action on the user. The call to this endpoint updates the "refreshTokensValidFromDateTime" property to dateTimeNow(UTC)

  2. Update your custom policies to look at this field when the refresh token is used to get a new access/idToken

These changes checks if the "refreshTokensValidFromDateTime" is newer than the refreshToken issued tim. If it is then it means that the old refresh token has been invalidated.

To invalidate a refresh token for a user you need to call this endpoint in graph https://graph.windows.net/contoso.onmicrosoft.com/users/3ff8bee2-d1dc-445a-bca1-64101d478f43/invalidateAllRefreshTokens?api-version=1.6  

Clock Skew
There is a clock skew to account for the potential difference in observed time between the server that created the refresh token (Azure AD B2C service) and the server that stamps the refreshTokenValidFromDateTime value on the user object (the Graph service). It is currently set to 300000 milliseconds (or 5 minutes)

To account for Clock Skew you can update the ClaimsTransformtion = AssertRefreshTokenIssuedLaterThanValidFromDate with this value

<InputParameter Id="TreatAsEqualIfWithinMillseconds" DataType="int" Value="10000" />

Refresh Token invalidation can be called adhoc by the app or you can write an API in your password reset journey that calls graph, or call it on demand when required. The user that goes through password reset journey in that case will see their session being ended and the user will be prompted on all other devices.

Changes Needed

For custom policies the following Technical Profile (TP), Claims Transformation (CT), and Claims Provider (CP) needs to be added in order for the policy to check if the refresh token was invalidated.

<ClaimsTransformation Id="AssertRefreshTokenIssuedLaterThanValidFromDate" TransformationMethod="AssertDateTimeIsGreaterThan">
        <InputClaims>
          <InputClaim ClaimTypeReferenceId="refreshTokenIssuedOnDateTime" TransformationClaimType="leftOperand" />
          <InputClaim ClaimTypeReferenceId="refreshTokensValidFromDateTime" TransformationClaimType="rightOperand" />
        </InputClaims>
        <InputParameters>
          <InputParameter Id="AssertIfEqualTo" DataType="boolean" Value="false" />
          <InputParameter Id="AssertIfRightOperandIsNotPresent" DataType="boolean" Value="true" />
           <InputParameter Id="TreatAsEqualIfWithinMillseconds" DataType="int" Value="10000" />
        </InputParameters>
      </ClaimsTransformation>

...

    <ClaimsProvider>
      <DisplayName>Token Issuer</DisplayName>
      <TechnicalProfiles>
        <TechnicalProfile Id="JwtIssuer">
          <DisplayName>JWT Issuer</DisplayName>
          <Protocol Name="None" />
          <OutputTokenFormat>JWT</OutputTokenFormat>
          <Metadata>
            <Item Key="client_id">{service:te}</Item>
            <Item Key="issuer_refresh_token_user_identity_claim_type">objectId</Item>
            <Item Key="SendTokenResponseBodyWithJsonNumbers">true</Item>
            <Item Key="RefreshTokenUserJourneyId">RedeemRefreshTokenV1</Item>
          </Metadata>
          <CryptographicKeys>
            <Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
            <Key Id="issuer_refresh_token_key" StorageReferenceId="B2C_1A_TokenEncryptionKeyContainer" />
          </CryptographicKeys>
          <InputClaims />
          <OutputClaims />
        </TechnicalProfile>

...

        <TechnicalProfile Id="TpEngine_RefreshTokenReadAndSetup">
          <DisplayName>Trustframework Policy Engine Refresh Token Setup Technical Profile</DisplayName>
          <Protocol Name="None" />
          <OutputClaims>
            <OutputClaim ClaimTypeReferenceId="objectId" />
            <OutputClaim ClaimTypeReferenceId="refreshTokenIssuedOnDateTime" />
          </OutputClaims>
        </TechnicalProfile>
      </TechnicalProfiles>

...

        <TechnicalProfile Id="AAD-UserReadUsingObjectId-CheckRefreshTokenDate">
          <OutputClaims>
            <OutputClaim ClaimTypeReferenceId="refreshTokensValidFromDateTime" />
          </OutputClaims>
          <OutputClaimsTransformations>
            <OutputClaimsTransformation ReferenceId="AssertRefreshTokenIssuedLaterThanValidFromDate" />
          </OutputClaimsTransformations>
          <IncludeTechnicalProfile ReferenceId="AAD-UserReadUsingObjectId" />
        </TechnicalProfile>

...

    <UserJourney Id="RedeemRefreshTokenV1">
      <AssuranceLevel>LOA1</AssuranceLevel>
      <PreserveOriginalAssertion>false</PreserveOriginalAssertion>
      <OrchestrationSteps>
        <OrchestrationStep Order="1" Type="ClaimsExchange">
          <ClaimsExchanges>
            <ClaimsExchange Id="RefreshTokenSetupExchange" TechnicalProfileReferenceId="TpEngine_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>

This journey will get called when using the Refresh Token, and actually check for the refreshTokenLastValidFrom timestamp that was updated in the Graph API call.

Related