How to store a JWT token inside an HTTP only cookie? - angularjs

I have created an app that simply uses a JWT sent by the server upon correct login credentials, and authorizes against any /api route on my backend Express.js server.
AngularJS, on the other hand, took this token, stored it in session storage, and used an auth interceptor every go around to send the token back to the server.
I've more recently come to understand how dangerous this practice is.
I understand the transfer method of tokens back and forth, in this scenario. However, would someone be so kind as to explain, at a high level, the method that takes place when you want to store that JWT inside a secure, HTTP only cookie that the client side Javascript cannot read?
For example: upon credential success
cookie is created on server,
create a JWT at the same time as the cookie
store the JWT in a cookie property called token etc..
I'm trying to gain a mental model here of how it works. If my understanding is correct, doing it this way wouldn't require an auth interceptor anymore because upon correct credential login, the server would do all of the transferring of the token inside the cookie.

Dealing with cookies has their fair share of subtleties, but at a high level a cookie is a piece of data that your web server can set, that will be then stored by the user's web browser and sent back to the server on any future requests that browser makes to the same server as long as the cookie is valid and applicable to the request being made.
(this is why you'll no longer need to use the Angular interceptors, because it's the browser itself that ensures the cookie is sent)
Besides some specials flag options, like the HTTP only, at a higher level you can set cookies to be associated with a given domain and path. For example, your server could set a cookie in such way that it would only be later sent by the browser to requests made under the /api path.
To sum it up, cookies are a state management mechanism for HTTP, see the associated RFC 2617 for more details.
In contrast, a JWT is just some data that has a well-know representation and follows some conventions. More specifically, a JWT is composed of a header, payload and signature sections and is generally advised to keep the size of the payload small for most of the JWT use cases. See Get Started with JSON Web Tokens for more details.
If you go through the previous article you'll notice that the final representation of a JWT is three Base64url encoded strings separated by dots. This is specially of interest because it means a JWT is well-suited to be used within HTTP, including as the value of a cookie.
One thing to have in mind is that by the specification you are only guaranteed that a browser will support a cookie up to 4096 bytes per cookie (as measured by the sum of the length of the cookie's name, value, and attributes). Unless you're storing way to much data in the token you should not have an issue, but it's always something to consider. Yes, you can also break a JWT token into multiple cookies, but things start to get more complex.
Additionally, cookies have their notion of expiration, so have that in mind also because the JWT itself, when used within the scope of authentication will also have thei own notion of expiration.
Finally, I just want to address some of your concerns about storing the JWT in localStorage/sessionStorage. You're correct that if you do it you have to understand its implication, for example, any Javascript code within the domain for which the storage is associated will be able to read the token. However, HTTP only cookies are also not a silver-bullet. I would give the following article a read: Cookies vs Tokens: The Definitive Guide.
It focuses on the differences between the traditional session identifier cookies vs the token-based (JWT) authentication systems, the section named Where to Store Tokens? warrants a read as it tackles the security related aspects of storage.
A summary for the TL:DR folks:
Two of the most common attack vectors facing websites are Cross Site
Scripting (XSS) and Cross Site Request Forgery (XSRF or CSRF). Cross Site Scripting) attacks occur when an outside entity is able to execute code within your website or app. (...)
If an attacker can execute code on your domain, your JWT tokens (in local storage) are vulnerable. (...)
Cross Site Request Forgery attacks are not an issue if you are using JWT with local storage. On the other hand, if your use case requires you to store the JWT in a cookie, you will need to protect against XSRF.
(emphasis is mine)

