From my understanding, the advantage that Authorization Code Flow has over Implicit Flow is that with ACF, the access token gets sent to a server side app rather than to a browser app. This makes the access token much harder to steal, because the access token never reaches the browser (and is thus not susceptible to a Cross Site Scripting attack).
I would have thought that PKCE would try to solve this issue. But it does not. The access token is still sent to the browser. Hence it can still be stolen.
Is there something I am missing here?
Many thanks.
Authorization Code Flow (PKCE) is considered superior security to the previous solution of Implicit Flow:
With implicit flow the access token was returned directly in a browser URL and could perhaps be viewed in logs or the browser history
With Authorization Code Flow this is handled better, with reduced scope for exploits:
Phase 1: A browser redirect that returns a one time use 'authorization code'
Phase 2: Swapping the code for tokens is then done via a direct Ajax request
PKCE also provides protection against a malicious party intercepting the authorization code from the browser response and being able to swap it for tokens.
Both are client side flows and their reason for existing is to use access tokens in public clients. Authorization Code Flow (PKCE) is the standard flow for all of these:
Single Page Apps
Mobile Apps
Desktop Apps
In the SPA case the token should not be easily stealable, especially if stored only in memory as recommended. However, there are more concerns when using tokens in a browser, since it is a dangerous place, and you need to follow SPA Best Practices.
In the browser case there are other options of course, such as routing requests via a web back end or reverse proxy in order to keep tokens out of the browser, and dealing with auth cookies in addition to using tokens.
I think your are right. The tokens are not in a http-only cookie, and are therefore accessible by a malicious script (injected via an XSS attack). The attacking script can read all tokens (after a successful and normal auth flow) from local storage (or wherever they got put) and use them.
I think CORS protections should prevent the malicious script from sending the tokens out to an attacker directly, which would be a devastating failure, as this potentially includes a long lived refresh token. Therefore, I suspect configuring CORS correctly is super critical when using these local-client based flows (by local-client I mean a browser, mobile app, or native PC app).
In short, these local-client flows can be made secure, but if there is an XSS attack, or badly configured CORS, then those attacks can become extremely dangerous - because the refresh token could potentially be sent to the attacker for them to use at will in their own good time, which is about as bad as an attack can get.
Related
Below I have tried to justify my question, "Does Proof Key for Code Exchange (PKCE) work without TLS? I believe I am missing understanding part of the spec and was hoping for some direction. I also have a second question, Does PKCE only relate to using Cookies to store the auth token (not mentioned in spec).Please help me identify the misinformation or lack of information in my comments below.
rfc7636's introduction section describes an attack authorization code interception attack on public clients. It states,
the attacker intercepts the authorization code returned from the
authorization endpoint within a communication path not protected by
Transport Layer Security (TLS) ...
The Introductions PreCondition section item 4 indicates that TLS not protecting the response .
The Introductions "To mitigate this attack" paragraph the states
This works as the mitigation since the attacker would not know this
one-time key, since it is sent over TLS and cannot be intercepted.
and
this extension utilizes a dynamically created cryptographically random
key called "code verifier".
RFC6949 mentions attacks around the use of cookies; however, rfc7636 does not specify pertaining only to cookies or local storage of the Auth token. Therefore is seems it would resolve an attack on the request if the auth token was also stored dynamically. Is this the case?
PKCE is used as a proof that the party which initiated authentication (via a browser redirect) is the same party completing it (via an HTTP POST).
It works without TLS, as in steps 4 and 8 of my blog post, where the code verifier in step 8 must match the code challenge in step 4.
Cookies are not directly related to PKCE. However, in a browser based app without PKCE or a client secret, malicious browser code only needs to send the authorization code to get tokens. If tokens are stored in cookies then the malicious code can only perform session hijacking.
PKCE was originally introduced for public clients that cannot use a client secret. These days, it is recommended for all clients that use the code flow, including those with a client secret. And of course TLS should also be used.
In Rails API applications we don't have out-of-box CSRF protection. In particular, storing access tokens (JWT for example) in the localStorage is not recommended, it is recommended to store in cookies (with httpOnly flag, SameSite, and etc.). But this time we're vulnerable to a potential CSRF attack. In a full-stack generated Rails app, a CSRF token is generated and embedded every time we open a form. But, I don't know and couldn't find anything how we protect against CSRF using tokens in Rails API apps. Do we have best practices or could anyone suggest an approach? I use access and refresh JWTs.
This is a usual tradeoff of API design, and you can choose from several different approaches, with different risk profiles.
You can store the access token in localStorage or sessionStorage, accessible to javascript, and accept the risk. The risk obviously is mostly around cross-site scripting (XSS), because this way javascript will have access to the token, and in case of XSS, it can be accessed by the attacker leading to session compromise. If talking about an API, responses should have the content type set to application/json, which makes the API itself protected from XSS in modern browsers. However, that does not mean the client (presumably a single page javascript app) is also protected, that can easily be vulnerable and leak the token. Some frameworks are better protected by default against XSS, some are not so much, and you might have checks like static scans in your SDLC that give you a level of assurance that might allow you to accept this risk. Also if your SPA needs to send the token to multiple origins (different api endpoints), you don't really have another option. In this case the token can be sent as a request header, and CSRF is not an issue.
Or you can exchange XSS for CSRF, by storing the token in a httpOnly cookie. This is generally considered more secure, because CSRF in general is a lower risk vulnerability (but still significant ofc). In that case you will not be able to send the token to different origins, but XSS will also not have access. This does not eliminate XSS for the whole application, but at least the token will be secure. The cost is now you will have to deal with CSRF. One way to do so is the samesite attribute to cookies. Using that for the token cookie will prevent most cases of CSRF, but it is a UX tradeoff, users of some browsers will not be protected, and some cases might be missed when using the lax option for samesite (like when a GET request changes state). Only having samesite as the protection will likely also be flagged in a penetration test for the reasons above.
If based on the above you decide to have more protection, you can implement something like double submit, and still keep it stateless, which these APIs many times aim to be. In case of double submit, you generate a random value, set it as a cookie (either directly on the client, or by a response from the server), and copy the same value from the cookie in a request header. The server only has to compare the value from the cookie to the one from the request, if they match, the request is ok. The reason this works is because an attacker on their own domain (origin) cannot set or read cookies for the victim application domain, this is ensured by the same origin policy of browsers.
A somewhat different approach might be applying a message authentication code (like HMAC) computed from the whole request and a shared secret (like the API key), and checking that on the server, but this is a can of worms, it's easy to have unprotected fields not covered by the HMAC, the server needs to have access to plaintext api keys so it can compute the hmac and so on - it's not at all straightforward to get this right).
Note that if the client app is vulnerable to XSS, that negates any CSRF protection as the attacker will have a way to get any secret from the client and with that, perform any request, with any computed field (like a valid token).
Since PKCE is now the recommended method of authorisation over the implicit flow, i'm looking for best practice on handling code verifier and recommendations on how this might be done. On high level PKCE Authorisation flow consist of:
Generate code_verifier on client side
Generate code_challenge from (1)
hit /authorise with code_challenge which redirect to select idp and in callback there's a code
use code from (3) along with code_verifier to exchange for access token
Question is, in step 3, before the application redirect to authorisation server and then the idp, one has to store the code_verifier somewhere. Where is that somewhere?
Seems like libraries like okta-oidc-js store the code_verifier in sessionStorage. Doesn't that expose you to XSS attack? i.e. if i was store the code_verifier in sessionStorage before the application goes into the Authorisation flow and redirects, on the callback, what stops some rouge extension from reading the code from the url and code_verifier from sessionStorage? Combination of which can be used to exchange for a access token.
What you describe is the standard SPA way of doing things - it could potentially be abused by malicious code, but there is some protection in the fact that an authorization code can only be used once and that the verifier is not stored for long.
A related XSS attack is to run a complete OAuth authorization redirect + code exchange on a hidden iframe - there is no protection against that, regardless of whether a back end or client secret is involved.
If you want to be strict about security, the emerging trend is more of a back end for front end approach, where the back end is a 'Proxy API' running at https://api.mywebdomain.com
The result of OAuth authorization is a same site cookie issued by the API, to prevent the above iframe attack
The SPA can then either use the auth cookie to get an access token or double hop API requests via the proxy API.
There is a good recent video on SPA security here that discusses these threats in further depth. The browser is a difficult place to implement security and redirects come with risks.
It is still recommended to separate Web and API concerns however - eg the above proxy API should not get in the way of a company wanting to deploy their SPA via a content delivery network.
LOGIN DANCE
In my opinion the preferred approach is summarized below, for full control and no issues with recent browser changes:
SPA calls a URL such as https://api.mywebdomain.com/login/start, which writes an HTTP only encrypted cookie for .mywebdomain.com containing the state and code_verifier, and also returns the authorization request URL
SPA then does the redirect itself, and saves page location / state to session storage beforehand if needed
SPA then receives the response URL with code and state, then POSTs them to a URL such as https://api.mywebdomain.com/login/end. Afterwards the SPA can restore its page location and state, so that usability is good.
API completes the login by verifying the state against that in the state cookie, then using the code_verifier from the state cookie. The result of all of this is to write an auth cookie (containing a refresh token) that could not be abused on an iframe.
I agree with Gary's approach, with one change.
The response url with code and state does not need to be intercepted by SPA and converted into another POST call to the BFF (backend for frontend).
If secure cookies were set on the browser at the beginning of the flow containing state and code verifier, the response url can land directly on the BFF which will then have all the parameters available for performing the code exchange (state and code as url parameters, code_verifier from cookie)
While learning how OAuth2 works, I cannot figure out why there is a separate step to retrieve Access Token?
A separate step means:
an extract HTTP request
passing Client Secret in the URL
I'd expect the Access Token to be generated in the "authorization" step, encrypted with using the Client Secret, and returned back when redirecting to the Callback URL. Then the client application would decrypt it and use it straight await without issuing an extra HTTP request.
I guess there are some reasons behind having an extra step, and I'm just not aware of them. I hope you can explain the reasons in your answer.
I'm assuming you're talking about the Authorization Code flow and not the Implicit flow, which does return a token directly.
The Authorization Code flow is designed to work with potentially unencrypted servers via a callback URL (this was designed years before Let's Encrypt and the relatively recent encrypt-everything push). Thus, the URL could be intercepted by any intermediate routers/proxies, and sending an access code as part of the callback URL in that environment is a Bad Idea.
So instead, the authorization code is sent. Then the client exchanges the authorization code along with its client secret for an access token. The authorization code can be intercepted, but in the Authorization Code flow, the client secret is actually secret and not known outside your server, so any intercepted authorization code is useless on its own.
These days, encryption is common and free, and unencrypted flows are strongly discouraged. The extra call remains part of the Authorization Code flow for historical reasons.
I am developing an oAuth2 server and I've stumbled upon this question.
Lets suppose a scenario where my tokens are set to expire within one hour. On this timeframe, some client goes through the implicit auth fifty times using the same client_id and same redirect_uri. Basically same everything.
Should I give it the same accessToken generated on the first request on the subsequent ones until it expires or should I issue a new accessToken on every request?
The benefits of sending the same token is that I won't leave stale and unused tokens of a client on the server, minimizing the window for an attacker trying to guess a valid token.
I know that I should rate-limit things and I am doing it, but in the case of a large botnet attack from thousands of different machines, some limits won't take effect immediately.
However, I am not sure about the downsides of this solution and that's why I came here. Is it a valid solution?
I would rather say - no.
Reasons:
You should NEVER store access tokens in plain text on the Authorization Server side. Access tokens are credentials and should be stored hashed. Salting might not be necessary since they are generated strings anyway. See OAuth RFC point 10.3.
Depending how you handle subsequent requests - an attacker who knows that a certain resource owner is using your service and repeat requests for the used client id. That way an attacker will be able to impersonate the resource owner. If you really return the same token then at least ensure that you authenticate the resource owner every time.
What about the "state" parameter? Will you consider requests to be the "same" if the state parameter is different? If no then a botnet attack will simply use a different state every time and force you to issue new tokens.
As an addition - generally defending against a botnet attack via application logic is very hard. The server exposing your AS to the internet should take care for that. On application layer you should take care that it does not go down from small-bandwidth attacks.
You can return the same access_token if it is still valid, there's no issue with that. The only downside may be in the fact that you use the Implicit flow and thus repeatedly send the - same, valid - access token in a URL fragment which is considered less secure than using e.g. the Authorization Code flow.
As a thumb rule never reuse keys, this will bring additional security in the designed system in case of key capture
You can send different access token when requested after proper authentication and also send refresh token along your access token.
Once your access token expires, you should inform user about that and user should re-request for new access token providing one-time-use refresh token previously provided to them skipping need for re-authentication, and you should provide new access token and refresh token.
To resist attack with fake refresh token, you should blacklist them along with their originating IP after few warnings.
PS: Never use predictable tokens. Atleast make it extremely difficult to brute force attacks by using totally random, long alpha-numeric strings. I would suggest bin2hex(openssl_random_pseudo_bytes(512)), if you are using php.