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

 

 

https://auth-server.com/auth

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)

https://my-app.com/redirect

code=AUTH_CODE

state=XXX

 

or errors

https://my-app.com/redirect

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

}

 

 

 

(src: Course: The Nuts and Bolts of OAuth 2.0)

This post was referenced in: