SAML-Based SSO With Azure AD B2C as an IDP
While signing on might not be the most fun thing for users, for devs, it's a critical part of the process of application security.
Join the DZone community and get the full member experience.
Join For FreeAmong the many perks of working in an Agile environment, one is to constantly evolve with challenging tasks. While working on my project, there was one such requirement where we needed to use another application without signing in every time.
I couldn’t find its implementation online except for these two documents which were very helpful-
- https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-get-started-custom
- https://github.com/Azure-Samples/active-directory-b2c-advanced-policies/blob/master/Walkthroughs/RP-SAML.md
So my most of the code will be from the above documents, except with some tweaks which I had to make because I was not getting the results I needed.
We were using AD B2C to signup and sign-in to the application. We had already built-in policies for sign in and sign up but, for SAML, we were required to create a new Custom Policy.
Let’s follow these steps to create a Custom Policy to support SAML SSO.
Step 1 – Create Policy Keys and Identity Experience Framework Application
Follow the first 3 points of this URL.
- Prerequisites
- Add signing and encryption keys to your B2C tenant for use by custom policies.
- Register Identity Experience Framework applications.
We do not need to change anything and these steps are clear and explanatory.
Step 2 – Create Certificate
We need to create certificates to sign the SAML response.
- Create the cert using makecert
- makecert -r -pe -n “CN=yourappname.yourtenant.onmicrosoft.com” -a sha256 -sky signature -len 2048 -e 12/21/2018 -sr CurrentUser -ss My YourAppNameSamlCert.cer
- Go to cert store “Manage User Certificates” > Current User > Personal > Certificates > yourappname.yourtenant.onmicrosoft.com
- Right-click > All Tasks > Export
- Yes, export the private key.
- Defaults (PFX and first checkbox)
- Go to your Azure AD B2C tenant. Click Settings > Identity Experience Framework > Policy Keys.
- Click +Add, and then click Options > Upload.
- Enter a Name (for example, YourAppNameSamlCert). The prefix B2C_1A_ is automatically added to the name of your key.
- Upload your certificate using the upload file control.
- Enter the certificate’s password.
- Click Create.
- Verify that you’ve created a key (for example, B2C_1A_YourAppNameSamlCert).
Step 3 – Download Sample Policies
Go to the URL and download the following 3 files. We will modify these policies based on our requirement in next steps.
- Base file- Iamnahealth.onmicrosoft.com_B2C_1A_base.xml
- Base Extension file- Iamnahealth.onmicrosoft.com_B2C_1A_base_Extensions.xml
- Sign in file- Iamnahealth.onmicrosoft.com_signin.xml
Step 4 – Update Base file
- Open the base file.
- Replace all instances of “Iamnahealth” with your tenant id.
- There should be an element with the id, “JwtIssuer.” Add the following code just after the JwtIssuer technical profile
<TechnicalProfile Id="Saml2AssertionIssuer">
<DisplayName>Token Issuer</DisplayName>
<Protocol Name="None" />
<OutputTokenFormat>SAML2</OutputTokenFormat>
<Metadata>
<Item Key="IssuerUri">https://login.microsoftonline.com/te/TenantID.onmicrosoft.com/B2C_1A_signin</Item>
</Metadata>
<CryptographicKeys>
<Key Id="SamlAssertionSigning" StorageReferenceId="B2C_1A_YourAppNameSamlCert" />
<Key Id="SamlMessageSigning" StorageReferenceId="B2C_1A_YourAppNameSamlCert" />
</CryptographicKeys>
<InputClaims />
<OutputClaims />
</TechnicalProfile>
The above code is taken from GitHub. But the IssuerUri is different. The IssuerUri that is in the document was not working. I was getting an error on the SP side. So I changed it to tge above URL.
4. There should be UserJourney
element with the with signIn
. Add the following code just after signIn
.
<UserJourney Id="SignInSaml">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="LocalAccountSigninEmailExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Email" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>authenticationSource</Value>
<Value>localAccountAuthentication</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadWithAltSecId" TechnicalProfileReferenceId="AAD-UserReadUsingAlternativeSecurityId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="3" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>authenticationSource</Value>
<Value>socialIdpAuthentication</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="4" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="Saml2AssertionIssuer" />
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>
Step 5 – Update Base_Extension File
Replace the base extension file with the following content:
<TrustFrameworkPolicy xmlns="http://schemas.microsoft.com/online/cpim/schemas/2013/06" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" PolicySchemaVersion="0.3.0.0" TenantId="TenantId .onmicrosoft.com" PolicyId="B2C_1A_base_extensions" PublicPolicyUri="http://TenantId.onmicrosoft.com">
<BasePolicy>
<TenantId>TenantId .onmicrosoft.com</TenantId>
<PolicyId>B2C_1A_base</PolicyId>
</BasePolicy>
<BuildingBlocks />
<ClaimsProviders>
<ClaimsProvider>
<DisplayName>Local Account SignIn</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="login-NonInteractive">
<Metadata>
<Item Key="client_id">ProxyIdentityExperienceFrameworkAppId</Item>
<Item Key="IdTokenAudience">IdentityExperienceFrameworkAppId</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="client_id" DefaultValue="ProxyIdentityExperienceFrameworkAppId" />
<InputClaim ClaimTypeReferenceId="resource_id" PartnerClaimType="resource" DefaultValue="IdentityExperienceFrameworkAppId" />
</InputClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
</ClaimsProviders>
</TrustFrameworkPolicy>
Replace all instances of:
- TenantId with your TenantId.
- ProxyIdentityExperienceFrameworkAppId with the application id that you created by following Step 1.
- IdentityExperienceFrameworkAppId with the application id that you created by following Step 1.
Step 6 – Create Policy File
Create a new file with name “signinsaml.xml.”
Paste the following code.
<TrustFrameworkPolicy xmlns="http://schemas.microsoft.com/online/cpim/schemas/2013/06" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" PolicySchemaVersion="0.3.0.0" TenantId="TenantId.onmicrosoft.com" PolicyId="signin" PublicPolicyUri="http://TenantId.onmicrosoft.com">
<BasePolicy>
<TenantId>TenantId.onmicrosoft.com</TenantId>
<PolicyId>B2C_1A_base_extensions</PolicyId>
</BasePolicy>
<RelyingParty>
<DefaultUserJourney ReferenceId="SignInSaml" />
<TechnicalProfile Id="PolicyProfile">
<DisplayName>PolicyProfile</DisplayName>
<Protocol Name="SAML2" />
<SubjectAuthenticationRequirements TimeToLive="40000" ResetExpiryWhenTokenIssued="false" />
<Metadata>
<Item Key="PartnerEntity"><![CDATA[<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" validUntil="2026-12-27T23:42:22.079Z" entityID="entityID" xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> <md:SPSSODescriptor WantAssertionsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"> <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat> <md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://acs_url.com/sso/sp/ACS.saml2″ index="0″ isDefault="true"/> </md:SPSSODescriptor> </md:EntityDescriptor>]]></Item>
<Item Key="Saml2AttributeEncodingInfo"><![CDATA[<saml2:AttributeStatement xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion"><saml2:Attribute FriendlyName="UserPrincipalName" Name="UserId" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"><saml2:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string"></saml2:AttributeValue></saml2:Attribute></saml2:AttributeStatement>]]></Item>
<Item Key="Saml11AttributeEncodingInfo"><![CDATA[<saml:AttributeStatement xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion"><saml:Attribute AttributeName="ImmutableID" AttributeNamespace="http://schemas.microsoft.com/LiveID/Federation/2008/05″><saml:AttributeValue></saml:AttributeValue></saml:Attribute><saml:Attribute AttributeName="UPN" AttributeNamespace="http://schemas.xmlsoap.org/claims"><saml:AttributeValue></saml:AttributeValue></saml:Attribute></saml:AttributeStatement>]]></Item>
<Item Key="client_id">ProxyIdentityExperienceFrameworkAppId</Item>
<Item Key="IdTokenAudience">IdentityExperienceFrameworkAppId</Item>
</Metadata>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="givenName" />
<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub" />
<OutputClaim ClaimTypeReferenceId="surname" />
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" />
<OutputClaim ClaimTypeReferenceId="userPrincipalName" />
</OutputClaims>
<SubjectNamingInfo ClaimType="sub" />
</TechnicalProfile>
</RelyingParty>
</TrustFrameworkPolicy>
Replace all instances of:
- TenantId with your TenantId.
- ProxyIdentityExperienceFrameworkAppId with the application id that you created by following Step 1.
- IdentityExperienceFrameworkAppId with the application id that you created by following Step 1.
- https://acs_url.com/sso/sp/ACS.saml2 with the URL of the SP where you want to POST your SAML Response.
- entityId with the entityID of the SP.
Step 7 – Upload Policy in Azure Portal
- Go to https://portal.azure.com
- Click “More Services” (at the bottom left corner) and type “Azure AD B2C” and select it.
- Click on “Identity Experience Framework – PREVIEW” and then “Upload Policy.”
- Upload the Base file.
- Upload the Base extension file.
- Upload the signinsaml file.
Note– Upload should be in the same order.
Step 8 – Run Policy
- Configure you SAML Metadata – from the below URL, you will get the SAML Metadata. You will need to use SAML Metadata at SP side. https://login.microsoftonline.com/te/tenantId.onmicrosoft.com/B2C_1A_signin/Samlp/metadata
- Login URL –https://login.microsoftonline.com/te/tenantId.onmicrosoft.com/B2C_1A_signin/Samlp/sso/login This URL will be used to initiate the SAML flow. You have to hit the above URL with two query string parameters:
SAML_Request
RelayState
Sample AuthnRequest
<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns="urn:oasis:names:tc:SAML:2.0:metadata" ID="F84D888AA3B44C1B844375A4E8210D9E" Version="2.0" IsPassive="false" AssertionConsumerServiceURL="https://acs_url.com/sso/sp/ACS.saml2">
<Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion" />
<samlp:NameIDPolicy Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified" />
</samlp:AuthnRequest>
The above request will send the SAML Response to the ACS URL.
This way you can implement SSO.
Published at DZone with permission of Shanky Munjal. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments