Sending session key in the header vs HTTP-Only cookie - security

I would like to know the best option for sending session key in my system.
In my system, there is an API server that is used by web browser, command line interface and desktop apps. It authenticates the user by looking at the Authorization HTTP header.
Currently, the browser stores the session key in the localStorage and attaches it in the Authorization header for requests that require login. However, it was pointed out that a more secure way to store secrets such as session keys is using HTTP-Only cookies.
The problem is that the web browser client will not be able to read the HTTP-Only cookie and put the session key in the HTTP header.
Given the situation, I am thinking about extending the API server to use either one of Authorzation header or cookie to authorize users. Is this a feasible option, and are there alternatives?

You are right, Cookies and Authorization headers are not compatible out of the box. As you pointed out, you are looking at two use-cases: one for browser usage and another for API (cli, desktop app).
If you want to support both via a single authentication scheme, you will need to work a bit more. As a good rule of thumb, browsers work well with cookies and its easy to set it up securely. You should opt for cookie-based session management with browsers.
Given the situation, I am thinking about extending the API server to
use either one of Authorization header or cookie to authorize users. Is
this a feasible option, and are there alternatives?
Yes, this is feasible, it will make your browser use-case more secure. As for alternatives, I put together a Web Authentication Guide that will greatly assist you in exploring your options.

Related

How worried should I be about opening up a JWT to an XSS vulnerability?