Basically, I save access_token(jwt) in a refresh token object stored in the database when the user logs in. see an example of the saved object below;
const newToken = new RefreshToken({
issuedUtc: moment().unix(), /* Current unix date & time */
expiresUtc: moment().add(4, "days").unix(), /* Current unix date&time + 4 days */
token: refreshToken, /* Generate random token */
user: data.id, /* user id */
/* Signing the access Token */
access_token: jwt.sign(
{ sub: data.id, user: userWithoutHash },
Config.secret,
{
issuer: "http://localhost:3000",
expiresIn: "30m", // Expires in 30 minutes
}
),
});
The generated and saved rand token is then sent as httpOnly cookie to the browser;
res.cookie("refreshToken", newToken.token, {
httpOnly: true,
sameSite: "strict",
});
Since the browser sends the cookie for every request all that is left is to use middleware on protected routes, retrieve the token from the cookie, verify if it is exists by looking for it in the database, check if it has not expired, try to verify the access token saved in the database for that refresh token, if it is expired then sign new jwt and update the refresh token in the database then allow the user to proceed to protected route, if it is valid simply allow the user to proceed to protected route. If the refresh token has expired, redirect the user to the login page, and lastly if no refresh token is received also redirect the user to the login page.
var cookie = await getcookie(req); // get the cookie as js object using my custom helper function
/* Check if refresh token was received */
if (cookie.refreshToken) {
/* Check find the refresh token object in the database */
var refreshToken = await RefreshToken.findOne({
token: cookie.refreshToken,
});
/* Check if the refresh token is still valid using expiry date */
if (moment.unix(refreshToken.expiresIn) > moment.now()) {
/* If the condition is fulfilled try to verify the access token using jwt */
jwt.verify(refreshToken.access_token, Config.secret, async (err, result) => {
/* in callback check for error */
if (err) {
/* If error this means the access_token is expired, so find and update the user's refresh token with a newly signed access token */
await RefreshToken.findByIdAndUpdate(refreshToken.id, {
access_token: jwt.sign(
{ sub: result.id, user: result.user },
Config.secret,
{
issuer: "http://localhost:3000",
expiresIn: "30m", // Expires in 30 minutes
}
),
});
/* Proceed to save the user in a local variable then call next */
res.locals.user = result.user;
return next();
}
/* If no error proceed by saving the user in a local variable then call next */
res.locals.user = result.user;
return next();
});
} else {
/* If the refresh token is expired, then redirect to log in */
return res.status(401).redirect('/login');
}
} else {
/* If no refresh token is provided, then redirect to log in */
return res.status(401).redirect('/login');
}
This is something I came up with myself so I can't say it is full proof but since httpOnly cookie cannot be accessed in the DOM, running malicious script in the DOM can't access the refresh token, and even if the refresh token somehow falls in the hand of bad guys then it will be useless because it does not hold any information at all until it gets to the server. So as long the right cors header is set on the server it is highly unlikely that any information can be leaked using the refresh token.

Related

Access Tokens, Refresh Tokens, And User Data

I am using a JWT authentication scheme for my app. I did some research about how to store and use access and refresh tokens, and I have several questions that I couldn't really find an answer to. For the app, I am using React for the frontend and .NET 6 Web API for the backened.
Question 1: Storing what where?
Based on the research I did, local storage is not a good place to store a jwt token for security reasons. So probably the second best alternative would be HttpOnly cookie for the jwt token and local storage for the refresh token. However I did read some articles where jwt token is stored in local storage while the refresh token is stored as HttpOnly cookie. Which approach is better, and the pros and cons of each. P.S I will be rotating the tokens, i.e a new access and refresh token will be generated once the old jwt token is refreshed. Or even store it in memory such as redux state
Question 2: When to refresh JWT Token?
Should the jwt token be refreshed just before it expires, such that the backend can verify the token, or is it fine to refresh the token after it expires (by bypassing the verificatoin when refreshing the token only i.e the refresh endpoint). Also should refreshing, be done by setting an timer/interval, or waiting for a request to fail?
Question 3: Accessing User Data and Expiry Date
I am storing some user data, such as username and password in the jwt token so I can acees them in the frontend. The problem is that when setting the jwt token as HttpOnly cookie, since Javascript can't access the token, I won't be able to access user data and the token's data(such as jti and expiry date). For the user data, I could do a seperate request to access user data such as username and email, but for the JWT token's expiry date, how could I obtain it?
I would appreciate answers to these questions or any feedback if someone faced similar issues and how you resolved them
Consider these as discussion rather then guideleines
Question 1: Storing what where?
Storing access tokens in memory is a good choice
But if you have a refresh token, and you need to do a silent login, local storage is the only choice
but you can always encrypt the token before storing
Question 2: When to refresh JWT Token?
if you wait for token to expire and then refresh with refresh token then existing request which failed with expired token need to be queued again.
if you refresh token on regular intervals, if existing token is invalidated with refreshing, then again the same issue failing requests needing to be queued again.
if you are using axios, you can use libraries like axios-auth-refresh, which will queue the failed requests and try then again with a new token.
you can check their source code or may be create your own version if handling failed calls is important.
Question 3: Accessing User Data and Expiry Date
Access token and cookies should not contain sensitive information
its better to make another call to the api to get users info
Question 1: Storing what where?
First, it is never recommended to use refresh tokens if you are not able to store them securely. Consider building a traditional web app instead.
Second, session storage is always recommended over local storage for these types of tokens.
However, I understand the problem and there are ways to get around this with “Secure SameSite Cookies” if both your apps use the same domain name. OWASP has recommendations, have a look at “token side jacking”: https://cheatsheetseries.owasp.org/cheatsheets/JSON_Web_Token_for_Java_Cheat_Sheet.html
A simplified version below (please read OWASP recommendation and make the necessary adjustments):
During the authentication face (Web API):
create a “user context”, a random string
set access token with “user context”
set refresh token with “user context”
set http response header with a hardened cookie (flags: HttpOnly + Secure + SameSite + cookie prefixes). Value: “user context”
You need to implement validation of “user context” for API requests and for the refresh token flow; if “cookie user context” does not match “token user context”, then respond with proper http error code.
This makes it possible to:
store access token in session storage
store refresh token in local storage
Question 2: When to refresh JWT Token?
Should the jwt token be refreshed just before it expires, such that the backend can verify the token, or is it fine to refresh the token after it expires (by bypassing the verificatoin when refreshing the token only i.e the refresh endpoint).
If access token has expired then try refreshing tokens using your refresh token. If refresh token has expired, then a new authentication is needed. The definition of an expired token is that it is no longer valid and cannot be used.
Also should refreshing, be done by setting an timer/interval, or waiting for a request to fail?
Wait for the request to fail. If both access token and refresh token has expired, then a new authentication is needed.
Question 3: Accessing User Data and Expiry Date
It is not recommended to store sensitive information in access token. The access token should be opaque to the client application. Set access token with some identifier and create a /userinfo endpoint that returns information about the user. Eg, Create an authController with two actions:
/token (used for authentication)
/userinfo (used for retrieving information about the user)

