Plain HTTP
The same flow as raw requests, for a language with no library available.
Do this when there is no ready library for your language. If there is one, use it: verifying a token signature by hand is the last thing worth writing yourself.
1. Send the user
https://auth.my/auth
?client_id=YOUR_CLIENT_ID
&response_type=code
&redirect_uri=https%3A%2F%2Fexample.com%2Fcallback
&scope=openid+profile+email
&state=RANDOM_STRING
&nonce=RANDOM_STRING
&code_challenge=BASE64URL(SHA256(verifier))
&code_challenge_method=S256
verifier is a random string of 43–128 characters. Keep it, along with
state and nonce, until the user comes back.
2. Handle the return
The user returns to redirect_uri?code=…&state=….
Compare state with what you sent. If it differs, stop — this is not
your flow.
3. Exchange the code
curl -u "CLIENT_ID:CLIENT_SECRET" https://auth.my/token \
-d grant_type=authorization_code \
-d code=RECEIVED_CODE \
-d redirect_uri=https://example.com/callback \
-d code_verifier=ORIGINAL_VERIFIER
{
"access_token": "…",
"id_token": "…",
"refresh_token": "…",
"token_type": "Bearer",
"expires_in": 3600
}
Public clients have no secret — instead of -u, send client_id in the
request body.
4. Verify the id_token
This is not a formality. An unverified id_token is a claim about someone's
identity submitted by anyone at all.
Check every one of these:
| What | How |
|---|---|
| signature | with the key from https://auth.my/jwks matching kid in the header |
iss | exactly https://auth.my |
aud | your client_id |
nonce | equals the one you sent |
exp | not expired |
Use a vetted crypto library for your language. A hand-written signature check is a source of vulnerabilities, not of savings.
In particular: never trust the alg header blindly and never accept
alg: none. You decide the expected algorithm, not the token.
5. Afterwards
curl -H "Authorization: Bearer ACCESS_TOKEN" https://auth.my/me
Refresh, sign-out and revocation are covered in Tokens and Sign-out.