Sample code for 30+ languages & platforms
Xbase++

Validate the at_hash Claim of an ID Token

See more JSON Web Token (JWT) Examples

Demonstrates how to hash an access token to compare it with the at_hash claim of an ID token.

Chilkat Xbase++ Downloads

Xbase++
LOCAL nSuccess
LOCAL oJsonToken
LOCAL oJwt
LOCAL cIdToken
LOCAL cJose
LOCAL oJsonHeader
LOCAL cClaims
LOCAL oJsonClaims
LOCAL cToken_to_hash
LOCAL cToken_hash_expected
LOCAL oCrypt
LOCAL oBdHash
LOCAL nSz
LOCAL cToken_hash_computed

nSuccess := 0

//  This example requires the Chilkat API to have been previously unlocked.
//  See Global Unlock Sample for sample code.

//  This example uses a Google access_token + id_token that looks like this:

//   {
//    "access_token": "ya29.a0...0f",
//    "expires_in": 3599,
//    "scope": "openid https://www.googleapis.com/auth/userinfo.email",
//    "token_type": "Bearer",
//    "id_token": "eyJhb...o5nQ"
//  }

oJsonToken := CreateObject("Chilkat.JsonObject")
nSuccess := oJsonToken:LoadFile("qa_data/tokens/google_sample_id_token.json")
IF (nSuccess == 0)
    ? "Failed to load the JSON file..."
    oJsonToken:destroy()
    RETURN
ENDIF

//  Use Chilkat's JWT API to examine the id_token..
oJwt := CreateObject("Chilkat.Jwt")
cIdToken := oJsonToken:StringOf("id_token")

//  Extract the JOSE header..
cJose := oJwt:GetHeader(cIdToken)

oJsonHeader := CreateObject("Chilkat.JsonObject")
oJsonHeader:Load(cJose)
oJsonHeader:EmitCompact := 0
? oJsonHeader:Emit()

//  The JOSE header looks like this:

//  {
//    "alg": "RS256",
//    "kid": "e8799db06287515556213c80acbcfd022fb302a9",
//    "typ": "JWT"
//  }

cClaims := oJwt:GetPayload(cIdToken)

oJsonClaims := CreateObject("Chilkat.JsonObject")
oJsonClaims:Load(cClaims)
oJsonClaims:EmitCompact := 0
? oJsonClaims:Emit()

//  The claims look like this:

//  {
//    "iss": "https://accounts.google.com",
//    "azp": "258999997753-5ni8lu5f15r7mno97d82f5lir9i9f6i1.apps.googleusercontent.com",
//    "aud": "258999997753-5ni8lu5f15r7mno97d82f5lir9i9f6i1.apps.googleusercontent.com",
//    "sub": "111787341816486547572",
//    "email": "somebody@gmail.com",
//    "email_verified": true,
//    "at_hash": "HYJZImlW3mUK-UfjRfXjKw",
//    "iat": 1615315968,
//    "exp": 1615319568
//  }

//  The at_hash is the Access Token hash value. Its value is the base64url encoding of the
//  left-most half of the hash of the octets of the ASCII representation of the access_token value,
//  where the hash algorithm used is the hash algorithm used in the alg Header Parameter of the
//  ID Token's JOSE Header. For instance, if the alg is RS256, hash the access_token value with SHA-256,
//  then take the left-most 128 bits and base64url encode them. The at_hash value is a case sensitive string.

cToken_to_hash := oJsonToken:StringOf("access_token")
cToken_hash_expected := oJsonClaims:StringOf("at_hash")

//  Step 1. hashes the access token using SHA-256 (Google uses `RS256` as the ID Token `alg`).
oCrypt := CreateObject("Chilkat.Crypt2")
oBdHash := CreateObject("Chilkat.BinData")

oCrypt:HashAlgorithm := "sha256"
//  This encoding mode must match the encoding mode passed in the 2nd arg to AppendEncoded.
//  The encoding mode can be anything, as long as they are the same in both places.
oCrypt:EncodingMode := "hex"

nSuccess := oBdHash:AppendEncoded(oCrypt:HashStringENC(cToken_to_hash), "hex")
nSz := oBdHash:NumBytes

cToken_hash_computed := oBdHash:GetEncodedChunk(0, nSz / 2, "base64url")

//  If the hashes are identical, then the access_token as issued for the given id_token.
? "token_hash_expected: " + cToken_hash_expected
? "token_hash_computed: " + cToken_hash_computed

oJsonToken:destroy()
oJwt:destroy()
oJsonHeader:destroy()
oJsonClaims:destroy()
oCrypt:destroy()
oBdHash:destroy()