OAUTH 2.0 · LAB
Authenticate the order service with a client certificate
Run a small partner CA, issue a client authentication certificate, and plan tls_client_auth and self_signed_tls_client_auth for a print-orders client. Today, see the tenant ignore the certificate.
PlannedUses your lab tenant
The lesson
New to the labs? Start with the lab toolkit and the shared cast and names every lab uses.
Planned. The core of this lab waits on platform features that are not built yet. The planned walkthrough shows exactly how it will run; Do today is a real exercise you can do now.
- G8 Client authentication beyond `client_secret_basic` and `none`
- G27 PKI / X.509 certificates
- G57 mTLS edge (per-tenant host that requests client certificates)
Setup
Make sure
lab-print-ordersexists in OAuth > Clients (Machine to machine preset, grantclient_credentials, scopeprints.create), and that the Flow policy allowsclient_credentials. Runexport ORDERS_ID=<its client_id>.Build the partner CA and the order service's certificate. This runs today, on your machine only.
openssl req -x509 -new -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes -keyout lab-ca.key -out lab-ca.crt -days 90 \
-subj "/O=Lab Print Partners/CN=Lab Partner CA 1" -addext "basicConstraints=critical,CA:TRUE" -addext "keyUsage=critical,keyCertSign,cRLSign"
openssl req -new -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes -keyout orders.key -out orders.csr \
-subj "/O=Lab Printer/CN=orders.printer.example"
printf 'basicConstraints=critical,CA:FALSE\nkeyUsage=critical,digitalSignature\nextendedKeyUsage=clientAuth\nsubjectAltName=DNS:orders.printer.example\n' > client-ext.cnf
openssl x509 -req -in orders.csr -CA lab-ca.crt -CAkey lab-ca.key -CAcreateserial -out orders.crt -days 30 -extfile client-ext.cnf
Planned (G27): in OAuth > Client certificate authorities, upload
lab-ca.crtas a trust anchor for client certificates in this tenant only.Planned (G8): create a temporary client
lab-tmp-mtls-orderswith the Machine to machine preset, scopeprints.create, authentication methodtls_client_auth, expected subject value SAN DNSorders.printer.example. Runexport MTLS_ID=<its client_id>.
Planned walkthrough
Read the mutual TLS alias from metadata.
curl -s "$ISSUER/.well-known/oauth-authorization-server" | jq '{token_endpoint_auth_methods_supported, mtls_endpoint_aliases}'
MTLS_TOKEN=$(curl -s "$ISSUER/.well-known/oauth-authorization-server" | jq -r .mtls_endpoint_aliases.token_endpoint)
Expect tls_client_auth and self_signed_tls_client_auth in the list, and a token endpoint on a separate host.
Why it matters: "Separate addresses for mutual TLS". Only the alias host asks for certificates, so browsers on the sign-in pages are never prompted.
Request a token with the certificate and no credential in the request.
curl -s --cert orders.crt --key orders.key "$MTLS_TOKEN" -d grant_type=client_credentials -d client_id=$MTLS_ID -d scope=prints.create | jq .
Why it matters: "A certificate on both sides". Possession of the key is proved in the handshake, and client_id tells the tenant which registration to compare the certificate with.
Open Audit:
oauth.tokensucceeded forlab-tmp-mtls-orderswith the methodtls_client_authand the matched subject value, never the certificate itself.
Why it matters: "Matching the certificate to a client". The tenant records which registration value matched.
Switch the client to
self_signed_tls_client_auth. Create a self-signed certificate from the same key and register it in the client's key set.
openssl req -x509 -new -key orders.key -out self.crt -days 30 -subj "/CN=orders.printer.example"
Repeat step 2 with --cert self.crt.
Why it matters: no CA vouches for this certificate; the registration itself is the trust.
Fill in the lesson's comparison table from what you just changed: who vouches, what the registration holds, and what a renewal needs.
Do today
Inspect what you built in Setup step 2.
openssl x509 -in orders.crt -noout -subject -issuer -ext subjectAltName,extendedKeyUsage,basicConstraints
openssl verify -CAfile lab-ca.crt -purpose sslclient orders.crt
Confirm the lesson's three details: a private issuer, "TLS Web Client Authentication", and the SAN orders.printer.example. The chain check prints orders.crt: OK.
See that the tenant's host never asks for a client certificate.
openssl s_client -connect ${ISSUER#https://}:443 -servername ${ISSUER#https://} </dev/null 2>/dev/null | grep -i "client certificate"
It prints No client certificate CA names sent.
Present the certificate to the token endpoint anyway, with only
client_idin the body, forlab-print-orders.
curl -s --cert orders.crt --key orders.key "$ISSUER/oauth/token" -d grant_type=client_credentials -d client_id=$ORDERS_ID -d scope=prints.create | jq .
The answer is 401 invalid_client, and Audit shows oauth.token rejected unsupported_auth_method for lab-print-orders. The certificate was never requested, so the tenant saw a confidential client with no credential.
In OAuth > Metadata, try to add
mtls_endpoint_aliasesas an extension field. The tenant refuses to save it: advertising an alias it does not run would send clients' token requests somewhere else. Leave the metadata unchanged.
Break it
These run once the gaps close.
Call the alias with no
--cert. Expect401 invalid_client, the same answer as any failed client authentication.Issue a second certificate from your CA with SAN
reports.printer.exampleand use it forlab-tmp-mtls-orders. Expect401 invalid_client: the subject does not match the registration.
Check your work
Today, Audit shows oauth.token rejected unsupported_auth_method for lab-print-orders, and the metadata change was refused. Once the gaps close, Audit shows oauth.token succeeded with tls_client_auth and with self_signed_tls_client_auth, and the two Break it refusals.
Cleanup
Once the gaps close, delete
lab-tmp-mtls-ordersand the uploaded CA.Keep
lab-ca.crt,lab-ca.key,orders.key,orders.csr,orders.crtandclient-ext.cnffor the next two labs, then delete them.
Missing infrastructure
G8: the
tls_client_authandself_signed_tls_client_authmethods with per-client expected subject settings.G27: tenant trust anchors for client certificates and X.509 chain validation.
G57: a per-tenant alias host where the edge requests a client certificate and passes it, or its SHA-256 thumbprint and verification result, to the tenant. Whether per-tenant private CAs can be checked at the edge or must be checked by the tenant needs investigation before design.
Once these exist, the Planned walkthrough runs as written.