Authentication with JWT in HTTP only cookie without refresh token

The motto of the upcoming question is "I don't know what I don't know".
I would like to know if there are downsides or security risks with an authentication implementation.
Right now, I'm storing a JWT in an HTTP only cookie to send it from the client (React application) to the server (Spring Boot/Kotlin application). This removes the XSS vulnerability.
The JWT follows the basic principles (encoded secret, expiration date, issuer check, etc.). The server has servlet filters that check an existing cookie for validity on every request. When signing in via the client, the server responds with a valid JWT:
The client will send its cookie with every request, which is furthermore checked for validity by the server's servlet filters on every request. When these checks are positive, the user is authenticated with Spring Security.
As described above, the JWT expires after 1 hour, 1 day, or whatever is configured. That's why I need to refresh it some way or another. Now, instead of using a refresh token and placing it in the client's local storage, I decided to just make a small request to the server and create a new JWT, which is send back via the response again:
Again, when the refresh endpoint is called, the servlet filters will check for validity. So only an already authenticated user will receive a new JWT token that way. Some invalid JWT will not receive a new JWT.
I'm calling this endpoint from the client in an interval and therefore regularly extend the expiration date of the JWT inside the cookie.
What I'm aware of:
With the current refresh mechanism, an access token can be valid indefinitely (when the user is signed in regularly).
I also don't persist valid tokens or sessions in the database, so I can't really invalidate specific user sessions "globally" that way, as the cookie is the only source of truth. But this has nothing to do with the refresh token, I could create such whitelist/blacklist via the user ID instead.
If something really bad happens, I could still
...change the expiration date of all JWT to 0, so every authenticated user will be unauthenticated the next time he sends a request.
...or I could change the JWT secret, from which one no request will be authenticated anymore.
My question is: Has what I'm doing (replacing the JWT inside the cookie) any more downsides?

How to implement remember me functionality for authentication in ReactJS when i only receive jwt token from backend api

I currently have to implement remember functionality for remembering my login info on my frontend website. How to implement remember me functionality for authentication in ReactJS when i only receive jwt token from backend API.
Once you receive a jwt token, you can set an "expiry time" for your jwt token. For example, if I receive a token with id: "abc_token_123", I will create an object inside sessionstorage, localstorage, or even cookies with a key called expireTime (for example). And I will use a useEffect hook on the main file (App.js) to watch for the time, if the time exceeds the expiry time, log the user out, otherwise, if the expiry key is present inside your storage, keep the user logged in.
Store the JWT in browser Local Storage if the user chooses the remember me option. Assuming you can change the backend JWT expiry, make the JWT expiry longer (whatever is an acceptable number of days between logins) where the user has chosen remember me.
After a search online I was able to find this answer in this website. The site also expands on how to load it.
function setWithExpiry(key, value, expiration) {
const now = new Date()
// `item` is an object which contains the original value
// as well as the time when it's supposed to expire
const item = {
value: value,
expiry: now.getTime() + expiration,
}
localStorage.setItem(key, JSON.stringify(item))
}
You do this to set up a way to save 'RememberMe' to your local storage with some expiration time.

