How to Authenticate and Query Azure Digital Twins using PowerShell?

Summary

Continuing from the last post, this entry explores how to achieve the desired outcome using PowerShell—step by step.

Step By Step Solution

Step # 1: Create a self-signed cert and export a PFX for Azure AD app authentication

Intro: This script creates a self-signed certificate in your CurrentUser certificate store, then exports it (including the private key) to a PFX file you can use for automated client authentication.

$certName = "CN=MyAzureADAppCert"
$certPath = "C:\SRC\MyAzureADAppCert.pfx" # CHANGE the PATH to your need
$certPassword = ConvertTo-SecureString -String "[Change to your need]" -Force -AsPlainText 

# Create self-signed certificate in the CurrentUser\My store
$cert = New-SelfSignedCertificate -Subject $certName `
  -CertStoreLocation "Cert:\CurrentUser\My" `
  -KeyExportPolicy Exportable `
  -KeySpec Signature `
  -KeyLength 2048 `
  -NotAfter (Get-Date).AddYears(2) `
  -HashAlgorithm "SHA256"

# Export the certificate with private key to a PFX file
Export-PfxCertificate -Cert $cert `
  -FilePath $certPath `
  -Password $certPassword

# Output thumbprint and path
Write-Host "Certificate Thumbprint: $($cert.Thumbprint)"
Write-Host "Certificate exported to: $certPath"

What the script does (line-by-line)

a. Variables

$certName = “CN=MyAzureADAppCert” — subject name for the certificate.
$certPath — path where the PFX (private key + cert) will be written.
$certPassword — secure string used to protect the exported PFX.

b. Create the certificate

CertStoreLocation — where cert is saved (CurrentUser\My).
KeyExportPolicy Exportable — allows exporting the private key (needed to create a PFX).
KeySpec Signature — key intended for signing (used to sign the JWT client_assertion).
KeyLength, NotAfter, HashAlgorithm — cryptographic choices (2048-bit RSA, two-year lifetime, SHA256).


c. Export the cert+private key to a PFX

This creates MyAzureADAppCert.pfx that contains the private key — keep it secret.
Output thumbprint and path

Write-Host prints the certificate thumbprint; you’ll use that thumbprint to reference the cert (e.g., as kid/x5t in JWT headers).

d. Why this is useful?

Apps that need non-interactive auth (service-to-service) should use certificate-based client credentials instead of long-lived client secrets. The PFX holds the private key used to sign a JWT that Azure AD will verify using the public certificate uploaded to the App Registration.
Security note

NOTE: (IMPORTANT)

  • Protect the PFX (private key). Do not commit to source control.
  • Upload only the public certificate (.cer) to Azure AD.

Step # 2: Use the PFX to obtain an access token (certificate-based client_credentials)

High-level steps:

  • Export the public cert (.cer) and upload it to the Azure AD App registration.
  • Use the PFX (private key) locally to build and sign a client_assertion JWT.
  • POST to Azure AD token endpoint with client_assertion to get an access token.
  • Call the resource API (e.g., Azure Digital Twins) with the returned access token.
# Get certificate
$cert = Get-Item "Cert:\CurrentUser\My\[REPLACE WITH THUMBPRINT YOUR VALUE]"
$now = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds()
$exp = $now + 3600
$client_id = "REPLACE WITH YOUR VALUE"
$tenant_id = "REPLACE WITH YOUR VALUE"
# Replace these variables with your values
$adt_instance_url   = "[TODO REPLACE YOUR VALUE].azure.net" # e.g., myinstance.api.wus2.digitaltwins.azure.net
$api_version        = "2023-10-31" # Make sure you are using the latest version


# Compute SHA-1 thumbprint and base64url encode it for x5t
$sha1 = $cert.Thumbprint
# Convert hex thumbprint to byte array
$bytes = for ($i=0; $i -lt $sha1.Length; $i+=2) { [Convert]::ToByte($sha1.Substring($i,2),16) }
# Base64 encode, then base64url encode
$b64 = [Convert]::ToBase64String($bytes)
$x5t = $b64.Replace('+','-').Replace('/','_').Replace('=','')

