OAuth OIDC – Client Authentication Using Signed JWT Instead of Secret

authenticationazurecertificateoauthtoken

I'm sending out the signal flare after exhausting my search efforts. I feel I'm real close to getting this working but hit the wall. Below details an example of what I'm trying to accomplish and the steps taken so far. Please point out errors and make suggestions where able.


I have an OAUTH/OPENID/OIDC application that's registered in Azure and want to use a certificate to authenticate my client instead of a client secret.


I generated a certificate, exported and uploaded the public portion to the "certificates and secrets" section of the configuration. Thumbprint: 3BC87980310C490A62AA5F6343D4C55DF8EBBA85

Manifest with modified values…

"keyCredentials": [
{
"customKeyIdentifier": "3BC87980310C490A62AA5F6343D4C55DF8EBBA85",
"endDate": "2020-10-10T19:45:00Z",
"keyId": "ff3ce8e8-7268-4b46-88be-d3a191a0695e",
"startDate": "2019-10-10T19:45:00Z",
"type": "AsymmetricX509Cert",
"usage": "Verify",
"value": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0….",
"displayName": "L=mycity, E=myemail@email.com, CN=jarred-test-oauth, OU=Federation, O=12345"


The authentication is simple enough and am able to obtain an authorization code. I signed my client assertion JWT with the Private certificate:

Header: {
"alg": "RS256",
"typ": "JWT"
}

Payload: {
"iss": "2f877daa-b6f5-42a3-8430-acf238b234e1",
"sub": "2f877daa-b6f5-42a3-8430-acf238b234e1",
"nbf": 1570803651,
"exp": 1570807251,
"iat": 1570803651,
"jti": "3BC87980310C490A62AA5F6343D4C55DF8EBBA85",
"typ": "JWT"
}

"iss" and "sub" are the client_id of my app within Azure.


I tried to test the exchange of the code for tokens with the following in Postman:

POST: https://login.microsoftonline.com/my-tenant/oauth2/v2.0/token

Headers:
Content-Type = application/x-www-form-urlencoded

Body:
grant_type = authorization_code

code = obtained_code

client_assertion_type = urn:ietf:params:oauth:client-assertion-type:jwt-bearer

client_assertion = eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiIyZjg3N2RhYS1iNmY1LTQyYTMtODQzMC1hY2YyMzhiMjM0ZTEiLCJzdWIiOiIyZjg3N2RhYS1iNmY1LTQyYTMtODQzMC1hY2YyMzhiMjM0ZTEiLCJuYmYiOjE1NzA4MDM2NTEsImV4cCI6MTU3MDgwNzI1MSwiaWF0IjoxNTcwODAzNjUxLCJqdGkiOiIzQkM4Nzk4MDMxMEM0OTBBNjJBQTVGNjM0M0Q0QzU1REY4RUJCQTg1IiwidHlwIjoiSldUIn0.t8lArFodXkHO9Ps9O3q7VH55pRl6NtcIkEbSz-hDL0V6I7iWi4N-1VBNM_nFUHkNhBoGaskV0eQtqMXYildb7oEr75KgbcjacZy2OI319uPwztHp9jVxjsBhB_rKXND4M6URr23IWkLwFb2008vq_fY4trLUZR9ILZOE0Dr_MdaQmrt8fU9mYNkSEnRsiXKuqcS97oBfo6-9MuDbkcNuAOxZnsmbYvutk1LeabFywbc4qO3dgb8PtfqMAiYxgYTzg72tAw-ncq6uRXgG5XoxJVOExCyn5CXV9lSsE33_oekOEfRU5CyC0IvtSLhSoZ7LKtSMJ22ZXiyFqvddenJC8w


Sending the request yields the following error from Azure (in postman):

{
"error": "invalid_request",
"error_description": "AADSTS5002723: Invalid JWT token. No certificate thumbprint nor keyId specified in token header.\r\nTrace ID: 89f69560-9ae7-482f-803c-9faa71d44100\r\nCorrelation ID: e2ebab72-8b4d-47a6-85be-14893158dd5e\r\nTimestamp: 2019-10-11 14:23:32Z",
"error_codes": [
5002723
],
"timestamp": "2019-10-11 14:23:32Z",
"trace_id": "89f69560-9ae7-482f-803c-9faa71d44100",
"correlation_id": "e2ebab72-8b4d-47a6-85be-14893158dd5e"
}

I assume The header is supposed to contain a KID or 'thumbprint' value. I don't have a KID as I'm not setting up a public facing JWKS for this purpose, and not sure what to enter as a field for thumbprint.


Very much appreciated if someone could point me in the right direction. I've already seen all the references to others that have posted questions. I could really use someone with knowledge on what to try next.


Here are some tools and documents that have gotten me this far:

JWT signing tool – http://kjur.github.io/jsjws/tool_jwt.html

Oauth client authentication explained – https://medium.com/@darutk/oauth-2-0-client-authentication-4b5f929305d4

RFC 7523 OAuth JWT Assertion Profiles – https://www.rfc-editor.org/rfc/rfc7523#section-2.2

Microsoft blog – https://blogs.msdn.microsoft.com/exchangedev/2015/01/21/building-daemon-or-service-apps-with-office-365-mail-calendar-and-contacts-apis-oauth2-client-credential-flow/

JSON Web Token (JWT) – Claims and Signing
draft-jones-json-web-token-01 – https://tools.ietf.org/id/draft-jones-json-web-token-01.html

KB for Client Authentication – https://kb.authlete.com/en/s/oauth-and-openid-connect/a/client-secret-jwt

Thank you!

–Jarred

Best Answer

Had to panic before finding the answer. 5 days of searching and here's my findings in case anyone else finds this question and needs help.

Quick Steps:

  1. Generate your private key (the below uses PowerShell)

  2. Generate your Public certificate

  3. Convert your public cert and private key in to a PEM format

  4. Upload your public certificate in to the application configuration under 'certificates and secrets'

  5. Obtain the Thumbprint of your certificate in Base64 (either grab while creating certificate or look up from keystore or other location)

$cert=New-SelfSignedCertificate -Subject "CN=$ApplicationName" -CertStoreLocation "Cert:\CurrentUser\My"  -NotAfter (Get-Date).AddMonths(24) -KeyExportPolicy Exportable -KeySpec Signature
$bin = $cert.RawData
$base64Value = [System.Convert]::ToBase64String($bin)
$bin = $cert.GetCertHash()
$base64Thumbprint = [System.Convert]::ToBase64String($bin)
  1. build your JWT (I used https://jwt.io/)

Header

{
  "alg": "RS256",
  "typ": "JWT",
  "x5t": "<Base64 Thumbprint>"
}

Payload

{
  "iss": "<clientid>",
  "sub": "<clientid>",
  "exp": 1570838377 (expiration time),
  "jti": "<random unique identifier>",
  "aud": "https://<token-endpoint>"
}

Drop your private key (PEM) in to the bottom verify-er which will sign your JWT in the "Encoded" window.

  1. Obtain your authorization code
  2. Craft your request for tokens

Header

Content-Type: application/x-www-form-urlencoded

Body

grant_type=authorization_code
code=OAQABAAIAAACQN9QBR.... (your authorization code)
client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer
client_assertion=eyJhbGciOiJSUzI1NiIsInR5.... (your signed JWT)
state=state (optional)
redirect_uri=https%3A%2F%2Fblackhole.com (The callback URI)
  1. POST to your token endpoint. If all goes as planned, you should receive your tokens.
Related Topic