I am building a node.js web application with react for the the GUI and graphQL served with Apollo for the back-end connecting to a RDS (MySQL) instance on AWS.
I am authenticating users and then returning JWTs. I have it figured out on how to renew/expire tokens, but now I am being faced with the question where to save it on the client side when a user visits the site...
There are two main concepts with a third being a hybrid model. 1) Store it as localStorage with JavaScript as described on HowToGraphQL 2) Store it in a Cookie with http-only set to true as described in the afore mentioned article as a cationary reference to Randall Degges
There is another alternative to store it in memory only on the client side but then a user would have to login every time the page is refreshed as it would not be persistent anywhere.
Concept 1 is vulnerable to XSS only if there is another XSS vulnerability already exploited. But it is secure to the site only so only scripts running on the site can access it and not scripts on any site. There it a lot of security talk that it should not be stored this way even though it is the common way because a developer cannot trust EVERY JavaScript script they are running on their site and there may be one that reads the localStorage and then sends it offsite.
Concept 2 removes the XSS vulnerable by declaring the http-only to only make it accessible to the server at your site. The problem here lies in that then a separate method has to be created to use the same backend authentication for other uses such as a standard API (for native apps or other sites) where the JWT is sent in the header over https where it is stored securely on another server.
So I researched and found this hybrid method described by Ben Awad 3) use a request token and a refresh token. The request token can then act normally for the standard API but then also on our react app site we can store it only in memory and store a refresh token in a cookie to send back a request token when users refresh or close and reopen browsers.
So theoretically, the best solution is Concept 3 which solves all of the concerns, but it is of course more complicated to setup.
My question: How worried should I be about opening up a JWT to an XSS vulnerability? It is something that down the road I would do the long way when I have more time, but I am pushing for a deadline. My site will be lesser known and not something like Facebook or Sales-Force that hackers would necessarily target. My site is not storing Credit Card data or other highly sensitive data other than a basic CRM and task list. If my site was open to XSS through other code, wouldn't the entire authentication process be vulnerable through keylogging scripts or the likes without even knowing the JWT. I feel like I would be doing a lot of extra work to secure against a possible threat that if occurred, the entire system would be compromised already.
If you are comfortable with your site to not work on Internet Explorer and some older versions of the major browsers, you can take advantage of a new cookies property, called Same-Site (to be more precise, the site will work but the cookie will not be secure).
By defining a cookie as HttpOnly, you are immediately secured from XSS attacks, but you leave yourself open to CSRF attacks.
Now by defining the cookie to have the property Same-Site=Strict, the cookie will be only sent through Http calls and only if the domain matches your site's domain. So for example, if someone creates a form in another site and tries to perform a post request to your own site, the cookie will be never sent.
If you want the cookie to be passed only on GET requests, you can set the Same-Site property to Lax but as you mentioned.
You can find more info about this feature in the following link under the SameSite cookies section:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies
You should also check the browser compatibility of the feature by using the following link:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#Browser_compatibility
This is the issue I spent a lot of time on. How to store the authorization token securely. People have different strategies in dealing with this so I will share what works for me. Users of my apps were targeted by different attacks, all of them where unsuccessful in stealing anything so far. None used XSS.
Here is what I do
In the end I opted for storing authorization token in local storage. Applications that I work usually have WebSocket connections on top of HTTP routes and I want the token to be saved in one place and act as a single source of truth. They are all web applications running in the browser. Most of the applications I build use JWT.
Why I do it like that
First why I don't use refresh tokens. If they are saved the same way as the actual authorization token would be saved negates the reason for refresh token to exist since the attacker can use the refresh token to get the authorization one.
Storing the token in cookies gives no benefits over local storage assuming that the app is secured against attackers being able to inject JavaScript into your app, mostly through forms and api on your app. Make sure all user inputs are JS injection safe. On top of that with cookies there are issues when using WebSockets that you must go around.
There is also the point of one of the accounts being hacked and you want to invalidate that token as soon as possible. JWT by default has no mechanism of being revoked. Implementing this feature negates the scalability of JWT because checking the JWT would require a call to the database to know if that user can do the specific action. There are 2 ways you can go about this. One is just check the user data if the user is frozen from the database, it is less scalable because of the call but if you already pull the user data in a middleware it is good enough TM. Other is to pull the the "is the user frozen" data from the database just when making changes to the database or when the call from the client is important.
In summary
I would store the token in local storage. Secure the app from code injections. And make a kill switch for the accounts if they get compromised in any way.
EDIT THANKS TO THE COMMENTS BY #JerryCauser
It is more secure to keep your token in a secure http only cookie. Don't expect a storage mechanism choice to automatically save your users from being hacked. There are ways to hijack sessions and other exploits including users using web extensions and approving their request to read protected data.
For the example of the betting website below, you wouldn't require user to write their password (or approve the request via automated email) every time they place a bet, but you would every time they want to take a withdrawal for example.
I use local storage because even if it happens for the token to be stolen, or another person got to your user's laptop (like a kid for example) you should never let the account do critical tasks without approval.
There is no magic bullet of anti hack protection. Try your best to keep your users safe with common sense.
EDIT AS ANSWER TO THE COMMENT FROM THE ASKER #amaster
If you are making a trip to the database on every call, maybe JWT is not the best solution. Point of JWT is to have signed claims and the id of the user without calling the database. In this case, maybe opt in for sessions instead of JWT.
Before I proceed with my answer, you may want to check out OWASP for a set of general guidelines regarding XSS and CSRF since you've mentioned cookies.
Cedomir already covered a good deal of the points with storing JWT client side. One thing that's worth mentioning is that if you have Third-Party scripts running in your web app, they also have access to the Storage API. So if a script you had loaded were to be hijacked, they could conceivably steal the token there. As for XSS with inputs, if you make sure to escape every possible user input, then that is largely mitigated as an attack vector. But you only have to screw up once for someone to take advantage of the hole and steal the JWT at that point. (Refer to this blog post for more details)
Now, if you instead store the JWT in a Http-Only, then you largely sidestep the XSS issue as you've already noted. However, now you introduced a new problem, that being Cross Site Request Forgery. Since cookies are sent with every request, a malicious actor could set up a website to make a fraudulent request on behalf of user and execute actions without the user's consent. Now I won't cover the mitigation in detail here as OWASP and other places have done a pretty good job already, but the short of it can be summed up by installing the most popular and well-maintained Anti-CSRF package for your language :-)
As for invalidating the token as Cedomir brought up, having that mechanism can be quite useful. However, to implement it does mean you give up some of the benefits of using JWT gives you. Whether you store the current JWT assigned to user and validate that or a unique key used to sign the JWT for each user, you now have user state to keep track of, eliminating one of the reasons to use JWTs. Depending on your application, you will need to weigh that tradeoff. A much simpler way could be simply to have short-lived tokens so that any token that is stolen potentially won't have a very useful lifetime. However, as you probably recognize a short lifetime would be a potentially a very annoying user experience. You could have your website periodically poll the server for a new token while your user continues to use the website as a way to improve the experience. You can also balance your security concerns with the lifetime of the token, like a 15 minute token lifetime for a e-commerce app vs. a hour or more for a social application.
I would however advise against the use of a refresh token, at least for a Browser-Based Web App. Typically speaking, the browser is just not considered capable of securing sensitive secrets. By using a refresh token, you're just deferring the stealing of credentials to another layer as by the nature of the refresh tokens, they're 1) long-lived and 2) effectively used as credentials to obtain more JWTs. So if the refresh token were to be stolen, an attacker can just get more valid JWTs on behalf of a user. If you have a mobile or desktop app, you have mechanisms you can use to securely store refresh tokens and this advice does not apply.
...Or you could just use sessions ;-)
When logging in on server set JWT token and a random csrf token in the httpOnly cookie
Also send this csrf token in body response of login back to client
On every future request from client send this csrf token via some header (eg. X-CSRF-TOKEN)
On the backend verify if the csrf tokens coming through the cookie and x-csrf-token are the same.
Then verify your JWT token and continue with your app logic.
Putting JWT token in httpOnly cookie prevents XSS attacks, validating CSRF token prevents CSRF attacks. Double sending csrf token in both cookie and header avoids storing stuff in the backend database.
XSS check
CSRF check
Stateless auth check
Auth doesn’t have to be over complicated. If you have clients that only want to pass JWT token in some header other than cookie then it’s better to just make a separate api endpoint for those programs.
While the question is not actually about OAuth / OpenID Connect I still think you can learn a great deal by checking out this Internet-Draft: OAuth 2.0 for Browser-Based Apps (Best Current Practice)
To sum it up: there simply is no secure way to store an access token on the client. If you develop only the frontend you pretty much have to use and store a token on client side - not because it's great but because you have no other choice. However, if you do have full control over Frontend and Backend you do have that choice and should think about using the same domain for both and use a session cookie as described in the Internet Draft. Basically the React application never even sees the acesss token, because your backend is serving a http page and handling the authentication directly, with the final step being a redirect back to your frontend while setting the session-cookie.
A potential XSS attack is pretty bad as it is and you should be careful not to introduce a vulnerability. The thing is: with the JWT-approach a XSS vulnerability leads pretty much to the worst-case scenario: the attacker is able to steal the user authentication and can impersonate the user - this is basically session hijacking.
The same attack against a regular session-cookie simply does not have the same impact (as long as the cookie uses the HttpOnly Flag which is highly recommended). Even though the vulnerability enables arbitrary JavaScript Code to run on the machine (which is really bad obviously) it's still a lot harder for the attacker to do some damage. He is not able to hijack the session in this case, because he is unable to read the cookie.
Just use HTTP only + SSL only cookies to save your JWT. It will make almost impossible to stole user's jwt via a soft or any type of code injections.
Someone said here, what it is no diff between LocalStorage and Cookies. He is not correct, bcs third party libraries and chrome extensions can easily stole LocalStorage data. But they cannot stole HTTP only cookie.
It will protect against any known and most likely new types of attacks.
JWT itself is completely protected. Just don’t store something there that could compromise your architecture or something like that (do not put a hashed password for example)
Upd: Good article about best practices for JWT strategy: https://ducktypelabs.com/5-mistakes-web-developers-should-avoid-when-using-jwts-for-authentication/

