Azure Active directory authentication using Client certificates
Most of you may already be aware of how to register an app/service principal in Azure Active directory. Once the app is registered, it can be used to authenticate against AAD and get access to Azure resources. For those of you need a refresher, you can refer to this LINK.
The documentation talks of 2 way to authenticate on AAD.
- Using Client secret
- Using Client certificate
Most of the cases, I’ve seen people using the 1st method because it is easier. The secret is just a random string which is shared with the client. If this along with the other details fall into the wrong hands, your app could be compromised. So the safer of the 2 methods is to use client certificates. Today we will be talking about how to authenticate using certificates. It took me some time to get the entire chain working but now that I have, I thought it would be best to share it so that it is easier for everyone else.
If you have a look at the sequence diagram above, the first step is for the client to generate the certificates and share the public certificate with the server. For Dev and test it is fine to use a self signed certificate but for production it is recommended to use a certificate signed by CA.
Execute the following code in Powershell.
New-SelfSignedCertificate -Subject “CN=CertForMyApp” -CertStoreLocation “Cert:\CurrentUser\My” -KeyExportPolicy Exportable -KeySpec Signature
The result should look like this
You will also get the hex value of the thumbprint which you will need later.
The next step is to export the certificates and keys.
Search for “mmc” in the windows search box
Click on “File”, “Add/Remove Snap-in”.
Add “Certificates — Current User” to the Selected Snap-ins.
In the Certificate sub-folder under the Personal folder, we should see the new certificate which we had created.
Right click on the certificate, All Tasks, and Export.
Click Next, Next.
Select Base-64 excoded X.509 (.CER) option and click Next.
Select a local location and file name and click Next and Finish to complete the export. The public key will be exported in .CER format. This is the public certificate that needs to be shared with the server and this is what will be uploaded into AAD against the new app registration/service principal.
The certificate contents will look like this
— — -BEGIN CERTIFICATE — — -
MIIDCDCCAfCgAwIBAgIQOFQ4ssy9KKBB1g8R4Kv8PDANBgkqhkiG9w0BAQUFADAX
MRUwEwYDVQQDDAxDZXJ0Rm9yTXlBcHAwHhcNMjEwODI1MDMwOTM5WhcNMjIwODI1
…….
..
.
— — -END CERTIFICATE — — -
Next, the client will need need to extract the private key from their certificates.
In the mmc application, Right click on the certificate, All Tasks, and Export.
Click Next, Select “Yes Export the Private key” and click Next.
Leave the defaults as shown below and click next.
Then enter a password and click Next.
Select a location and file name and click Next and Finish to complete the export. The private key will be exported in .PFX format.
You can use SSL Converter (decoder.link) to convert the pfx to pem format.
This PEM format will have 2 files in the zip file. 1 is the public certificate (.crt) file and another will be the private key(.key) file. The private key will look like this.
— — -BEGIN PRIVATE KEY — — -
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDN1MpfR6m1IEr8
oQXMem+QUHvFxyEFMeh8FBnROwz//EI/Qkywu1xqtcamJXozHAak0EoxTKs6pBap
…
.
— — -END PRIVATE KEY — — -
Now the server will need to register the app on Azure AD and upload the certificate onto it.
You can follow the steps in this LINK. Make note of the tenant_id, client_id as you will need this later.
Then client add a certificate or secret.
Upload the public certificate (.cer) format file. Click on upload certificate.
Select your public certificate(.cer) file which you had exported earlier.
After uploading the certificate successfully, you should see the screen as below.
Till now, we have only done the initial registration and key exchange. Now starts the actual API sequence flow. The first step here is to retrieve the bearer token from Azure AD. But before this, we will need a base64 encoded of the hex thumbprint we had earlier. We can use https://cryptii.com/pipes/hex-to-base64 to do this for us or we can use python as well.
from binascii import unhexlify, b2a_base64
result = b2a_base64(unhexlify(hex_string))
We will now proceed to create the signed jwt token. What I’ve shown below is the python code to do the needful.
pip install pyjwt
import jwt
import base64
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
from binascii import unhexlify, b2a_base64
pem_bytes = b””” — — -BEGIN PRIVATE KEY — — -
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDN1MpfR6m1IEr8……
— — -END PRIVATE KEY — — -”””
passphrase = None
private_key = serialization.load_pem_private_key(
pem_bytes, password=passphrase, backend=default_backend()
)
actualthumbprint = b”A874990C2DCCA23D0E804E1512093BB6E415A7A9"
result = b2a_base64(unhexlify(actualthumbprint)) #the base64 encoded version of the hex thumbprint.
print(f”The base64encoded version of the thumbprint is {result}”)
encoded = jwt.encode({“iss”: “<client_id>”,”sub”: “<client_id>”,”aud”: “https://login.microsoftonline.com/<tenant_id>/oauth2/token","iat": 1629864013,”jti”: 1123455614,”exp”: 1729869113}, private_key, algorithm=”RS256",headers={“alg”:”RS256", “typ”:”JWT”,”x5t”: “<base64 encoded_thumbprint>”})
print(f”Received token is : {encoded}”)
If you want to see how this was created you can have a look at this documentation.
Once you have the signed jwt token you can proceed to execute the API call to AAD to fetch the bearer token. You can find more details on this HERE.
You will need to fire a GET request to the url “https://login.microsoftonline.com/<tenant_id>/oauth2/token”.
Following is x-www-form-urlencoded body. The resource will depend on the type of resource you are trying to access.
grant_type = client_credentials
client_id = <client_id>
client_assertion_type = urn:ietf:params:oauth:client-assertion-type:jwt-bearer
client_assertion = <signed_JWT>
resource = https://management.azure.com/
The response to the above call will be a bearer token which you can then use to execute any API to which the service principal has access to.
Let me know in the comments if you have any questions. Will be happy to answer.