Sending a SAML LogoutRequest with SessionIndex from Azure AD B2C - azure-ad-b2c

We have a simple B2C user journey where the user authenticates via a 3rd party SAML Identity Provider and then does an api call to an external system based on the attributes received from the IdP. After completing the user journey the user should be logged out from the IdP. The IdP instructs that this should be done by sending a SAML LogoutRequest which includes the service provider EntityID and IssueInstant (timestamp), as well as the NameID and SessionIndex from the SAML Response. The IdP then returns a LogoutResponse.
The IdP metadata also includes a SingleLogoutService url with bindings for HTTP-POST and HTTP-Redirect.
How can we send the LogoutRequest from B2C to the IdP? The preferable way would be to launch the LogoutRequest directly from the B2C user journey. If this is not possible, we can also send the LogoutRequest from our application. To achieve this, B2C should be able to collect the SessionIndex as a claim from the SAML Response.
However, according to the B2C documentation, only attributes from the AttributeStatement of the SAML Response can be collected as output claims. The SessionIndex is included in the AuthnStatement of the SAML Response:
<saml2:AuthnStatement AuthnInstant="2023-02-14T14:40:02.726Z" SessionIndex="_5fb496b49e1b00b902e63ed857c4fdea">
My SAML IdP technical profile and session management profile are as follows:
<TechnicalProfile Id="SAML2-IdP">
<DisplayName>SAML IdP</DisplayName>
<Description>SAML IdP</Description>
<Protocol Name="SAML2"/>
<Metadata>
<Item Key="PartnerEntity">https://.../metadata.xml</Item>
<Item Key="WantsSignedRequests">true</Item>
<Item Key="XmlSignatureAlgorithm">Sha256</Item>
<Item Key="WantsEncryptedAssertions">true</Item>
<Item Key="ResponsesSigned">false</Item>
<Item Key="IncludeClaimResolvingInClaimsHandling">true</Item>
<Item Key="SingleLogoutEnabled">true</Item>
</Metadata>
<CryptographicKeys>
<Key Id="SamlMessageSigning" StorageReferenceId="B2C_1A_SAMLSecret"/>
<Key Id="SamlAssertionDecryption" StorageReferenceId="B2C_1A_SAMLSecret"/>
</CryptographicKeys>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="userId" PartnerClaimType="userId" />
<OutputClaim ClaimTypeReferenceId="givenName" PartnerClaimType="givenName" />
<OutputClaim ClaimTypeReferenceId="surname" PartnerClaimType="surname" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="socialIdpAuthentication" />
...
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="CreateRandomUPNUserName"/>
<OutputClaimsTransformation ReferenceId="CreateUserPrincipalName"/>
<OutputClaimsTransformation ReferenceId="CreateAlternativeSecurityId" />
<OutputClaimsTransformation ReferenceId="CreateSubjectClaimFromAlternativeSecurityId" />
</OutputClaimsTransformations>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Saml-idp"/>
</TechnicalProfile>
<ClaimsProvider>
<DisplayName>Session Management</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="SM-Saml-idp">
<DisplayName>Session Management Provider</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.SSO.SamlSSOSessionProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="IncludeSessionIndex">false</Item>
<Item Key="RegisterServiceProviders">false</Item>
</Metadata>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
I have also tried setting IncludeSessionIndex and RegisterServiceProviders to “true” but this didn't seem to change anything.
Is there a way to extract the SessionIndex in order to build the payload for SAML LogoutRequest or even better, can we somehow trigger B2C to always launch the LogoutRequest at the end of the journey, based on the data it received from the SAML Payload, and the IdP metadata?

Related

Sending a Claim in a validation Technical Profile

I am dong a validation check to our database during our password forget policy.
During this process, it calls and API and needs a bearer token. I already have a technical profile to do this we use in another step.
So in this process, It keeps failing to look for the bearer token. It is saying the only token available in the email.
I need to do a step in between to get the bearer token, but I'm not sure how to do that since I'm doing a validation...
Here is what I have. After I type in the email to validate it.
<ClaimsProvider>
<DisplayName>REST API to Check Member</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="LocalAccountDiscoveryUsingEmailAddress">
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="REST-CheckMemberAccountHolder" ContinueOnError="false"/>
</ValidationTechnicalProfiles>
</TechnicalProfile>
<TechnicalProfile Id="REST-CheckMemberAccountHolder">
<DisplayName>Rest API call to Check Member status</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="ServiceUrl">{API}</Item>
<Item Key="SendClaimsIn">Body</Item>
<Item Key="AuthenticationType">Bearer</Item>
<Item Key="UseClaimAsBearerToken">bearerToken</Item>
<Item Key="AllowInsecureAuthInProduction">false</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" PartnerClaimType="emailaddress"/>
<InputClaim ClaimTypeReferenceId="bearerToken" />
</InputClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
I need it to call
<TechnicalProfile Id="REST-AcquireAccessToken">
To get the bearertoken for the REST call in the validation.
I cant seem to figure out how to do it.
As per this, I assume you had something like:
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="bearerToken" PartnerClaimType="access_token" />
</OutputClaims>
when you acquired the token?
Is this all part of the same user journey?
What do you mean by "saying the only token available in the email"?