Can I securely store an OpenID Connect id_token and access_token in a cookie if it's marked as HTTP only?

I am integrating a legacy application (an ASP.NET MVC 4 app) with OpenID Connect. Once I obtain the id_token and access_token from my OIDC provider I need to store them. In typical fashion they have to be sent 'over the wire' from the client side to the server side because the server side must process the id_token to determine which user made the request. The access_token is not processed by my application. It's just stored in my application until I need to make a request to an API that requires JWT Bearer authentication.
The way I see it is that the id_token and access_token are sent from client and server either way - whether it's an a header or a cookie. Can I store the id_token and access_token securely in a cookie if it's marked as HTTP only?
Edit:
I should add a little more information about my scenario.
1) My application always uses HTTPS, and all cookies are marked as secure. This removes MITM (Man In The Middle) vulnerabilities
2) Every PUT, POST and DELETE request uses ASP.NET's anti forgery token classes. This protects against XSRF.
3) All input is escaped and sanitized using ASP.NET libraries which removes XSS vulnerabilities.
4) The cookie that would contain the id_token would be marked as http only, removing the ability to read and access the cookie from the client side.
You should probably not store the tokens in cookies. Ideally the access token would be stored in memory on the client. This way they aren't sent automatically with requests to the server which is why there are risks involved with cookies. Anywhere else could open you up to potential vulnerabilities.
The RFC 6819 specification, titled "OAuth 2.0 Threat Model and Security Considerations" touches on the risks and vulnerabilities around OAuth tokens. Specifically, I would recommend reading the following sections:
4.1.3. Threat: Obtaining Access Tokens
4.4.2.2. Threat: Access Token Leak in Browser History
5.1.6. Access Tokens
In applications I have written the tokens have been stored in local storage and in memory.
I'd recommend reading through the OAuth 2.0 specification so you know the risks involved when using OAuth 2.0.
Please don't count on that, HttpOnly is a flag that tells the browser that this cookie should not be accessed by client side scripts and it is true only if the browser supports it.
You can find more info here: https://www.owasp.org/index.php/HttpOnly
Also I suggest to dive a little in the OWASP web site as they have documents regarding best practices for problems like the one you listed.
You can see if your browsers support HttpOnly here: https://caniuse.com/?search=httponly
As of 2021, 95% of browsers support it.