# Build JWT header with x5t
$header = @{ alg = "RS256"; typ = "JWT"; x5t = $x5t } | ConvertTo-Json -Compress

# Build payload
$payload = @{
  aud = "https://login.microsoftonline.com/$tenant_id/oauth2/v2.0/token"
  iss = $client_id
  sub = $client_id
  jti = [guid]::NewGuid().ToString()
  nbf = $now
  exp = $exp
}

# Make sure you install the JWT module for New-Jwt  
# clone the git to SRC
# git clone https://github.com/SP3269/posh-jwt.git
# Import-Module C:\SRC\posh-jwt\JWT\JWT.psd1
# Create JWT with custom header
$jwt = New-Jwt -Cert $cert -Header $header -PayloadJson ($payload | ConvertTo-Json -Compress)
Write-Host "Signed JWT:" $jwt

# Prepare token request
$body = @{
    client_id             = $client_id
    scope                 = "https://digitaltwins.azure.net/.default"
    client_assertion_type = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
    client_assertion      = $jwt
    grant_type            = "client_credentials"
}

$loginResponse = Invoke-RestMethod -Method Post -Uri "https://login.microsoftonline.com/$tenant_id/oauth2/v2.0/token" -Body $body


$access_token       = $loginResponse.access_token

# Prepare the query body
$body = @{
    query = "SELECT * FROM DIGITALTWINS"
} | ConvertTo-Json

# Prepare headers
$headers = @{
    "Authorization" = "Bearer $access_token"
    "Content-Type"  = "application/json"
}

# Invoke the REST API
$response = Invoke-RestMethod -Method Post `
    -Uri "https://$adt_instance_url/query?api-version=$api_version" `
    -Headers $headers `
    -Body $body

$response

Explanation of the above PowerShell code

  1. Export public certificate (no private key) Follow Certmgr.msc or Certificate Manager in Windows 11/10
  2. From the certificate store (thumbprint from script output) export the public cert:
  3. The .cer file contains the public key only — safe to upload.
  4. Portal path: Azure Portal -> Azure Active Directory -> App registrations -> Your app -> Certificates & secrets -> Upload certificate -> select the .cer.
  5. Do NOT upload the PFX to Azure AD (unless you’re using a managed identity or special scenario). PFX contains the private key — it must remain private and protected by you.
  6. What Azure stores: public key and certificate metadata; Azure uses this to validate signatures of JWTs signed by the corresponding private key.
  7. When you POST the signed JWT (client_assertion), Azure AD looks for a matching public cert uploaded under the app. It uses the JWT header x5t or kid to pick the right public key. If not found, you get invalid_client / invalid JWT errors.
  8. Generate and sign client_assertion JWT (PowerShell outline)
  9. JWT header must include either x5t (base64url-encoded SHA-1 thumbprint) or kid that matches the uploaded certificate identity. Azure AD requires one to map token to the uploaded public key.
  10. Minimal PowerShell (using the posh-jwt approach you have available — adapt thumbprint and paths):
  11. Call Azure AD token endpoint using the client_assertion
  12. If the token call succeeds, Azure AD validated the signed JWT using the public cert you uploaded and returned an access token.
  13. Use the token to call the resource API (ADT example)

Conclusion

  • The above code creates and exports a PFX with a private key for signing client_assertion JWTs.
  • Upload the public .cer to Azure AD App -> Certificates & secrets.
  • Use the PFX locally to sign a JWT (include x5t or kid in header) then POST it as client_assertion to the token endpoint.
  • Use returned access token to call the resource API.

Reference

https://learn.microsoft.com/en-us/rest/api/digital-twins/dataplane/operation-groups?view=rest-dataplane-2023-10-31

Unknown's avatar

About Pankaj

I am a Developer and my linked profile is https://www.linkedin.com/in/pankajsurti/
This entry was posted in Technical Stuff and tagged , , , , . Bookmark the permalink.

Leave a comment