Script to request and get access token from Microsoft graph API with certificate instead of client secret

bezell 21 Reputation points
2024-10-31T19:08:22.47+00:00

I want to avoid using the standard MSGraph or AzureAD modules by using Invoke-WebRequest. There are plenty of examples on how to use a client secret for App Registration authentication with Invoke-WebRequest. The below is from Vasil Michev's excellent resource website, for example:

How do I specify a certificate for authentication instead of the client_secret?

$Scopes = New-Object System.Collections.Generic.List[string]
$Scope = "https://graph.microsoft.com/.default"
$Scopes.Add($Scope)

$body = @{
    grant_type = "client_credentials"
    client_id = $clientId
    client_secret = $client_secret
    scope = $Scopes
}

    Write-Verbose "Obtaining token..."
    $res = Invoke-WebRequest -Method Post -Uri $uri -Body $body -ErrorAction Stop -Verbose:$false
    $token = ($res.Content | ConvertFrom-Json).access_token


    $authHeader = @{
       'Authorization'="Bearer $token"
    }      'Authorization'="Bearer $token"


Microsoft Graph
Microsoft Graph
A Microsoft programmability model that exposes REST APIs and client libraries to access data on Microsoft 365 services.
12,219 questions
PowerShell
PowerShell
A family of Microsoft task automation and configuration management frameworks consisting of a command-line shell and associated scripting language.
2,570 questions
Microsoft Entra ID
Microsoft Entra ID
A Microsoft Entra identity service that provides identity management and access control capabilities. Replaces Azure Active Directory.
22,061 questions
0 comments No comments
{count} votes

3 answers

Sort by: Most helpful
  1. Rich Matheisen 46,801 Reputation points
    2024-10-31T21:30:27.67+00:00

    I believe you can use the -CertificateThumbprint parameter. As an alternative you can also use the X509 certificate with the -Certificate parameter.

    https://www.saotn.org/posts/use-powershell-with-ssl-client-certificates-for-https-get-requests

    https://davidhamann.de/2019/04/12/powershell-invoke-webrequest-by-example/

    . . . there are lots more, too.

    0 comments No comments

  2. Vasil Michev 107.3K Reputation points MVP
    2024-11-01T07:23:24.42+00:00

    If you want to do the whole thing with direct HTTPS requests, this should cover the authentication part:

    # Define variables
    $tenantId = "tenant.onmicrosoft.com"
    $appId = "xxxxxxxxxx"
    $thumbprint = "xxxxxxxxxx"
    $resource = "https://graph.microsoft.com"
    $authUrl = "https://login.microsoftonline.com/$tenantId/oauth2/token"
    
    # Create a certificate object
    $cert = Get-Item -Path Cert:\CurrentUser\My\$thumbprint
    
    # Construct the JWT token
    $currentTime = [System.DateTime]::UtcNow
    $expiryTime = $currentTime.AddMinutes(60)  # Token valid for 1 hour
    $jwtHeader = @{
        alg = "RS256"
        typ = "JWT"
        x5t = [System.Convert]::ToBase64String($cert.GetCertHash())
    }
    $jwtPayload = @{
        iss = $appId
        sub = $appId
        aud = $authUrl
        nbf = [System.Math]::Floor($currentTime.Subtract((Get-Date "1970-01-01 00:00:00").ToUniversalTime()).TotalSeconds)
        exp = [System.Math]::Floor($expiryTime.Subtract((Get-Date "1970-01-01 00:00:00").ToUniversalTime()).TotalSeconds)
    }
    $jwtHeaderBase64 = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes(($jwtHeader | ConvertTo-Json)))
    $jwtPayloadBase64 = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes(($jwtPayload | ConvertTo-Json)))
    $signatureInput = "$jwtHeaderBase64.$jwtPayloadBase64"
    #$rsa = $cert.PublicKey.Key
    $rsa = $cert.PrivateKey
    $signatureBytes = $rsa.SignData([System.Text.Encoding]::UTF8.GetBytes($signatureInput), [System.Security.Cryptography.HashAlgorithmName]::SHA256, [System.Security.Cryptography.RSASignaturePadding]::Pkcs1)
    $jwtSignatureBase64 = [System.Convert]::ToBase64String($signatureBytes)
    $jwtToken = "$jwtHeaderBase64.$jwtPayloadBase64.$jwtSignatureBase64"
    
    # Construct the token request payload
    $tokenRequest = @{
        client_id = $appId
        resource = $resource
        client_assertion_type = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
        client_assertion = $jwtToken
        grant_type = "client_credentials"
    }
    
    # Send a POST request to Azure AD to obtain the access token
    $response = Invoke-RestMethod -Uri $authUrl -Method Post -ContentType "application/x-www-form-urlencoded" -Body $tokenRequest
    
    # Extract the access token
    $accessToken = $response.access_token
    
    # Print the access token
    Write-Host "Access Token: $accessToken"
    
    

    The certificate must be imported on the local device, and the current user. Adjust the code otherwise ($cert part).

    Much easier to get a token via MSAL though :)

    0 comments No comments

  3. CarlZhao-MSFT 42,211 Reputation points
    2024-11-01T09:17:20.9333333+00:00

    Hi @bezell

    To use a certificate for authentication instead of a client secret in PowerShell, you'll need to adjust your script to use certificate-based authentication. Here's how you can do it:

    1. Create or obtain a certificate: You can create a self-signed certificate using PowerShell or use an existing one. For example:
         $Cert = New-SelfSignedCertificate -DnsName "yourdomain.com" -CertStoreLocation "Cert:\CurrentUser\My" -FriendlyName "MyCert"
      
    2. Export the certificate: Export the certificate to a file if needed.
         Export-Certificate -Cert $Cert -FilePath "C:\path\to\your\certificate.cer"
      
    3. Register the certificate with your Azure AD app: Upload the certificate to your Azure AD app in the Azure portal under "Certificates & secrets".
    4. Modify your script to use the certificate:
         $Scopes = New-Object System.Collections.Generic.List[string]
         $Scope = "https://graph.microsoft.com/.default"
         $Scopes.Add($Scope)
         
         $CertThumbprint = "YOUR_CERT_THUMBPRINT"
         $Cert = Get-Item "Cert:\CurrentUser\My\$CertThumbprint"
         
         $body = @{
         grant_type = "client_credentials"
         client_id = $clientId
         scope = $Scopes
         client_assertion_type = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
         client_assertion = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($Cert))
         }
         
         Write-Verbose "Obtaining token..."
         
         $res = Invoke-WebRequest -Method Post -Uri $uri -Body $body -ErrorAction Stop -Verbose:$false
         
         $token = ($res.Content | ConvertFrom-Json).access_token
         
         $authHeader = @{
         'Authorization' = "Bearer $token"
         }
      

    This script uses the certificate's thumbprint to retrieve it from the certificate store and includes it in the authentication request.

    Also, for MS Graph PowerShell it should be: https://learn.microsoft.com/en-us/powershell/microsoftgraph/authentication-commands?view=graph-powershell-1.0#use-client-credential-with-a-certificate.

    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.