Why can't I store an oauth refresh token in the browser?

I want to store an oauth refresh token in the browser. The reason I want to store it there is so that the app can refresh the access token and let the user continue their session uninterrupted. I also want to eliminate the need for any kind of cache on the server to store the tokens, thus making it stateful.
I'm told that storing the refresh token in the browser is wrong because it's insecure.
I think it's OK because:
The tokens would be stored in httpOnly, secure session cookies so they shouldn't be vulnerable to XSS or man in the middle attacks and they will be discarded when the user closes their session.
All communication to the server is done via HTTPS
The refresh token can be invalidated if suspicious activity is detected
Most importantly you can't use the refresh token unless you know the client secret which would be known only by the server.
Am I wrong to think it should be OK? Please explain why!
Storing the tokens in an httpOnly, secure cookie is probably the best you can achieve security-wise. The problem sometimes is that an httpOnly cookie is not good enough due to other (non-security) reasons as Javascript obviously does not have access (that's the point). So people sometimes want to store tokens in other browser stores like localStorage, or slightly better, in JavaScript objects, both of which are significantly less secure than an httpOnly cookie (but still may be good enough for some applications).
Storing the token in an httpOnly and secure cookie makes it pretty much equivalent to a session id, and its security will also be the same in this respect (obviously other aspects may be different).
Actually you can store your token in the browser, you just need to know which store mechanisms fits better with your solution. For example in the local storage is the least safe of all, if you have the backend and your Single Page App at the same domain I would recommend you using the cookies.
Auth0 website has some recommendations about it:
We recommend using the Auth0 Single Page App SDK. The Auth0 SPA SDK
handles token storage, session management, and other details for you.
For further details click here.

Authorization (and security, in general) for web and mobile apps of the same service

Help me to understand how to implement proper security for web and mobile apps, which would be enough for my case.
What I have:
Backend. Some sort of Stateless REST API, which consumes and produces JSON text. Does not store any kind of state.
Web application. Main portal to the service functionality.
Mobile applicaiton. Provides a reduced a set of functions to users of the service
I am not going to store any state on the backend. Instead, I am going to delegate this to both mobile and web browser applications.
Now here comes the question of security. How do I properly secure that?
Since session mechanism does not really work for me, I decided to go with JWT.
In my JWT I am going to store user Id and some other information like, for instance, user's privilegies.
For mobile app, I am going to send this token as a part of a response and the app will store it inside its secure store.
Each request it will send this token as Authorization Header.
For web app, I am going to send this token via HttpOnly cookie. This token, thus, will be included in every request from the client.
The problem now is a possible CSRF-attack. To address that I thought of the following. Each user "session" will be associated with CSRF token.
Since I can't store this token on the server (remember, stateless API), I can send it as encrypted (again, with JWT) token to the client via HttpOnly cookie and non-crypted in a regular cookie.
Now, every request the web client will use non-crypted token from the cookie and send it back to the server. The server will check if this token matches from the Encrypted one which is stored in HttpOnly cookie.
Also, I am going to use different URL endpoints for web and mobile web apps. What for? In order to keep auth mechanisms described above separate - I believe this will help me to keep the service secure.
Do you think it is an OK solution? What problems do you see here?
Thanks in advance.
In general, what you described looks good and pretty standard. However, if I understand correctly, the CSRF protection is flawed.
To make sure I understand correctly: a csrf token would be stored in an encrypted httpOnly cookie, only to be sent back to the server as reference. Another cookie would have the same value but unencrypted, in a plain (non-httpOnly) cookie, and the server would compare these two. What's the point? An attacker would still be able to create a webpage to have a user make an request to your website, and both cookies would still be sent.
The reference cookie is ok to be in the httpOnly cookie for reference, but the other one should not be a cookie. It could for example be a request header value that you add to all requests. The client could receive it in a response, but not as a cookie. With jQuery in the web app, you can use the beforeSend hook to add it to all subsequent requests as a header. This way an attacker could not make valid requests from another domain.

Authenticating users with tokens useless after HTTPS?

I am building a mobile application that include users doing various things in the app and I started off with authenticating all user actions inside the app using a token that is stored locally on the device. My biggest concern was that anyone can sniff the network and look at the http requests I make inside the app and thus send false requests on behalf of a real user. Something like this:
http://mywebsite.com/postmessage?user=abcd&token=35sxt&msg=Hi
Now, I am using HTTPS though and no one can see my domain name nor the data being sent. So I'm inclined to get rid of tokens all together and do just this:
https://mywebsite.com/postmessage?user=abcd&msg=Hi
Am I correct in assuming I don't need tokens anymore? The whole purpose of them for me was making sure that no one can make an action on behalf of another user without authorization and now it seems pointless that I still use tokens. Am I missing something else?
Firstly, you were correct that having the token in the URL (or anywhere else) was a security risk over HTTP. However, now that you are on HTTPS, it should not matter whether you have the API token in the URL or you are transmitting it in some other way. The URL should be as secure as any other part of the transaction. I say "should" because in practice your internal infrastructure may do logging, metrics collection or reporting that reveals the URL slightly more easily than you intend. And the client may submit the visited URL (but not other info) to its own logging system or to a smart search service like Google, etc. But for most use cases and in most configurations this is not a major issue.
But it sounds from your question like you are talking about not removing the token from the URL and adding it to the HTTP headers or some other fashion, but actually removing the token concept entirely.
So what you should ask is, what is special about HTTPS that makes the token unnecessary? HTTPS secures the communication but it does not authenticate the client. Except in very unusual configurations, anyone can connect via HTTPS and issue commands, and unless you have some method of authentication the HTTPS will not protect you from unauthorized access. If you are using cookies for authentication, or if you are passing the token via HTTP headers (which is actually how I prefer to handle tokens when possible) then your need for authentication is satisfied and you do not need the token. If you do not have any other form of authentication, and you need authentication for security on your website, then you do need the token.
HTTPS is basically used to ensure that you are communicating with a webstie that you intended to and to encrypt communication data so that even if someone intercepts your data, it makes no sense to them.
For e.g. if you are placing an order on Amazon and making a payment,
HTTPS will ensure that:
you are actually submitting payment details to Amazon
your payment data is encrypted when flowing from your browser to Amazon webserver.
When communicating over HTTPS, browsers validates servers digital certiifcate to confirm their identity , then a key is exchanged between server and browser to encrypt data flow between browser and server.
By default HTTPS does not authenticate client. So if you have some actions specific to particular user, you still needs authentication token from client.
But if the token is passed as query parameter in URL itself, then it is still exposed to attackers, so send the token in cookie over HTTPS.
It is also recommended to mark your cookies as secure, to ensure that cookies are sent only over a secure (https) connection and not over http as it can reveal user details.
Hope it helps.

Resources