I am currently working on a medium scale app and am a month into learning React. I got to the part where I need to authenticate users. I have written some code and It is working, but I don't know is it secure enough. When my users login, they are assigned with a JWT token like this:
await axios.post(APIbase + '/login', {
username: username, password: password
}).then(res=>{
const token = res.data.token;
localStorage.setItem('token', token);
}).catch(err => {
console.log(err);
});
And then, when the user makes a request to a server it send the token by an auth header like this:
const token = localStorage.getItem('token');
const headers = { Authorization: `Bearer ${token}`};
const detailResult= await axios.get(API.base + API.details, {
headers:headers});
Is this safe enough? I heard that this is a not really a good practice, but I am not sure what exactly should I do.
Local storage is generally used for this kind of token, but keep in mind any JS on the page can access local storage. If you have any 3rd party code, it can get to the token by simply reading the local storage.
If you want a bit more secure way of storing it, you can use HTTPonly, secure cookie. That way it will not be accessible by JS and it will also be sent automatically in any request to the API, but it requires changes on the server to implement cookies instead of Authorization header.
You can also use a BFF (backend for frontend) approach with a server handling session then you don't need to store the token on the client side either (and only store in on BFF linked to the session), but keep using it for requests to the API from the BFF.
Security is a complex field and has a lot of trade-offs. There is no one correct answer for every use case.
This does really belong to react domain but is a more beta question and there is a special stack exchange for this: https://security.stackexchange.com/
Related
I'm using next-auth for authentication and using the Credentials provider for logging in, my API returns an object containing an accessToken like this object:
{ "token" : "UzI1NiIsInR5cCI..." },
And I'm returning a similar object containing the token in authorize property of CredentialsProviders while my session callback looks like this:
session({ session, token }) {
session.data = {
...session.user,
...token.user,
};
session.accessToken = token.accessToken;
return session;
}
And I use the useSession hook in my client code like this:
const { data: session, status } = useSession();
to access the user's token to make API requests.
Is this safe? Or is there another ways to achieve this?
Keep in mind that this application is gonna be fully client side rendered despite the fact that I'm using Next.js, so keep that in mind. I'm asking this because most of the docs of Next.js is SSR focused.
Quite often, we use localStorage to store the token after authorization, of course, since localStorage is the browser api, this means that we store the token on the client, which is actually similar to your case. I don't see much of a security issue with this, although it does make the token vulnerable to XSS attacks, so cookies are considered a safer way to store the token, since it can't be retrieved from the script side (if you set the http-only cookie option).
If you're worried, you can put the token in cookies to be sure. Although even so, the way to use this token for malicious purposes. The best way is to protect yourself from XSS attacks and then there will be nothing to worry about, react (well, including next) out of the box has good protection mechanisms.
In general, since we are talking about an access token, this is the thing that identifies the user and this should not be a problem for you to use it into the client, you should be more careful with the secrets of the application, such as the secret that is used to encrypt the token, then there will be security problems if you give access to it by client.
If you still don't want to use a token on the client side, which you probably don't need, you can use getSession on the server side for authorized requests with nextjs and proxy your requests through next to backend, using next as api is also a common practice.
To summarize, since you are using the next-auth library, you have nothing to worry about, since it takes care of most of the security concerns.
https://next-auth.js.org/getting-started/introduction#secure-by-default
I am writing a react app and using localStorage (for now) to store a JWT.
My question is is it better to retrieve the token on page load from localStorage and set in the redux store? or should I retrieve it from localStorage on every request.
i.e.
const App = () => {
useEffect(() => {
const token = localStorage.getItem('token')
dispatch(setTokenInStore(token))
})
}
Then in each request pull token in from the store and then use it in the requests.
Or the other option is to retrieve the token from localStorage on every request:
fetchA = () => {
const token = localStorage.getItem('token')
fetch(url, token)
}
fetchB = () => {
const token = localStorage.getItem('token')
fetch(url, token)
}
If one is better than the other would it be possible to give reasons. If anything is unclear please let me know.
Generally speaking about the redux store vs localStorage is that they are meant for different purposes. If you need to share state in your app then you go with redux. If you need to persist data in your browser cache then localStorage is the way.
Regarding where to put an jwt-token is a debate iself. The jwts are design to be short lived and IF an external user somehow would get a hold of the token they have a limited time to use it. If its not stored in the localStorage, the user would need to login if they refresh the browser. That would not make a good experience for the end user.
But, If you need authentication in your app you should not reinvent the wheel.
Most jwt-providers can be used with the OpenID Connect layer and there is a lot of docs regarding that topic. For your react app I would there for go with the react-oidc-client. The client uses its own store (WebStorageStateStore) which can be set to localStorage.
If you store tokens in local storage, then it's easily accessible by any XSS attack and any external user can get the token by a script inside your page. You can save JWT token in redux, but mostly redux is used if you want to share the states across the app.
But the general answer to your question would be : try to follow best practices of jwt tokens and security. One main point of security would be setting expiry of your tokens.
There are several blogs which suggest to store tokens in cookies and they are super secure. You can read here.
I'm currently working on a Next.js (React) project, where I use Firebase Auth for authentication. I use this to connect to a REST API back-end, which receives the user token Firebase provides (via getIdToken()).
Because the IdToken changes every now and then, I'm currently reqesting the latest IdToken before sending a fetch request like this:
const fetcher = (url: string) => {
return user.getIdToken().then((token) =>
fetch(url, {
method: "GET",
headers: new Headers({
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
}),
}).then((res) => res.json())
);
};
This setup actually works, but I was wondering if it's considered efficient/best-practice?
I see a lot of examples out there where the IdToken is used to set a cookie (eg. firebase docs, next.js example).
I could see why when using SSR, since getIdToken() can't be called there. But my application only uses client-side data fetching. Would there be any benefits for me moving away from my current approach to using cookies?
The Firebase Authentication SDK already caches the token in local storage, so there's no need for you to cache it elsewhere again.
In fact, the token is refreshed every hour, and the Firebase Authentication SDK refreshes it automatically in the background. If you cache the token yourself, you might end up using an outdated token.
So I'd recommend always calling getIdToken() when you need the ID token. Of course it's fine to store it in a variable, and use that in a single code block (code that runs at pretty much the same time).
Using a cookie to pass the token to your server is fine, as is using the Authorization header as you do now. The latter is more common, but a cookie works too.
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 });
}
})
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.