The PKCE flow does not require client secret. Note that the implicit grant flow for native apps is now deprecated. In the implicit grant flow, the access token was directly issued by authorization end point. As the access token was part of redirect url, hence the implicit grant flow is not safe and is replaced by authorization code flow with pkce .(client secret is replaced by one time/one request secret) The right way for authorizing users in native apps is to perform the OAuth authorization request in an external user-agent (typically the browser).
Embedded user-agent (such as web-views) should be avoided as
- Host app has access to credentials cookies etc.
- Webviews cannot be used for single sign on as cookies will not be visible in different instance of webview loaded by another app.
Just as URIs are used for OAuth 2.0 on the web to initiate the authorization request and return the authorization response to the requesting website, Interapp URIs can be used by native apps to initiate the authorization request in the device's browser and return the response to the requesting native app.
Mobile platforms support inter-app communication via URIs by allowing apps to register private-use URI schemes (also called custom URL schemes). To perform an OAuth 2.0 authorization request with a private-use URI scheme redirect, the native app launches the browser with a standard authorization request. When the authentication server completes the request, it redirects to the client's redirection URI . As the redirection URI uses a custom scheme it results in the operating system launching the native app, passing in the URI as a launch parameter. The native app then processes the authorization response like normal.
Steps in brief
- The client app opens a browser tab, directing the user to the /authorize endpoint of the authserver with client id, redirect uri, state and code challenge.
- The user sees authorization prompt and approves the request.
- The user is redirected back to redirect uri which is customr uri and will lead to launch of native app.
- In case of spa without back end,to handle redirect , you can use a small popup, which will load the necessary oauth2 endpoint, setting a separate HTML file or a "noop" route on your SPA as redirect URL. You may then poll for that URL in the popup, so you know when to close it (user is done with oauth2 flow). Alternatively you could also save the current state of you application in browser storage, redirect to the oauth2 endpoint, and point back to your app as redirect URL. When coming back to your app, the specific route should be able to restore the saved data and resume user experience, with the addition of the oauth2 tokens.
- The native app will be able to get the auth code.
- the app exchanges auth code and code verifier with access token and refresh token.
- The refresh token can be stored in android key store (safer than shared prefrence) and access token can be saved in native app memory.
- note that pkce has no impact on how securely the tokens are stored.
- In case of spa (without backend) access token can be stored in app memory (in a js variable) and refresh token should be stored in localstorage with refresh token rotation.
- The access token is used to make api request to resource server.
Request to authorization end point
The client app opens a browser tab, directing the user to the /authorize endpoint of the authserver. The request parameters are
- client_id The ID of the application that asks for authorization.
- reponse_type : Tells the authorization server which grant to execute.
- response_type=code
- Both the authorization and token end point are used.
- authorization end point generates auth code
- auth code can be exchanged for access
- Both the authorization and token end point are used.
- response_type=token
- Indicates that access token is directly generated by authorization end point.
- token end point is not used.
- this is nothing but implict flow
- response_type=code
- redirect_uri In case of native apps, the redirection URI will be a private interapp uri which results in the operating system launching the native 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 )
- code_challenge
- In PKCE flow the client native app cannot store the client secret hence pkce flow relies on time client secret generated for the request which stored in native app memory.
- Create a random string between 43-128 characters long . This string is called code verifier. The code verifier is the one time client secret for the request
- Create the SHA256 hash (the code verifier is getting one way encrypted and only the encrypted value will be visible in browser history)
- then base64-encode the SHA256 hash.
- this url-safe base64-encoded SHA256 hash of the code_verifier is called the code challenge.
- In case of native app the native app should store the code verifier in memory so that it can use code verifier to exchange authcode with tokens. This code verifer will not be there with the malicious app which tries to execute interception attack.
- code_challenge_method : The hashing method which as used to create the code challenge.
- 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
- 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.
- query
- state
- An opaque value, used for security purposes.
- 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 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. In case of native apps the state can be saved in app memory. In case of spa , browser memory via javascript variable or browser local storage. It should be noted that the state parameter need not be hashed 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 login (if the user is not allready logged in)
- Show the consent screen to user and ask the user to allow access 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.
Response from authorization end point
This is redirect to private / custom uri and will lead to launch of the native app.
On receiving the redirect response the native app will
- Get the auth code in the query string
- Get the state value in query parameter
- Ensure that the state value which is received in the redirect response matches the one that was passed in the initial authorization request. If the state matches then client can trust the redirect response. (In case of native app executing PKCE the state would be save in app memory).
- Note that response does NOT have access token ie the access token is not in browser . This is main advantage of PKCE flow.
- Initiate step 2 (post request to token end point)
- Note that the authorization server stores the code challenge and the code challenge method so that it can use it later in the flow.
POST request to token end point
The native app will initiate a POST request to token end point to get access and/or id token with
- grant_type The grant type for this flow is authorization_code.
- code : the authorization code received in step 1 response.
- code_verifier this is the plain text string which was hashed to create the code challenge. A malicious app will not be able to exeucte this request as it will not have the code verifier
- client_id The ID of the application that asks for authorization.
The token end point responds with access token and / or id token
- The code_verfier will be hashed using code challeng method and compared to code challenge, a match would ensure that the redirect has not been received by malicious app.
- If openid is not included in scope then only access token will be returned.
- If openid is included in scope then access token and id token will be returned.
- Access token can be opaque or signed jwt token (hence self contained).
- The id token is typically jwt token which can be verified with the public key of the auth server.
- The access token should be stored by spa in session storage / application memory .
- access token is shortlived can be stored in memory (javascript variable) or session storage.
- session storage is prone to xss attack but less volatile compared to app memory.
- access token is shortlived can be stored in memory (javascript variable) or session storage.
- The refresh token should be stored in local storage.
- local storage is less volatile compared to session storage/memory.
- local storage is prone to xss attack. refresh token rotation should be used to reduce security risk.
- Note that pkce flow does NOT help in secure storage of tokens on the client storage.
- api proxy pattern can be used in to introduce a proxy component which will function like usual server side app.
- the tokens can be securely stored in proxy component.
- with this proxy back end authorization cod flow could be executed. ie app is technically no more a public client and store client secret.
- the access token and refresh token would be stored in server side against a uuid and uuid would be stored in client in a httponly secure cookie.
- when ever api needs to be invoked from spa , request will be made to proxy back end with cookie carrying uuid value which will be used to get the tokens and then proxy server call the api and pass the result to spa.
- this also solves cors issues as api call is being done by proxy.
web controller components for native apps
Both Apple (iOS9+ - SFSafariViewController) and Google (Chrome 45+ - Chrome Custom Tabs) have added a web controller that provides all the benefits of the native system browser in a control that can be placed within an application. Most importantly you get cookie based single sign on capability (unlike webviews cookies will persist when using web controller components as cookies are shared across instances) . besides you would get user experience like webviews , ie the user stays within the application. also web controller are secure like browser as application does not have access to browser data.
Attacks (CSRF/REPLAY/Authorization code interception attack)
- Authorization code interception attack (genuine user's device , malicious app)
- How it is executed :
- The attacker registers malicious app to be launched on response redirect from authorization end point. (redirect url is custom uri )
- How does PKCE block it
- The attacker must have the code verifier to exchange generated auth code with access token. ie an auth code generated by a request can only be exchanged with tokens by using the code verifier used at the time time of request. Since the request has not originated in malicious app , the attacker will not have the code verifier.(so auth code and code verifer are mapped) Without code verifier or wrong code verifier the auth code cannot be exchanged with tokens. Also note that in the initial authorization request code verifier is not sent, hashed code verifier (code challenge is sent ) so that code verifier is not leaked.
- Was the state parameter enough to block auth code interception attack? Note that state parameter helps in correlating authorization request and response as the state parameter passed in authorization request and is part of the authorization server redirect response url.
- No , state parameter cannot block interception attack as the malicious client is only interested in authcode .State comparsion for request and response correlation is responsibility of client and malicious client will simply ignore this and accept the auth code.
- How it is executed :
- auth code injection/CSRF attack (genuine user's device)
- how the attack is created
- state paramter blocks CSRF attack.
- In pkce is flow code verifier acts as one (time/request) client secret. a code challenge is sent with request and without code verifier the auth code is unusable . Can this not block csrf attack . is state required?
- IN MY OPNION YES, PKCE flow will block CSRF as PKCE generates a one time client secret(code verifier) FOR THAT particular request of the client ie a auth code generated by a request (say r1) can only be exchanged with tokens by using the code verifier sent along with request r1 . In other words an auth code can be exchanged with access tokens by using one time client secret used to generate the auth code, ie there should map of auth code and one time client secret(code verifier) . If an attacker creates CSRF attack then even if state is not specified in original authorization request, exchange of attackers auth code with tokens will fail as during generation of attackers authcode , some other secret was used.
- Auth code replay attack (attacker's device)
- state paramter will block auth code replay attack . state parameter will be stored in native app memory. on authcode replay attack , the state paramter in native app memory and response redirect url will be compared . on attackers device matching will fail as state parameter wil not be found in native app memory.
- code verifier will block auth code replay attack . Let us say an attacker is shoulder surfing.He determines the response redirect url and pastes the redirect url on his device's browser where the client app is installed or a malicious app is installed. this will not work as the acutal client app or malious app on his device will not have code verifier. without code verifier exchanging auth code with tokens is not possible. Also it should be noted that auth code can only once be exchanged with access tokens (true for most authorization servers)