Skip to content

Instantly share code, notes, and snippets.

@SafeEval
Created May 27, 2023 05:13
Show Gist options
  • Save SafeEval/8cd4af27211fcf79e4fe81da66b5b1bb to your computer and use it in GitHub Desktop.
Save SafeEval/8cd4af27211fcf79e4fe81da66b5b1bb to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
"""
Pure Python3 example of using a OIDC ID token's `at_hash` claim to verify
an opaque OIDC access token.
Required for Authelia, which doesn't issue JWT access tokens.
If the OIDC implementation uses an /introspection endpoint to verify an opaque
access token, that's another HTTP call that "violates stateless purity."
If it's alright to add a request for that, why not one for a blocklist?
Three ways to verify an access token. This script focuses on the second.
1. Access token is a self-contained JWT. Verify independently.
2. Access token is an opaque string. Parse ID token's `at_hash` claim to verify.
3. Access token is an opaque string. Call IdP's `/userinfo` endpoint to verify.
https://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken
3.1.3.6. ID Token
The contents of the ID Token are as described in Section 2. When using the
Authorization Code Flow, these additional requirements for the following
ID Token Claims apply:
at_hash
OPTIONAL. 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.
https://auth0.com/blog/id-token-access-token-what-is-the-difference/
An ID token is an artifact that proves that the user has been authenticated...
... so you can trust the claims about their identity.
OAuth 2 core specifications say nothing about the access token format.
https://auth0.com/docs/secure/tokens/access-tokens#opaque-access-tokens
If you receive an opaque Access Token, you don't need to validate it.
You can use it with the /userinfo endpoint,
"""
import base64
import hashlib
at = 'authelia_at_PNQjOdymqp4e6q_AwDRXQT6BviOWvN7c7udsOKFlRa0.h638cxezuYO3GFWvzn_1fN5oJ2RZU6UZxnMQAFvAz68'
at_hash = 'qaL20OjGCKkjyii5Dn8wew'
hash = hashlib.sha256()
hash.update(at.encode())
digest = hash.digest()
print("Hash digest:", digest)
print("Digest size:", hash.digest_size)
left_side = digest[:int(hash.digest_size/2)]
print("Left side:", left_side)
result = base64.urlsafe_b64encode(left_side).decode().rstrip("=")
print("Actual: ", result)
print("Expected: ", at_hash)
if at_hash == result:
print("PASS: valid access token")
else:
print("FAIL: bad access token")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment