This post will demonstrate a couple of things:
- How to create a signed jwt token (aka Client Assertion) using Powershell.
- How to use this generated Client Assertion in Postman to get an Access Token Using Client Credentials Grant Flow.
To get an Access Token using Client-Credentials Flow, we can either use a Secret or a Certificate. This post will use a self-signed certificate to create the client assertion using both the nuget packages Microsoft.IdentityModel.Tokens and MIcrosoft.IdentityModel.JsonWebTokens. The idea in this blog is borrowed from the documentation Generating proof of possession tokens for rolling keys. Thanks to my team members Ray Held and Bac Hoang for assisting me with this sample. 🙂
Pre-requisites
To run the script in this blog you should have the following:
- Application created on the Azure portal under App registrations Blade.
- Assign Application level permissions under Microsoft Graph resource. Make sure you give the admin consent.
- Signing certificate
Here is the reference for Creating a self-signed Certificate. By running the Powershell script given in this reference, you will have the private key pfx and public key cer files created in the specified folder. Next you will need to upload that .cer file in the App registrations, like shown below.
Once you upload the certificate this is how it looks:
1. Create a signed jwt token (aka Client Assertion) using Powershell.
About few Objects used in this PowerShell script:
- $x509cert = This will give the certificate from the .pfx file. You will need the full path to the .pfx file and the password for the pfx.
- $signingCredentials = Represents an X.509 token used as the signing credential.
- $securityTokenDescriptor = Place holder for all the attributes related to the issued token.
Note: This PowerShell script may not work in PS core environment.
$ClientID = "XXXX" # Application/Client id used for the cert name $TenantID = "XXXX" # tenant id is used for the cert name $CertPassWord = "XXXX" # Password used for creating the certificate $aud = "https://login.microsoftonline.com/$TenantID/v2.0/" $CertificatePath_Pfx = "Get the path to the .pfx file" # Path where the certificate is saved Function JsonWeb-Libraries{ if ( ! (Get-ChildItem $HOME/IdentityModel/lib/Microsoft.IdentityModel.Logging.* -erroraction ignore) ) { install-package -Source nuget.org -ProviderName nuget -SkipDependencies Microsoft.IdentityModel.Logging -Destination $HOME/IdentityModel/lib -force -forcebootstrap | out-null } [System.Reflection.Assembly]::LoadFrom((Get-ChildItem $HOME/IdentityModel/lib/Microsoft.IdentityModel.Logging.*/lib/net45/Microsoft.IdentityModel.Logging.dll).fullname) | out-null if ( ! (Get-ChildItem $HOME/IdentityModel/lib/Microsoft.IdentityModel.Tokens.* -erroraction ignore) ) { install-package -Source nuget.org -ProviderName nuget -SkipDependencies Microsoft.IdentityModel.Tokens -Destination $HOME/IdentityModel/lib -force -forcebootstrap | out-null } [System.Reflection.Assembly]::LoadFrom((Get-ChildItem $HOME/IdentityModel/lib/Microsoft.IdentityModel.Tokens.*/lib/net45/Microsoft.IdentityModel.Tokens.dll).fullname) | out-null if ( ! (Get-ChildItem $HOME/IdentityModel/lib/Microsoft.IdentityModel.JsonWebTokens.* -erroraction ignore) ) { install-package -Source nuget.org -ProviderName nuget -SkipDependencies Microsoft.IdentityModel.JsonWebTokens -Destination $HOME/IdentityModel/lib -force -forcebootstrap | out-null } [System.Reflection.Assembly]::LoadFrom((Get-ChildItem $HOME/IdentityModel/lib/Microsoft.IdentityModel.JsonWebTokens.*/lib/net45/Microsoft.IdentityModel.JsonWebTokens.dll).fullname) | out-null } Function Get-ClientAssertion { JsonWeb-Libraries $x509cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($CertificatePath_Pfx, $CertPassWord) $claims = new-object 'System.Collections.Generic.Dictionary[String, Object]' $claims['aud'] = $aud $claims['iss' ] = $ClientId $claims['sub'] = $ClientId $claims['jti'] = [GUID]::NewGuid().ToString('D') $signingCredentials = [Microsoft.IdentityModel.Tokens.X509SigningCredentials]::new($x509cert) $securityTokenDescriptor = [Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor]::new() $securityTokenDescriptor.Claims = $claims $securityTokenDescriptor.SigningCredentials = $signingCredentials $tokenHandler = [Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler]::new() $clientAssertion = $tokenHandler.createToken($securityTokenDescriptor) write-host $clientAssertion } $myvar = Get-ClientAssertion
Once you get the Client-Assertion , you can decode it using jwt.ms . Here is how the decoded Client Assertion looks like.
The reference for Client – Assertion Format: https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-certificate-credentials
2. Use this generated Client Assertion in Postman to get an Access Token Using Client Credentials Grant Flow.
Use the TenantID and ClientID which are used while running the powershell script. And For client_assertion parameter, use the output from the PowerShell script.
POST: https://login.microsoftonline.com/{TenantID}/oauth2/v2.0/token
BODY:
scope=https://graph.microsoft.com/.default &client_id={ClientID} &client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer &client_assertion= {assertion you received from the above script} &grant_type=client_credentials
Here is the Postman screen shot:
You can decode the access token using jwt.ms.
Hi Bhavya.. nice article..
Can this be written in c using CURL library
can you please paste the code generated in POSTMAN for c-CURL here.
Good idea Pushpak . I honestly don’t know if that is possible. If you can open a support case with us and we can investigate on it.
Hi, wondering why powershell has to be used, and also why script needs pfx, when someone can use pem? Why cant it be proved just using postman
This is just one way to show how this can be achieved. If you know of a different way to accomplish this, please share your input.
[…] favor revise este oficial documento y sube un certificado como a […]
This script didn’t work for me as-is – maybe this is obvious to regular PowerShell users, but first I had to add the NuGet repo:
Find-PackageProvider -Name NuGet | Install-PackageProvider -Force
I also had to remove ‘-Source nuget.org’ from the install lines (might be another way to work around this).
Also needed to add a step to download Microsoft.IdentityModel.Abstractions which seems to be another required dependency.