- Python 100%
| .vscode | spelling. add subject + validator. formatting. ( #3 ) | |
| tokenauth | [bug] catch bad access_token in verify_token ( #7 ) | |
| .gitignore | try / except around b64 decode of nonce_signature ( #4 ) | |
| .prettierrc | relocate to tokenauth | |
| client.py | spelling. add subject + validator. formatting. ( #3 ) | |
| clientkeys.txt | relocate to tokenauth | |
| development.txt | relocate to tokenauth | |
| issuer.py | spelling. add subject + validator. formatting. ( #3 ) | |
| LICENSE | relocate to tokenauth | |
| README.md | add .well-known/tokenauth.json ( #6 ) | |
| requirements.txt | add .well-known/tokenauth.json ( #6 ) | |
Example Authorization Token via SSH Agent
Introduction
Example of a candidate approach for API keys. This approach:
-
Uses ssh keys (via the ssh-agent) on clients to provide identity. The ssh-agent is tasked with ssh key operations - this code never touches the client's private ssh key.
-
Does not centralize any private client key. Users can make their own ssh keys and submit the public key only for identification / usage.
-
Issues a signed JWT token with issue and expirataion times, and as the ip network that the key is valid for.
-
end-users can have a shorter validity and a single address network.
-
internal users can have a longer validty and the internal subnet.
-
-
Implementation uses standard python SSH / JOSE / JWT / JWK constructs and packages.
-
Verification requires only the issuer public key and a token provided (and signed) by the issuer. The issuer is not required to be available in order to verify an issued token.
-
Auth exchange is based on OAUTH, with client_id and PKCE, but with only two counterparties - the client and the issuer.
Requirements
- SSH Agent running on the client, with the required key added.
- Issuer routes availabe to the client
Token Issue
tc = TokenClient(logger=logger)
if tc.use('SHA256:<HASH>'):
token = tc.access_token
Token Issue Internals
-
Client GETs a nonce from the issuer, also providing the client_id (the public ssh key hash), and also a state + PKCE challenge as part of the request.
-
Issuer responses with a nonce (16 bytes of random data) and the state provided by the client, all signed with the Issuer's private key.
-
Client verifies the signed response from the Issue via the Issuer's public key.
-
Client uses ssh agent to sign the nonce.
-
Client POSTS the signed nonce data, the state + PKCE verifier.
-
Issuer verifies the PKCE verifier and the signature on the signed data.
-
Issuer generates claim for the client (a dictionary) with timestamps and network, signs this with the Issuer's private key, and issues the result as a token to the client.
-
Client uses the provided token in the
Authorizationhttp header.
Token Usage
Client:
if token.valid:
headers={'Authorization': f"Bearer {token!s}"
Server:
if TokenVerifier.verify_token(token, client_ip, issuer_public_key):
Discussion
Issuer
- Provides five routes:
-
GET
.well-known/tokenauth.jsonprovides a json payload with information on the
/nonce//jwks//verifyroutes. -
GET
/nonceinitial nonce generation and private storage of the nonce, PKCE challenge, and client_id.
-
POST
/nonceverification of client's data (signature, PKCE, etc). token issue.
-
GET
/jwkscovenience function to provide Issuers public key, useful if it isn't stored on the client (or server).
-
GET
/verifyconvenience function for a client to check if their token is valid. a simple wrapper around
TokenVerifier.verify_tokenwhere the client provides the token via theAuthorizationheader.
-
Issuer private key can be generated at run-time (means all tokens are invalidated asof when a Server read the updated public key via the
/jwksroute). Or stored in an Issuer config. -
Client public key fingerprints can be stored in a configuration.
-
Issuer public key can be requested from the Issuer or stored in a configuration.
-
Issuer can produce a token outside of the client verification with:
network_access_token = await issuer.generate_access_token(datetime.timedelta(days=7), ipaddress.ip_network("10.0.0.0/16"))