Azure AD B2C - MitID integration
Sample app demonstrating how to use Azure AD B2C with MitID to validate a citizens civil registration number (colloquially: CPR number) and write back a custom attribute in the corporate Azure AD.
This is done to be NSIS compliant.
Use cases
- Use MitID to ensure a given user has validated her civil registration number (this sample).
- Use MitID to enable a disabled account.
- Use MitID to perform a password reset.
Prerequisites
- Create an Azure AD B2C tenant using the Azure Portal.
- Create an account on https://www.criipto.com/ and create an App Registration on their side. Capture client id and client secret.
- For production: All users should have their civil registration number (ten digits, no dash) stored in Azure AD (value to be hashed using SHA256 and subsequently base64 encoded).
Flow
Source from https://sequencediagram.org/:
title Flow
User->App:Initiate sign in
App->Azure AD B2C:Redirect to Azure AD B2C
Azure AD B2C->Criipto:Redirect to Criipto to sign in with MitID
Criipto->Azure AD B2C:Return claims with uuid, name, and cprNumberIdentifier
Azure AD B2C->Azure AD B2C: Translate claims
Azure AD B2C->Azure Function:Call Azure Function with issuerUserId, displayName, and civilRegistrationNumber
Azure Function->Azure AD:Lookup user on hashed civilRegistrationNumber
Azure Function->Azure AD:If found, update user with civilRegistrationNumberValidated (UTC Now)
Azure Function->Azure AD B2C:Return user with id, displayName, accountEnabled, and civilRegistrationNumberValidated
Azure AD B2C->App:Return claims
App->User:Signed in
Setup
- Clone code
- Search and replace all instances of
ondfiskb2c
with<your-azure-ad-b2c-tenant-name>
- Search and replace all instances of
ondfisk
with<your-azure-ad-tenant-name>
Azure AD
Create test user
POST https://graph.microsoft.com/v1.0/users
Content-type: application/json
{
"accountEnabled": true,
"displayName": "Åge Petersen",
"givenName": "Åge",
"surname": "Petersen",
"mailNickname": "aagep",
"passwordProfile": {
"forceChangePasswordNextSignIn": true,
"forceChangePasswordNextSignInWithMfa": false,
"password": "..."
},
"userPrincipalName": "aagep@ondfisk.dk",
"jobTitle": "Senior Tester"
}
Capture id
:
id
:e110ccf5-f660-4777-ace0-ed9c56f04981
Create app registration for storing user data
POST https://graph.microsoft.com/v1.0/applications/
Content-type: application/json
{
"displayName": "ondfisk extensions app - DO NOT DELETE",
"description": "ondfisk extensions app. Reserved for directory extensions. DO NOT DELETE. Used by ondfisk for storing user data.",
"signInAudience": "AzureADMyOrg",
"tags": [
"Directory extensions",
"Extensions",
"Extension attributes"
]
}
Capture id
and appId
id
:c45cf23e-e189-4b24-95d7-8814c4d09736
appId
:1be97e58-6e49-44ee-a17a-42fd1fc944cf
Create service principal for app
POST https://graph.microsoft.com/v1.0/servicePrincipals
Content-type: application/json
{
"appId": "1be97e58-6e49-44ee-a17a-42fd1fc944cf"
}
Create directory extension definitions
POST https://graph.microsoft.com/v1.0/applications/c45cf23e-e189-4b24-95d7-8814c4d09736/extensionProperties
Content-type: application/json
{
"name": "civilRegistrationNumber",
"dataType": "String",
"targetObjects": [
"User"
]
}
POST https://graph.microsoft.com/v1.0/applications/c45cf23e-e189-4b24-95d7-8814c4d09736/extensionProperties
Content-type: application/json
{
"name": "civilRegistrationNumberValidated",
"dataType": "DateTime",
"targetObjects": [
"User"
]
}
Compute civil registration number hash for test user
$civilRegistrationNumber = "0905540335"
$bytes = [System.Text.Encoding]::UTF8.GetBytes($civilRegistrationNumber)
$hash = [System.Security.Cryptography.SHA256]::HashData($bytes)
$base64 = [System.Convert]::ToBase64String($hash)
$base64
Update civil registration number hash on test user
PATCH https://graph.microsoft.com/v1.0/users/e110ccf5-f660-4777-ace0-ed9c56f04981
Content-type: application/json
{
"extension_1be97e586e4944eea17a42fd1fc944cf_civilRegistrationNumber": "yqaVIggNuNCpgvScLH9GdX0gB3LMo+LUuGc8Jv+bxSc="
}
Azure
Deploy resources:
./infrastructure/deploy.sh
Azure Functions
Under /src/Ondfisk.B2C.Functions
, create a local.settings.json
:
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "",
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
"AzureWebJobsFeatureFlags": "EnableHttpProxying"
}
}
Deploy /src/Ondfisk.B2C.Functions
.
Capture the default function key from the ValidateUser
function.
Note: You may want consider using a client credential flow instead of function keys.
Azure AD B2C
-
Add signing and encryption keys for Identity Experience Framework applications
-
Configure App Registration:
- Name:
jwt.ms
- Supported account types:
Accounts in any identity provider or organizational directory (for authenticating users with user flows)
- Redirect URI (recommended):
Single-page application (SPA)
https://jwt.ms
- Permissions:
[X]
Grant admin consent to openid and offline_access permissions - Authentication/Implicit grant and hybrid flows:
[X]
Access tokens (used for implicit flows)
- Name:
Configure Application Insights for Azure AD B2C
Using your newly created Application Insights resource follow this guide:
Collect Azure Active Directory B2C logs with Application Insights
Note: Remember to change DeploymentMode="Development"
and DeveloperMode="true"
before moving to production.
Update policies
Using the data from your Criipto App Registration:
- Update
policies/TrustFrameworkExtensions.xml
with client id - Create policy key
MitIDClientSecret
with client secret - Create policy key
FunctionsKey
with function key
Upload policies
Upload /policies
.
Test
You should now be able to test your app registration using:
- Username: Åge29164
- Password: ZXzx11^x
- CPR number: 0905540335