I'm using Azure B2C to connect to an external OpenID Connect identity provider, i created a basic user flow within B2C which works but only brings back a small number of claims so i need to create a custom policy to pass custom input parameters to my IDP and collect additional claims.
I started with the SocialAndLocalAccount sample and modified with my IDP's details:
TrustFrameworkBase.xml (Cut down to Technical Profile + User Journey)
...
<ClaimType Id="sub">
<DisplayName>Subject</DisplayName>
<DataType>string</DataType>
<DefaultPartnerClaimTypes>
<Protocol Name="OpenIdConnect" PartnerClaimType="sub" />
</DefaultPartnerClaimTypes>
<UserHelpText />
</ClaimType>
<ClaimType Id="ui_locales">
<DisplayName>UI Locales</DisplayName>
<DataType>string</DataType>
<UserHelpText>Special parameter passed for account authentication to Tell Us Once.</UserHelpText>
</ClaimType>
<ClaimType Id="givenName">
<DisplayName>Given Name</DisplayName>
<DataType>string</DataType>
<DefaultPartnerClaimTypes>
<Protocol Name="OAuth2" PartnerClaimType="given_name" />
<Protocol Name="OpenIdConnect" PartnerClaimType="given_name" />
<Protocol Name="SAML2" PartnerClaimType="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname" />
</DefaultPartnerClaimTypes>
<UserHelpText>Your given name (also known as first name).</UserHelpText>
<UserInputType>TextBox</UserInputType>
</ClaimType>
<ClaimType Id="surname">
<DisplayName>Surname</DisplayName>
<DataType>string</DataType>
<DefaultPartnerClaimTypes>
<Protocol Name="OAuth2" PartnerClaimType="family_name" />
<Protocol Name="OpenIdConnect" PartnerClaimType="family_name" />
<Protocol Name="SAML2" PartnerClaimType="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname" />
</DefaultPartnerClaimTypes>
<UserHelpText>Your surname (also known as family name or last name).</UserHelpText>
<UserInputType>TextBox</UserInputType>
</ClaimType>
<ClaimType Id="DateOfBirth">
<DisplayName>Date Of Birth</DisplayName>
<DataType>string</DataType>
<DefaultPartnerClaimTypes>
<Protocol Name="OAuth2" PartnerClaimType="birthdate" />
<Protocol Name="OpenIdConnect" PartnerClaimType="birthdate" />
<Protocol Name="SAML2" PartnerClaimType="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/dateofbirth" />
</DefaultPartnerClaimTypes>
<UserHelpText>Your date of birth.</UserHelpText>
<UserInputType>TextBox</UserInputType>
</ClaimType>
...
<ClaimsProvider>
<Domain>tellusonce</Domain>
<DisplayName>Tell Us Once</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="TUO-OpenIdConnect">
<DisplayName>Tell Us Once Login</DisplayName>
<Description>Login through Tell Us Once</Description>
<Protocol Name="OpenIdConnect" />
<Metadata>
<Item Key="client_id">this is where i put my client id</Item>
<Item Key="ProviderName">Tell Us Once</Item>
<Item Key="METADATA">this is where my metadata is</Item>
<Item Key="response_types">code</Item>
<Item Key="response_mode">form_post</Item>
<Item Key="scope">openid</Item>
<Item Key="HttpBinding">POST</Item>
<Item Key="UsePolicyInRedirectUri">false</Item>
</Metadata>
<CryptographicKeys>
<Key Id="client_secret" StorageReferenceId="B2C_1A_TellUsOnceSecret" />
</CryptographicKeys>
<InputClaims>
<InputClaim ClaimTypeReferenceId="scope" DefaultValue="openid profile" />
<InputClaim ClaimTypeReferenceId="prompt" DefaultValue="login" />
<InputClaim ClaimTypeReferenceId="ui_locales" DefaultValue="en-US" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="issuerUserId" PartnerClaimType="sub" />
<OutputClaim ClaimTypeReferenceId="givenName" PartnerClaimType="given_name" />
<OutputClaim ClaimTypeReferenceId="middleName" DefaultValue="" />
<OutputClaim ClaimTypeReferenceId="surname" PartnerClaimType="family_name" />
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="email" />
<OutputClaim ClaimTypeReferenceId="DateOfBirth" PartnerClaimType="birthdate" />
<OutputClaim ClaimTypeReferenceId="identityProvider" DefaultValue="ITP-B2C" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="ITP-Auth-DEV-OIDC" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="CreateRandomUPNUserName" />
<OutputClaimsTransformation ReferenceId="CreateUserPrincipalName" />
<OutputClaimsTransformation ReferenceId="CreateAlternativeSecurityId" />
<OutputClaimsTransformation ReferenceId="CreateSubjectClaimFromAlternativeSecurityId" />
</OutputClaimsTransformations>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
...
<UserJourneys>
<UserJourney Id="SignUpOrSignInOidc">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
<ClaimsProviderSelections>
<ClaimsProviderSelection TargetClaimsExchangeId="TUO-OIDCExchange" />
</ClaimsProviderSelections>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="TUO-OIDCExchange" TechnicalProfileReferenceId="TUO-OpenIdConnect" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- For social IDP authentication, attempt to find the user account in the directory. -->
<OrchestrationStep Order="3" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadUsingAlternativeSecurityId" TechnicalProfileReferenceId="AAD-UserReadUsingAlternativeSecurityId-NoError" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- Create the user in the directory if one does not already exist. -->
<OrchestrationStep Order="4" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="AADUserWrite" TechnicalProfileReferenceId="AAD-UserWriteUsingAlternativeSecurityId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="5" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>
</UserJourneys>
TrustFrameworkExtensions.xml
<TrustFrameworkPolicy
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://schemas.microsoft.com/online/cpim/schemas/2013/06"
PolicySchemaVersion="0.3.0.0"
TenantId="itpauthdev.onmicrosoft.com"
PolicyId="B2C_1A_TrustFrameworkExtensions"
PublicPolicyUri="http://itpauthdev.onmicrosoft.com/B2C_1A_TrustFrameworkExtensions"
TenantObjectId="ae2201eb-e4e9-44e7-8c73-b52e37ba01f8">
<BasePolicy>
<TenantId>itpauthdev.onmicrosoft.com</TenantId>
<PolicyId>B2C_1A_TrustFrameworkBase</PolicyId>
</BasePolicy>
<BuildingBlocks></BuildingBlocks>
<ClaimsProviders>
<ClaimsProvider>
<Domain>tellusonce</Domain>
<DisplayName>Tell Us Once</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="TUO-OpenIdConnect">
<DisplayName>Tell Us Once Login</DisplayName>
<Description>Login through Tell Us Once</Description>
<Protocol Name="OpenIdConnect" />
<Metadata>
<Item Key="client_id">this is where i put my client id</Item>
<Item Key="ProviderName">Tell Us Once</Item>
<Item Key="METADATA">this is where my metadata is</Item>
<Item Key="response_types">code</Item>
<Item Key="response_mode">form_post</Item>
<Item Key="scope">openid</Item>
<Item Key="HttpBinding">POST</Item>
<Item Key="UsePolicyInRedirectUri">false</Item>
</Metadata>
<CryptographicKeys>
<Key Id="client_secret" StorageReferenceId="B2C_1A_TellUsOnceSecret" />
</CryptographicKeys>
<InputClaims>
<InputClaim ClaimTypeReferenceId="acr_values" DefaultValue="urn:id.gov.au:tdif:acr:ip1:cl1" />
<InputClaim ClaimTypeReferenceId="scope" DefaultValue="openid profile" />
<InputClaim ClaimTypeReferenceId="prompt" DefaultValue="login" />
<InputClaim ClaimTypeReferenceId="ui_locales" DefaultValue="en-US" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="issuerUserId" PartnerClaimType="sub" />
<OutputClaim ClaimTypeReferenceId="givenName" PartnerClaimType="given_name" />
<OutputClaim ClaimTypeReferenceId="middleName" DefaultValue="" />
<OutputClaim ClaimTypeReferenceId="surname" PartnerClaimType="family_name" />
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="email" />
<OutputClaim ClaimTypeReferenceId="DateOfBirth" PartnerClaimType="birthdate" />
<OutputClaim ClaimTypeReferenceId="identityProvider" DefaultValue="ITP-B2C" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="ITP-Auth-DEV-OIDC" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="CreateRandomUPNUserName" />
<OutputClaimsTransformation ReferenceId="CreateUserPrincipalName" />
<OutputClaimsTransformation ReferenceId="CreateAlternativeSecurityId" />
<OutputClaimsTransformation ReferenceId="CreateSubjectClaimFromAlternativeSecurityId" />
</OutputClaimsTransformations>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
</ClaimsProviders>
<UserJourneys>
<UserJourney Id="SignUpOrSignInOidc">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
<ClaimsProviderSelections>
<ClaimsProviderSelection TargetClaimsExchangeId="TUO-OIDCExchange" />
</ClaimsProviderSelections>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="TUO-OIDCExchange" TechnicalProfileReferenceId="TUO-OpenIdConnect" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- For social IDP authentication, attempt to find the user account in the directory. -->
<OrchestrationStep Order="3" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadUsingAlternativeSecurityId" TechnicalProfileReferenceId="AAD-UserReadUsingAlternativeSecurityId-NoError" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- Create the user in the directory if one does not already exist. -->
<OrchestrationStep Order="4" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="AADUserWrite" TechnicalProfileReferenceId="AAD-UserWriteUsingAlternativeSecurityId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="5" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>
</UserJourneys>
</TrustFrameworkPolicy>
signin_signup_oidc.xml
<TrustFrameworkPolicy
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://schemas.microsoft.com/online/cpim/schemas/2013/06"
PolicySchemaVersion="0.3.0.0"
TenantId="itpauthdev.onmicrosoft.com"
PolicyId="B2C_1A_signup_signin_oidc"
PublicPolicyUri="http://itpauthdev.onmicrosoft.com/B2C_1A_signup_signin"
TenantObjectId="ae2201eb-e4e9-44e7-8c73-b52e37ba01f8">
<BasePolicy>
<TenantId>itpauthdev.onmicrosoft.com</TenantId>
<PolicyId>B2C_1A_TrustFrameworkExtensions</PolicyId>
</BasePolicy>
<RelyingParty>
<DefaultUserJourney ReferenceId="SignUpOrSignInOidc" />
<UserJourneyBehaviors></UserJourneyBehaviors>
<TechnicalProfile Id="PolicyProfile">
<DisplayName>PolicyProfile</DisplayName>
<Protocol Name="OpenIdConnect" />
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="givenName" />
<OutputClaim ClaimTypeReferenceId="middleName" DefaultValue="" />
<OutputClaim ClaimTypeReferenceId="surname" />
<OutputClaim ClaimTypeReferenceId="email" />
<OutputClaim ClaimTypeReferenceId="DateOfBirth" DefaultValue="ClaimNotFound" />
<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub" />
<OutputClaim ClaimTypeReferenceId="identityProvider" />
<OutputClaim ClaimTypeReferenceId="tenantId" AlwaysUseDefaultValue="true" DefaultValue="{Policy:TenantObjectId}" />
</OutputClaims>
<SubjectNamingInfo ClaimType="sub" />
</TechnicalProfile>
</RelyingParty>
</TrustFrameworkPolicy>
When i click "B2C_1A_signin_signup_oidc" and choose Run User Flow i get my IDP's login screen, once i've successfully logged in i get an authorization code response from my IDP, my IDP has been configured with the "/authresp" redirect uri as per Microsoft documentation ("Redirect Uri" section at the bottom) but then it doesn't seem like "/authresp" is exchanging the code for an id_token because when it redirects to jwt.ms i get "token does not contain a valid issuer".
Fiddler Code Response
By using fiddler's inspector i can see that the IDP's endpoint is responding with a code that looks something like this (not actual code):
<HTML>
<HEAD>
<TITLE>OIDC Form_Post Response</TITLE>
</HEAD>
<BODY Onload="document.forms[0].submit()">
<FORM METHOD="POST" ACTION="https://itpauthdev.b2clogin.com/itpauthdev.onmicrosoft.com/oauth2/authresp"> <INPUT
TYPE="HIDDEN" NAME="code"
VALUE="4cfed1f1-bfcc-42db-b39c-d79480f6d333.056c6e05-eaec-426a-a5e7-bf9f66f962be.15ac212d-38b7-4e9b-80e2-a948be9360e5" />
<INPUT TYPE="HIDDEN" NAME="state"
VALUE="StateProperties=eyJTSUQiOiJ4LW1zLWNwaW0tcmM6M2RkYmRhYjktY2VkZS00MDA4LTliNWYtOGRmZjU2ZDZmZDYzIiveWIlEIjoiMmFtkoc5YjUtMzg0Zi00M2JmLThkMDUtMjIzMGYxNzU1M2JjIiwiVE9CRJI6ImFlMjIwMWViLWU0ZTktNDRlNy04YzczLWI1MmUzN2JhMDFmOCJ9" />
<INPUT TYPE="HIDDEN" NAME="session_state" VALUE="65e01676-90d1-4c5e-a7f4-66dfd7b3211b" /> <NOSCRIPT>
<P>JavaScript is disabled. We strongly recommend to enable it. Click the button below to continue .</P>
<INPUT name="continue" TYPE="SUBMIT" VALUE="CONTINUE" />
</NOSCRIPT> </FORM>
</BODY>
</HTML>
Fiddler AuthResp Response
Using Fiddler once again, i can see that AuthResp does not exchange the code for an id_token using the IDP's token endpoint (or encounters an error, i don't get told anything helpful). Instead i get back:
<html>
<head>
<title>Object moved</title>
</head>
<body>
<h2>Object moved to <a
href="https://jwt.ms/#error=invalid_request&error_description=AADB2C90238%3a+The+provided+token+does+not+contain+a+valid+issuer.+Please+provide+another+token+and+try+again.%0d%0aCorrelation+ID%3a+2ad979b5-384f-43bf-8d05-2230f17553bc%0d%0aTimestamp%3a+2021-02-26+04%3a29%3a08Z%0d%0a">here</a>.
</h2>
</body>
</html>
Questions
- I read in documentation somewhere that an "OpenIDConnect" technical profile will automatically exchange the code for an id token but i can't seem to find the document, is my configuration missing a step or do i have to do this manually with an extra userjourney?
- If there is miscommunication between "/authresp" and the token endpoint, is there somewhere i can look to get a more descriptive error message? Both jwt.ms and the audit logs just say "Token does not contain a valid issuer".