Web apps are written in a server-side programming language and run on a web/appserver .The source code of the application is not public. This means that the application can safely use its client secret when communicating with the authorization server, ie the access Token is passed directly to client's webserver, without going through the user's web browser and hence access token will not be visible in browser history. Even for native app/ spa with back end this flow can be used. (Native apps use browser to initiate authorization flow).
In the following example a user (resource owner) allready logged in to webapplication newservice.com (client) wants to authorize newsservice.com access to his google api (resource server)
Sequence diagram for authorizing webapp/spa with backend (for an authenticated user)
Step 1.a (Request to authorization end point)
The authorization code flow begins with the client directing the user to the /authorize endpoint of the authserver. The request parameters are
- client_id The ID of the application (newsservice.com) that asks for authorization.
- reponse_type : Tells the authorization server which grant to execute.
- response_type=code
- response_type must be code for authorization code flow.
- authorization end point generates auth code
- auth code can be exchanged for access and refresh token by providing client secret
- response_type=token
- Indicates that directly access token is generated by authorization end point.
- token end point is not used.
- this is nothing but implict flow
- response_type=code
- redirect_uri The redirect_uri of your app, where authentication responses can be sent and received by your app.
- scope A space-separated list of scopes that you want the user to consent to (hence allowing app to get consent for multiple web APIs )
- response_mode Specifies the method that will be used by auth server to send the resulting token back to app.
- query
- Query provides the code as a query string parameter on the redirect URI.
- This is the most commonly used by Web applications.
- fragment
- Typically used for implicit grant.
- Fragments are only evaluated locally by your web browser and not included into the request to the host. (where as when your browser gets redirected by a website to a URL with a query parameter, the query string is also part of the request that your browser now sends to the host ).In case of the Implicit Grant, you typically have some Javascript application directly running in your browser. There's no need to pass any authorization code to the host.
- form_post
- 200 OK response is sent by authorization end point with response parameters embedded in an HTML form as hidden parameters.
- After the browser loads the response from auth server , body onload event will trigger a form submit and post all the data to redirect uri.
- If you're requesting just the code, you can use query, fragment, or form_post.
- If you're requesting an ID token using the implicit flow, you can't use query as specified in the OpenID spec.
- note that response_mode form_post is secure as id_token will not be present in browsing history. You can read more here
- query
- state
- An opaque value, used for security purposes.(preventing CSRF attack)
- If this request parameter is set in the request, then it is returned to the application as part of the redirect_uri.
- It can be a string of any content that you wish. A randomly generated unique value is typically used for preventing cross-site request forgery attacks.
- The state value should be put inside the user's authentication session by the client webserver( here the assumption is that user/ resource owner is allready logged in to client server.) This is probably safer than putting state in cookie. In case of auth code replay attack from attacker's device, the attacker knows authcode and state (which are part of redirect url). A sophisticated attacker could create the cookie for client domain with state value on his browser (through xss). If the state is stored in user's session it would be safer as session cookie cannot leak through browser history and is not known to the attacker . It should be noted that scripts cannot create httponly cookie but on attackers device the attacker will not create httponly coookie . Also server has no way to know if the cookie coming in request are httponly or not. In any case state is primarily for blocking CSRF attack , not replay attack.
- The state value could also be a hash of the user's session id. (in which the state value need not be put inside the session)
- In addtion the value can also encode information about the user's state in the app before the authentication request occurred, such as the page or view they were on.
- It should be noted that the state parameter need not be hashed before making authorization request as it is primarly for defending against XSRF attack where the the attacker does not have access to device.
When the authorization request reaches the authorization server the auth server will
- Ask user/resource owner to provide credentails .
- Show the consent screen to user and ask the user to allow access to 3rd party based on the scope specified in authorization request.
If the user allows then the authorization service will redirect the user to redirect uri with auth code and state
Step 1.b (Response from authorization end point)
On receiving the redirect response the client webserver server will
- Get the auth code in the query string (auth code can only be used once for fetching tokens. This protects against replay of the redirect url)
- Get the state value in query parameter
- Ensure that the state value which is received in the redirect response matches the one saved in user's session (or is a hash of user's session id depending on implementation) , this is done to prevent CSRF attack.
- Note that access token is not there in response hence not shared with browser . This is what makes the authorization code flow secure , the access token will not be visible in browser history.
- Initiate step 2 (post request to token end point)
Step 2.a (Make POST request to token end point)
The client webserver will typically initiate a POST request to token end point to get access and/or id token with
- client_id The ID of the application that asks for authorization.
- client_secret : Note that client secret is being used in server to server request (also called back channel request)
- code : received in step 1
Step 2.b (The token end point responds with access token and refresh token )
- Access token will be returned.
- Access token can be opaque or signed jwt token (hence self contained).
- Refresh token is useful if the access token is self contained.
- The access token and refresh token are both saved in the webserver.
- refresh token have long lives so have to be saved in centralized persistent store (hence available to all appserver instances)
- The access token (with shorter life) if saved in webserver memory would make the webserver stateful so could be stored in centralized memory store like redis.
- The refresh and access token could be also be saved on client httponly, secure, samesite=strict cookies which has some benefits which keeps the server stateless without the need to store tokens in central store. Note that this how ever is less secure .
- Note that access_tokens are short lived, and you must refresh them after they expire to continue accessing resources. You can do so by submitting another POST request to the /token endpoint, this time providing the refresh_token instead of the code.
Step 3 - client uses access token to interact with resource server.
Now that access_token is acquired,it can use the token in requests to resouce server web APIs by including it in the Authorization header:
When the resouce server receives the request with access token,it will make a hit to token introspection end point ( in case of opaque access token).If the token is expired resouce server will return error which should be handled by client . (the client could use the refresh token to get another access token).
note that as per the spec
The reason why the spec does not cover how the access token is validated is that the interaction between resource server and authorization server is dependent upon the architecture of the deployment. For instance the authorization service and resource server may the the same or it may be distributed.
If the token is self contained jwt token then no need to hit token introspection end point.
Attacks (CSRF/REPLAY)
- auth code injection/CSRF attack. (From the genuine user's device, the genuine user should be logged in)
- The CSRF attack can be blocked by
- sending random state parameter as part of intial authorization request.
- the state paramter is stored in user's session. (note that the 3rd party is responsible for maintaining authentication session.)
- when the response comes back from authorization server the state paramter is present in redirect response.
- the webserver compares the state parameter in response with the state parameter stored in user's session thus blocking CSRF attack. Read this detailed artcile for a better understanding of CSRF attack. https://clarifyforme.com/posts/5639071388925952/What-kind-of-CSRF-attack-is-blocked-by-state-parameter-in-OAuth
- The CSRF attack can be blocked by
- Auth code replay attack from attacker's device. (response_type is code) where attacker is logged in.
- The attacker gets access to auth code and state via shoulder surfing or he may have access to mobile device of the user, where as user has authenticated from desktop browser. The user's browser history across devices can get synched up. In both cases the auth code and state is exposed as present in front channel/ browser.
- The attacker should login to the third party application on his browser.
- The attacker pastes the redirect response url (with auth code and state) on his browser .
- If auth code can only once be exchanged with access tokens (most auth servers follow this) then replay attack will fail.
- If the auth server does not stop use of auth code twice even then replay attack will fail as
- the state stored in attacker's session and genuine user's state(exposed through shoulder surfing) will be compared and request and response corrleation will fail .