OAuth for Server-Side applications (oauth2 secure backend flow)
March 25th, 2023
toc
secure flow original without PKCE
secure flow with PKCE
detailed flow examples (with PKCE)
secure flow original without PKCE
state param prevents CSRF
1) [front-channel] browser -> my-app
GET my-app.com/login
- generate random value: state
< redirect to auth-server.com/auth (response_type, client_id, redirect_uri, scope, state)
2) [front-channel] browser -> auth-server
GET auth-server.com/auth (response_type=code, client_id, redirect_uri, scope, state)
< redirect to my-app.com/redirect (code, state)
3) [front-channel] browser -> my-app/redirect
GET my-app.com/redirect (code, state)
- verifies state
4) [back-channel] my-app -> auth-server
POST auth-server.com/token (grant_type=authorization_code, code, redirect_uri, client_id, client_secret)
< json (access_token, refresh_token, ...)
_) [back-channel] my-app -> auth-server
POST auth-server.com/token (grant_type=refresh_token, refresh_token)
< json (access_token, refresh_token, ...)
secure flow with PKCE
Proof Key Code Exchange (PKCE)
PKCE prevents "Authorization Code Injection"
state param prevents CSRF,
but PKCE also covers that,
so now often used to store app-specific state (eg. which page to redirect to after login)
BUT only when auth-server supports PKCE
if not, you need it random for CSRF
1) [front-channel] browser -> my-app
GET my-app.com/login
- generate secret -> generate hash: code_challenge
- put anything in: state
< redirect to auth-server.com/auth (response_type, client_id, redirect_uri, scope, state, code_challenge, code_challenge_method)
2) [front-channel] browser -> auth-server
POST auth-server.com/auth (response_type, client_id, redirect_uri, scope, state, code_challenge, code_challenge_method)
< redirect my-app.com/redirect (code, state)
3) [front-channel] browser -> my-app/redirect
GET my-app.com/redirect (code, state)
- verifies state
4) [back-channel] my-app -> auth-server
POST auth-server.com/token (grant_type, code, redirect_uri, code_verifier, client_id, client_secret)
- verifies code_verifier matches previous code_challenge
< json (access_token, refresh_token, ...)
*) [back-channel]
POST auth-server.com/token (grant_type, refresh_token)
< json (access_token, refresh_token, ...)
detailed flow example (with PKCE)
Code Verifier (Secret)
a random string you create (between 43-128 chars long)
code_verifier = random_string(43, 128)
Code Challenge (Public Hash)
code_challenge = base64url(sha256(code_verifier))
// base64url != base64
response_type=code
client_id=CLIENT_ID
redirect_uri=REDIRECT_URL // must match configured on auth-server
scope=foo
state=XXX // random value for CRSF (not needed if auth-servier does PKCE)
code_challenge=XXXXXXXX
code_challenge_method=S256
auth server redirects back (using your REDIRECT_URL)
code=AUTH_CODE
state=XXX
or errors
error=access_denied
state=XXX
get first token
POST https://auth-server.com/token
grant_type=authorization_code
code=AUTH_CODE
redirect_uri=REDIRECT_URL // the one you used in the original request
code_verifier=VERIFIER_STRING
// either in Basic Auth header OR in post body (depending on server)
client_id=CLIENT_ID
client_secret=CLIENT_SECRET
get token with refresh token
POST https://auth-server.com/token
grant_type=refresh_token
refresh_token=REFRESH_TOKEN
// either in Basic Auth header OR in post body (depending on server)
client_id=CLIENT_ID
client_secret=CLIENT_SECRET
{
"token_type": "Bearer",
"access_token": "xxx"
"expires_in":3600,
"scope": "foo",
"refresh_token": "yyy" // maybe
}
This post was referenced in: