title: JSON Web Token, signature not matching using powershell and Azure AD app
body: <p>I'm creating a solution to get info from our Azure AD tenant. I'm using powershell Invoke-Webrequest and OAuth 2.0 client credential grant to get the info. When I use a client secret my script is working fine but I want to make it more secure and use a self-signed certificate. </p>
  
  <p>I use the following command to create a new certificate on my local pc:</p>
  
  <pre><code>$cert = New-SelfSignedCertificate -HashAlgorithm "SHA256" -Subject "CN=******" -CertStoreLocation "Cert:\Currentuser\My" -KeyExportPolicy Exportable -KeySpec Signature -NotAfter (Get-Date).AddYears(5)
  </code></pre>
  
  <p>After creating I export this certificate to .cer using mmc and upload it to the Azure AD app.
  <a href="https://i.stack.imgur.com/Heefl.png" rel="nofollow noreferrer">Image from uploaded certificate</a></p>
  
  <p>I also creating a JSON Web Token using the following script:</p>
  
  <pre><code>############################################
  ## Variable
  ############################################
  $appEndPoint = "https://login.microsoftonline.com/************/oauth2/token"
  $appClientID = "*******************************"
  $exportPath = [Environment]::GetFolderPath("desktop")
  $guid = [guid]::NewGuid()
  
  ############################################
  ## JWT Token starttime/endtime
  ############################################
  $jwtStartTimeUnix = ([DateTimeOffset](Get-Date).ToUniversalTime()).ToUnixTimeSeconds()
  
  $jwtEndTimeUnix = ([DateTimeOffset](Get-Date).AddHours(1).ToUniversalTime()).ToUnixTimeSeconds()
  
  ###########################################
  ##Decoded Json JWT Token
  ###########################################
  $jwtID = $guid.Guid
  $decJwtHeader = '{"alg":"RS256","typ":"JWT","x5t":"' + [System.Convert]::ToBase64String(($cert.GetCertHash())) + '"}'
  #$decJwtPayLoad = '{"ver":"2.0","aud":"'+ $appEndPoint + '","exp":' + $jwtEndTimeUnix + ',"iss":"' + $appClientID + '","jti":"' + $jwtID + '","nbf":' + $jwtStartTimeUnix + ',"sub":"' + $appClientID +'"}'
  $decJwtPayLoad = '{
          "aud":"' + $appEndPoint + '"
      ,   "exp":"' + $jwtEndTimeUnix + '"
      ,   "iss":"' + $appClientID + '"
      ,   "jti":"' + $jwtID + '"
      ,   "nbf":"' + $jwtStartTimeUnix + '"
      ,   "sub":"' + $appClientID + '"
  }'
  ##########################################
  ##Encode Json JWT Token
  ##########################################
  $encJwtHeaderBytes = [system.text.encoding]::UTF8.GetBytes($decJwtHeader)
  $encHeader = [system.convert]::ToBase64String($encJwtHeaderBytes).Split('=')[0].Replace('+', '-').Replace('/', '_')
  
  $encJwtPayLoadBytes = [system.text.encoding]::UTF8.GetBytes($decJwtPayLoad)
  $encPayLoad = [system.convert]::ToBase64String($encJwtPayLoadBytes).Split('=')[0].Replace('+', '-').Replace('/', '_')
  
  $jwtToken = $encHeader + '.' + $encPayLoad
  
  $toSign = [system.text.encoding]::UTF8.GetBytes($jwtToken)
  
  #########################################
  ##Sign JWT Token
  #########################################
  $RSACryptoSP = [System.Security.Cryptography.RSACryptoServiceProvider]::new()
  $HashAlgo = [System.Security.Cryptography.SHA256CryptoServiceProvider]::new()
  $sha256oid = "2.16.840.1.101.3.4.2.1"
  
  $dataBytes = [System.Text.Encoding]::UTF8.GetBytes($toSign)
  $hashBytes = $HashAlgo.ComputeHash($dataBytes)
  $signedBytes = $RSACryptoSP.SignHash($hashBytes, $sha256oid)
  
  $sig = [System.Convert]::ToBase64String($signedBytes) -replace '\+','-' -replace '/','_' -replace '='
  
  #$sig = [system.convert]::ToBase64String($cert.PrivateKey.SignData($toSign,[Security.Cryptography.HashAlgorithmName]::SHA256,[Security.Cryptography.RSASignaturePadding]::Pkcs1)) -replace '\+','-' -replace '/','_' -replace '='
  
  $jwtToken = $jwtToken + '.' + $sig
  
  $exportPath = $exportPath + "\jwtToken.txt"
  
  $jwtToken | Out-File $exportPath
  </code></pre>
  
  <p>This JSON Web Token is the client assertion in my Invoke-Webrequest</p>
  
  <pre><code>#################################################################
  ## ENKEL VOOR TESTING, NIET IN AMP GEBRUIKEN ####################
  #################################################################
  
  $tenantid = '**********'
  $subscriptionid = '**********'
  $clientid = '**********'
  $client_assertion_type = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
  $client_assertion = '********'
  
  ##################################################################
  ##################################################################
  ##################################################################
  
  $return = Invoke-Command -ScriptBlock { 
  param($tenantid,$subscriptionid,$clientid,$client_assertion_type,$client_assertion)    
  
  Add-Type -AssemblyName System.Web
  
  $enc_client_assertion_type = [System.Web.HttpUtility]::UrlEncode($client_assertion_type)
  $encScope = [System.Web.HttpUtility]::UrlEncode('https://management.azure.com/.default')
  $enc_client_assertion = [System.Web.HttpUtility]::UrlEncode($client_assertion)
  
  $body = "scope=$encScope&amp;client_id=$clientid&amp;client_assertion_type=$enc_client_assertion_type&amp;client_assertion=$enc_client_assertion&amp;grant_type=client_credentials"
  
  $auth = Invoke-WebRequest "https://login.microsoftonline.com/$tenantid/oauth2/token" -Method Post -ContentType "application/x-www-form-urlencoded" -Body $body -UseBasicParsing
  
  $token = ($auth | ConvertFrom-Json).access_token
  $headers = @{
      'Authorization'="Bearer $($token)"
  }
  
  $data = Invoke-WebRequest "https://management.azure.com/subscriptions/$subscriptionid/providers/Microsoft.Advisor/recommendations?api-version=2017-04-19" -Method GET -Headers $headers -UseBasicParsing
  
  New-Object PSObject -Property @{
      content=$data.content
  }
  
  } -ArgumentList $tenantid,$subscriptionid,$clientid,$client_assertion_type,$client_assertion
  
  $content = $return.content
  
  Write-Host $content
  </code></pre>
  
  <p>I'm receiving the following error but can't find out why. Anyone an idea?</p>
  
  <pre><code>Invoke-WebRequest : {"error":"invalid_client","error_description":"AADSTS700027: Client assertion contains an invalid signature. [Reason - The provided signature value did not match the expected signature value., 
  Thumbprint of key used by client: '******************', Found key 'Start=10/14/2019 09:02:58, End=10/14/2024 09:12:59', Please visit 'https://developer.microsoft.com/en-us/graph/graph-explorer' 
  and query for 'https://graph.microsoft.com/beta/applications/***********' to see configured keys]\r\nTrace ID: 085758a5-7470-4a3d-91ba-6f98518e7100\r\nCorrelation ID:
  1ab6c7d2-4e46-4e7b-a56f-42720a24286a\r\nTimestamp: 2019-10-14 09:43:15Z","error_codes":[700027],"timestamp":"2019-10-14
  09:43:15Z","trace_id":"085758a5-7470-4a3d-91ba-6f98518e7100","correlation_id":"1ab6c7d2-4e46-4e7b-a56f-42720a24286a","error_uri":"https://login.microsoftonline.com/error?code=700027"}
  </code></pre>