-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #7 from rjw57/docs-improvements
Flesh out some documentation including making the README less empty
- Loading branch information
Showing
2 changed files
with
130 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,82 @@ | ||
# Python library to verify OIDC tokens using OIDC discovery | ||
# Python library to verify id tokens using OIDC discovery | ||
|
||
[![PyPI - Version](https://img.shields.io/pypi/v/verify-oidc-identity)](https://pypi.org/p/verify-oidc-identity/) | ||
![PyPI - Python Version](https://img.shields.io/pypi/pyversions/verify-oidc-identity) | ||
[![GitHub Release](https://img.shields.io/github/v/release/rjw57/verify-oidc-identity)](https://github.com/rjw57/verify-oidc-identity/releases) | ||
[![Test suite status](https://github.com/rjw57/verify-oidc-identity/actions/workflows/main.yml/badge.svg?branch=main)](https://github.com/rjw57/verify-oidc-identity/actions/workflows/main.yml?query=branch%3Amain) | ||
|
||
[OpenID connect][oidc] identity tokens are a popular choice for federating identity between | ||
different systems without the need to share secrets. For example [Trusted publishing on | ||
PyPI](https://docs.pypi.org/trusted-publishers/) allows use of OIDC tokens created by | ||
GitHub or GitLab CI jobs to be used to authenticate when uploading new Python packages. | ||
Similarly, OIDC tokens can be used to authenticate to [Google | ||
Cloud](https://cloud.google.com/iam/docs/workload-identity-federation), | ||
[AWS](https://docs.aws.amazon.com/IAM/latest/UserGuide/introduction_access-management.html#intro-access-roles | ||
and | ||
[Azure](https://learn.microsoft.com/en-us/graph/api/resources/federatedidentitycredentials-overview?view=graph-rest-1.0) | ||
from any OIDC identity provider. | ||
|
||
The [jwt.io](https://jwt.io/) and [jwt.ms](https://jwt.ms/) tools allow validating OIDC | ||
id tokens without first configuring public keys by means of the [OpenID connect | ||
discovery][oidc-discovery] protocol. | ||
|
||
This library implements the OpenID Connect discovery standard in Python to allow | ||
verification of OpenID Connect id tokens without previous configuration of public keys, | ||
etc. | ||
|
||
Both synchronous and asynchronous (`asyncio`) implementations are provided. | ||
|
||
[oidc]: https://openid.net/specs/openid-connect-core-1_0.html | ||
[oidc-discovery]: https://openid.net/specs/openid-connect-discovery-1_0.html | ||
|
||
## Example | ||
|
||
Suppose you created a [GitLab OIDC | ||
token](https://docs.gitlab.com/ee/ci/secrets/id_token_authentication.html) as part of a | ||
CI job to make an authenticated HTTP GET request to some service: | ||
|
||
```yaml | ||
# .gitlab-ci.yml within https://gitlab.com/my-group/my-project | ||
|
||
job_with_id_token: | ||
id_tokens: | ||
ID_TOKEN: | ||
aud: https://my-service.example.com | ||
script: | ||
- curl -X GET -H "Authorization: Bearer $ID_TOKEN" https://my-service.example.com | ||
``` | ||
The following example shows how to verify the OIDC token came from a specific project | ||
within a backend implementation: | ||
```py | ||
from typing import Any | ||
from federatedidentity import Issuer, verifiers, verify_id_token | ||
|
||
# Use OIDC discovery to fetch public keys for verifying GitLab tokens. | ||
GITLAB_ISSUER = Issuer.from_discovery("https://gitlab.com") | ||
|
||
# Expected project path for id token | ||
EXPECTED_PROJECT_PATH = "my-group/my-project" | ||
|
||
# Expected audience claim for id token. | ||
EXPECTED_AUDIENCE_CLAIM = "https://my-service.example.com" | ||
|
||
def verify_gitlab_token(token: str) -> dict[str, Any] | ||
""" | ||
Verify an OIDC token from GitLab and return the dictionary of claims. Raises | ||
federatedidentity.FederatedIdentityError if the token failed verification. | ||
""" | ||
return verify_id_token( | ||
token, | ||
valid_issuers=[GITLAB_ISSUER], | ||
valid_audiences=[EXPECTED_AUDIENCE_CLAIM], | ||
required_claims=[ | ||
# The "project_path" claim must match the expected project. | ||
{"project_path": EXPECTED_PROJECT_PATH}, | ||
], | ||
) | ||
``` | ||
|
||
See [the full documentation](https://rjw57.github.io/verify-oidc-identity/) for more | ||
examples. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
#!/usr/bin/env python3 | ||
""" | ||
Script to verify an OIDC identity token as issued by Google by means of the gcloud command | ||
line tool. For example: | ||
$ gcloud auth login | ||
... | ||
$ gcloud auth print-identity-token | ./examples/verify-google-id-token.py | ||
Verified token claims: | ||
{ | ||
"iss": "https://accounts.google.com", | ||
"azp": "32555940559.apps.googleusercontent.com", | ||
"aud": "32555940559.apps.googleusercontent.com", | ||
"sub": "12345678901234567890", | ||
"email": "example@example.com", | ||
"email_verified": true, | ||
"at_hash": "abcdefghijklmn", | ||
"iat": 1731940000, | ||
"exp": 1731953600 | ||
} | ||
""" | ||
import json | ||
import sys | ||
|
||
from federatedidentity import Issuer, verifiers, verify_id_token | ||
from federatedidentity.exceptions import FederatedIdentityError | ||
|
||
# Read JWT token from standard input. | ||
token = sys.stdin.read().strip() | ||
|
||
# The "aud" claim which is present in tokens created by `gcloud auth print-identity-token`. | ||
expected_audience = "32555940559.apps.googleusercontent.com" | ||
|
||
# Verify and extract the payload checking that it is issued by Google. | ||
try: | ||
verified_claims = verify_id_token( | ||
token, | ||
valid_issuers=[Issuer.from_discovery("https://accounts.google.com")], | ||
valid_audiences=[expected_audience], | ||
required_claims=[ | ||
# The 'azp' claim must be present and *also* match the expected audience. | ||
{"azp": expected_audience}, | ||
# Check that the 'sub' claim is present in the claims. | ||
verifiers.all_claims_present(["sub"]), | ||
], | ||
) | ||
except FederatedIdentityError as e: | ||
print(f"Token failed verification: {e}", file=sys.stderr) | ||
sys.exit(1) | ||
|
||
# Write the verified claims to standard output as a JSON document. | ||
print(f"Verified token claims:\n{json.dumps(verified_claims, indent=2)}") |