Misunderstanding the process of JWT authentication

I create project using React + Redux + Apollo Client + Graphql
When we need to log in in our app we need to use token (saved in localStorage for example) which is put in the headers parameter like in the code below:
const client = new ApolloClient ({
uri: 'http://localhost:4000/api',
headers: {
authorization: `Bearer ${localStorage.token}`,
},
});
After request server verifies token and becomes aware who is the user.
My question: from where do we need to get token and put it to the headers parameter for log on (sign up) process? A new customer comes to our log on page, he has no token (in localStorage or somewhere else) at the beginning but server requires it in the requests. And if we remove headers parameter from our client, the log on process will proceed but server won't understand who is the current user.
Typically the server would be the one issuing the JWT token, and this would happen during user login or maybe during account creation. For these particular calls, you should not be expecting the JWT in the header. Instead, the user would be passing credentials, such as username and password. For most other calls, it is appropriate to pass the JWT in the header of the request.
Keep in mind that the main purpose of the JWT is free the user from having to provide personal credentials during most requests. Instead, the user can just present a JWT, much as one would present a passport, to get access to your web services.
In response to your comments below, I would suggest that you keep the signup/registration process separate from the user-only area of your application. Here is a typical workflow:
Prospective user visits your site, and creates an account, by choosing a username and password, and possibly by providing certain other personal information
Your application creates an account, and then sends an email verification link to the user's email address. The server lands the user on a page which mentions all of this
The user opens the email, which contains a verification link, which when clicked will activate the account. Your application returns a web page which then asks the user to login.
Finally, the user logs in from the normal login page.
Note carefully here, that JWT were not at all involved in the signup process, nor do they need to be. The user JWT only needs to come into existence after the user actually logs in for the first time.
Decision:
you need to check for token in localStorage and update the request if token exists
const client = new ApolloClient({
uri: 'http://localhost:4000/api',
request (operation) {
const headers = {};
const token = localStorage.getItem('token');
if (token) headers.authorization = 'Bearer ' + token;
operation.setContext({ headers });
}
})

OAuth2 grant for interacting between my Angular app and my REST API?

Help me pick the right OAuth2 grant type for my Angular App and my REST API?
UX-wise I want just one login form on my front-end, that would ask for username/pass(no dialog asking for permissions). I think the "Resource Owner(Password) Grant" is the most appropriate for me(since I control front&backend), but I'm not sure how should I handle access token refresh.
Correct me if I wrong about the flow:
When user submits credentials through login form, access token is returned.
I can store this token in LocalStorage to make subsequent Ajax requests with it.
As I understand access tokens should be short-lived. And should be updated with Refresh token. Should the refresh token be returned with the access token after initial login and also stored on the client? If not what is the alternative?
Should there be any session maintained on the server to invoke access token refresh? or I should make calls from front-end to refresh the access token when it is about to expire. But then I need a refresh token on the front-end, right?
As you see there is a mess in my head about refresh token. Would be great to have some clarification or suggestion for another grant implementation.
Backend technology I guess is irrelevant here, but just in case it's Symfony2 with FOSOAuthServerBundle.
When you are calling the TOKEN endpoint (for every grant_type possible) on a OAuth Server, you get an access_token but other information as well (I think there are all here):
{
access_token: // your short-lived token
expires_in: // number of seconds before the access_token is invalid
token_type: // the type of the access_token
scope: // scopes of the access_token
refresh_token: // long-lived token to get a new access_token
}
You need, in my opinion, all these information (maybe the scope is unused, but all others will be used later). You have to store the access_token to ba able to make API calls. After seconds, your access_token will not work anymore. You will need to get a new one. You can either ask the user to log in AGAIN or use the refresh_token.
You will have to call the OAuth Server on the TOKEN endpoint but with a grant_type: refresh_token. You will have to provide the refresh_token from the first request (among other information) and in return you will have the same response as above. In fact, I think you will have to do that every time an access_token is expired. In my opinion, the server side does not know anything about sessions or connected users. It knows about valid and invalid access_token.
This is OAuth. If you don't want to have to refresh everytime, you can make a long-lived access_token (by setting the expires_in), I think this is the only solution that makes sens in an OAuth context.
Do you need some clarification about OAuth in general?

Resources