Azure B2C federated sign out is not complete (identity token is not passed to third party end session endpoint)

I have application which uses Azure B2C as IDP. Azure B2C provides a possible to use local account or federation via OpenIdConnect to third party IDP (Identity Server). I'm using custom policies in B2C. Login works fine but I have issues with sign out. Sign out from federated identity provider is executed but identity token of third party IDP is not passed to the end session endpoint and sign out is not properly executed.
Sign out requests:
GET https://xxxxx.b2clogin.com/xxxxx.onmicrosoft.com/b2c_1a_signup_signin/oauth2/v2.0/logout?post_logout_redirect_uri=https%3A%2F%2Flocalhost%3A44317%2Fsignout-callback-oidc&id_token_hint=xxxxx
GET https://thirdpartyidp.com/idp/connect/endsession <- id_token_hint querystring parameter is missing from here
GET https://thirdpartyidp.com/idp/logout?id=xxxxx
GET https://localhost:44317/signout-callback-oidc?state=xxxxx
I have tested this also with out of box user flow but same problem occurs.
I'm using this technical profile configuration:
<ClaimsProvider>
<Domain>thirdpartyidp</Domain>
<DisplayName>thirdpartyidp</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="thirdpartyidp-OAUTH">
<DisplayName>thirdpartyidp</DisplayName>
<Protocol Name="OpenIdConnect" />
<Metadata>
<Item Key="ProviderName">thirdpartyidp</Item>
<Item Key="METADATA">https://thirdpartyidp.com/idp/.well-known/openid-configuration</Item>
<Item Key="ValidTokenIssuerPrefixes">https://thirdpartyidp.com</Item>
<Item Key="IdTokenAudience">app</Item>
<Item Key="DiscoverMetadataByTokenIssuer">true</Item>
<Item Key="response_types">code</Item>
<Item Key="response_mode">form_post</Item>
<Item Key="scope">openid profile</Item>
<Item Key="HttpBinding">POST</Item>
<Item Key="UsePolicyInRedirectUri">false</Item>
<Item Key="client_id">app</Item>
<Item Key="SingleLogoutEnabled">true</Item>
</Metadata>
<CryptographicKeys>
<Key Id="client_secret" StorageReferenceId="B2C_1A_thirdpartyidp" />
</CryptographicKeys>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="issuerUserId" PartnerClaimType="sub" />
<OutputClaim ClaimTypeReferenceId="givenName" PartnerClaimType="sub" />
<OutputClaim ClaimTypeReferenceId="surName" PartnerClaimType="sub" />
<OutputClaim ClaimTypeReferenceId="displayName" PartnerClaimType="sub" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="socialIdpAuthentication" />
<OutputClaim ClaimTypeReferenceId="identityProvider" PartnerClaimType="iss" />
<OutputClaim ClaimTypeReferenceId="identityProviderAccessToken" PartnerClaimType="{oauth2:access_token}" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="CreateRandomUPNUserName"/>
<OutputClaimsTransformation ReferenceId="CreateUserPrincipalName"/>
<OutputClaimsTransformation ReferenceId="CreateAlternativeSecurityId"/>
</OutputClaimsTransformations>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-SocialLogin"/>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
I found two-three year old articles that Azure AD B2C does not support signing you out from the external identity provider. According this pretty new article (https://learn.microsoft.com/en-us/azure/active-directory-b2c/session-behavior?pivots=b2c-custom-policy#sign-out) it should be possible if I understood correctly. Azure B2C definitely attempts federated sign out but it's not complete.
Do I have some kind of custom policy configuration problem or what could be the issue?
You need to configure your relying party to send the Id token during signout.
<UserJourneyBehaviors>
<SingleSignOn Scope="Tenant" EnforceIdTokenHintOnLogout="true"/>
</UserJourneyBehaviors>
I got answer from MS support that passing identity token to third party IDP end session endpoint is not currently supported.

Getting access token for an api protected by B2C, using custom policies

I have an api that is protected using ADB2C authentication. I need to call this api via custom policies. I followed the documentation enter link description here and have added the two technical profiles as validation technical profile of a self asserted profile.
I am getting an access token returned by the below technical profile :
<TechnicalProfile Id="SecureREST-AccessToken">
<DisplayName></DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="ServiceUrl">https://login.microsoftonline.com/{tenant id here}/oauth2/v2.0/token</Item>
<Item Key="AuthenticationType">Basic</Item>
<Item Key="SendClaimsIn">Form</Item>
</Metadata>
<CryptographicKeys>
<Key Id="BasicAuthenticationUsername" StorageReferenceId="B2C_1A_SecureRESTClientId" />
<Key Id="BasicAuthenticationPassword" StorageReferenceId="B2C_1A_SecureRESTClientSecret" />
</CryptographicKeys>
<InputClaims>
<InputClaim ClaimTypeReferenceId="grant_type" DefaultValue="client_credentials" />
<InputClaim ClaimTypeReferenceId="scope" DefaultValue="{app id uri for protected resource}/.default" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="bearerToken" PartnerClaimType="access_token" />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
And then making the rest api call using below profile :
<TechnicalProfile Id="UserMigrationViaLegacyIdp">
<DisplayName>REST API call to communicate with Legacy IdP</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="ServiceUrl">
https://99a0a14a6402.ngrok.io/api/Identity/SignUpAsync
</Item>
<Item Key="AuthenticationType">Bearer</Item>
<Item Key="SendClaimsIn">Header</Item>
<Item Key="AllowInsecureAuthInProduction">false</Item>
<Item Key="UseClaimAsBearerToken">bearerToken</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="bearerToken"/>
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="phonePresent"/>
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
However, scopes are missing from the returned access token, hence token validation is failing on the api.
Is my call to get access token missing anything?
For the client credentials grant flow, the API permissions must be created as roles (see How to: Add app roles to your application and receive them in the token) and then granted admin consent (see Admin consent button).
As result, the bearer token contains the roles claim, rather than the scp claim.
The API application checks access using this roles claim (see Verify app roles in APIs called by daemon apps).

Azure B2c error 'Unable to validate the information provided.' when using custom attributes

I'm trying to add custom attributes to a custom policy. However it generates this error 'Unable to validate the information provided.'
I followed the documentation of the links below, I have already added application id b2c-extensions-app and object too.
https://learn.microsoft.com/pt-br/azure/active-directory-b2c/custom-policy-custom-attributes
https://learn.microsoft.com/pt-br/azure/active-directory-b2c/configure-user-input?pivots=b2c-custom-policy
Error log message "Error returned was 400/Request_BadRequest: The following extension properties are not available: extension_f41be....._tipoUsuario."
Part of the code in my TrustFrameworkExtensions file, where it writes and retrieves the information in AD:
<ClaimsProvider>
<DisplayName>Azure Active Directory</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="AAD-Common">
<Metadata>
<!--Insert b2c-extensions-app application ID here, for example: 11111111-1111-1111-1111-111111111111-->
<Item Key="5bfd........"></Item>
<!--Insert b2c-extensions-app application ObjectId here, for example: 22222222-2222-2222-2222-222222222222-->
<Item Key="18bd6......."></Item>
</Metadata>
</TechnicalProfile>
<TechnicalProfile Id="AAD-UserWriteUsingLogonEmail">
<Metadata>
<Item Key="client_id">f41be......</Item>
<!--Insert b2c-extensions-app application ID here, for example: 11111111-1111-1111-1111-111111111111-->
</Metadata>
<PersistedClaims>
<PersistedClaim ClaimTypeReferenceId="extension_tipoUsuario"/>
</PersistedClaims>
</TechnicalProfile>
<!-- Write data during edit profile flow. -->
<TechnicalProfile Id="AAD-UserWriteProfileUsingObjectId">
<PersistedClaims>
<PersistedClaim ClaimTypeReferenceId="extension_tipoUsuario"/>
</PersistedClaims>
</TechnicalProfile>
<!-- Read data after user authenticates with a local account. -->
<TechnicalProfile Id="AAD-UserReadUsingEmailAddress">
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="extension_tipoUsuario" />
</OutputClaims>
</TechnicalProfile>
<!-- Read data after user authenticates with a federated account. -->
<TechnicalProfile Id="AAD-UserReadUsingObjectId">
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="extension_tipoUsuario" />
</OutputClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
One of the common root causes to this problem is misconfiguration of client ID metadata of B2C Extension App.
Make sure the tutorial here is followed.

How to get claims from OpenID Connect provider in Azure AD B2C

I am having some issues getting claims from an OpenID Connect provider with an Azure AD B2C custom policy.
My OIDC provider does not return any claims in the id_token, it has a separate endpoint for claims called userInfo_endpoint where you send a GET request with Bearer authentication and the access_token go get user claims in json format. I understand this is pretty standard OIDC functionality.
Most examples I see use the ClaimsEndpoint to get claims and it seems to me the claims are added to the user as part of the signin_signup user journey.
Everything up to this point works as expected, testClaim is returned from b2c as part of the id_token, but no other claims are set. I have Application Insights set up for the policy, but the endpoint /userinfo is never called by B2C, and I see no trace of it in the logs. Are OIDC /userinfo endpoints even supported?
Below is my claims provider section.
<ClaimsProvider>
<DisplayName>Provider</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="provider-oidc">
<DisplayName>Providerprofile</DisplayName>
<Protocol Name="OpenIdConnect" />
<OutputTokenFormat>JWT</OutputTokenFormat>
<Metadata>
<Item Key="client_id">preprod-provider</Item>
<Item Key="scope">openid profile</Item>
<Item Key="response_types">code</Item>
<Item Key="METADATA">https://preprod.provider.com/oidc/.well-known/openid-configuration</Item>
<Item Key="ProviderName">https://preprod.provider.com/oidc</Item>
<Item Key="state">123abc</Item>
<Item Key="HttpBinding">POST</Item>
<Item Key="UsePolicyInRedirectUri">true</Item>
<Item Key="authorization_endpoint">https://preprod.provider.com/oidc/authorize</Item>
<Item Key="token_endpoint">https://preprod.provider.com/oidc/token</Item>
<Item Key="ClaimsEndpoint">https://preprod.provider.com/oidc/userinfo</Item>
<Item Key="ClaimsEndpointAccessTokenName">oauth2_access_token</Item>
<Item Key="ClaimsResponseFormat">json</Item>
<!--Item Key="userinfo_endpoint">https://preprod.provider.com/oidc/userinfo</Item-->
</Metadata>
<CryptographicKeys>
<Key Id="client_secret" StorageReferenceId="B2C_1A_ProviderClientSecret" />
</CryptographicKeys>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="socialIdpUserId" PartnerClaimType="sub"/>
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="providerAuthentication" />
<OutputClaim ClaimTypeReferenceId="identityProvider" DefaultValue="provider" />
<OutputClaim ClaimTypeReferenceId="displayName" PartnerClaimType="name" />
<OutputClaim ClaimTypeReferenceId="givenName" PartnerClaimType="given_name" />
<OutputClaim ClaimTypeReferenceId="surName" PartnerClaimType="family_name" />
<OutputClaim ClaimTypeReferenceId="testClaim" DefaultValue="testValue" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="CreateRandomUPNUserName"/>
<OutputClaimsTransformation ReferenceId="CreateUserPrincipalName"/>
<OutputClaimsTransformation ReferenceId="CreateAlternativeSecurityId"/>
<OutputClaimsTransformation ReferenceId="CreateSubjectClaimFromAlternativeSecurityId"/>
</OutputClaimsTransformations>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
Azure AD B2C does not support the userinfo_endpoint. You can request this feature in the Azure AD B2C feedback forum.
There are two workarounds for this:
At the application level - add code that, after obtaining the id_token, calls out to this userinfo_endpoint to obtain those extra claims and add them to the token for the rest of the application to leverage
At the B2C custom policy level - add a callout to a Rest API to retrieve the extra claims and add them in the token. Note that you won't be able to call the userinfo_endpoint, rather you'll need to write an in-between service that transforms the call REST call from B2C (which doesn't yet support sending an Authorization: Bearer X header) into a call to your userinfo_endpoint or to the underlying user store with the extra claims.
While the OpenIdConnect Technical Profile doesn't seem to support a userinfo endpoint, you should be able to use the OAuth2 Technical Profile together with the ClaimsEndpoint to get claims from the userinfo endpoint
The user_info endpoint is now supported in ADB2C. Please see https://learn.microsoft.com/en-us/azure/active-directory-b2c/userinfo-endpoint?pivots=b2c-custom-policy for more information.

Resources