Authentication with JWT in HTTP only cookie without refresh token - reactjs

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?

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)

How to safely persist logged-in state of user in React website?

I have a website that runs on example.com. The website makes AJAX calls to my backend API which sits at api.example.com.
I employ a double-submit verification strategy for my authentication.
The backend has protected endpoints which check the JWT token with each request. The JWT token is stored in a httpOnly cookie. It also contains the CSRF token.
Alongside the JWT cookie I also send a CSRF cookie which is not httpOnly.
Each request that the client makes must contain the value of this cookie in a special header.
So far so good, but, I want to make sure that the client does everything in its power to prevent users from making pointless unauthenticated requests. So in my React app I have declared a few private routes which check if the user is logged in and if they are not, the user is redirected to the login page. The logged-in state is kept in the Redux store.
The issue is that on a full refresh the Redux store is reset. So as I see it, my options are:
Option 1) Check the existence of a CSRF cookie
The way I check if a user is authenticated is by checking if they have the CSRF cookie; I also do this during the store initialisation. If they do have the cookie, they are allowed to navigate to the protected page. All subsequent requests on this page are still verified on the backend for a JWT and CSRF token.
Option 2) Check against the backend each time
I can create a simple endpoint on the backend that is used to check if the user is logged in. So instead of just checking the cookie, the client can submit a request to this endpoint and verify that the token in the cookie is still valid.
Option 3) Persist the Redux store
I could implement a persisted store (there are some libraries that do this out there) so that the initial problem is automatically resolved, i.e.: the logged-in state is preserved upon full refresh. However, this yields a bunch of issues with caching and token expiration and it may not be worth the effort.

Session authentication with cookies in Django not working

I am using Session Authentication for my Django REST API. What I want is to be able to access self.request.user every time an API call is received at any of my endpoints, so I can process user generated content.
From the docs, it seems session authentication stores only session id's in cookies, but does not send any data beyond that. So, even after I log in, the subsequent requests I send are user-anonymous.
One solution is to send the username and password every time a call is made from the origin, which I think is probably not good practice.
How can I go about persisting user in a session?
You can use JWT Authentication and send the token with every request, this way you can access request.user in your code.

How to interact with back-end after successful auth with OAuth on front-end?

I want to build small application. There will be some users. I don't want to make my own user system. I want to integrate my application with oauth/oauth2.0.
There is no problem in integration of my front-end application and oauth 2.0. There are so many helpful articles, how to do this, even on stackoverflow.com. For example this post is very helpful.
But. What should I do after successful authorization on front-end? Of course, I can just have flag on client, which says "okay, mate, user is authenticated", but how I should interact with my backend now? I can not just make some requests. Back-end - some application, which provides API functions. EVERYONE can access this api.
So, I need some auth system anyway between my FE and BE. How this system should work?
ps I have some problems with English and may be I can not just correctly 'ask google' about it. Can you provide correct question, please :) or at least give some articles about my question.
UPD
I am looking for concept. I don't want to find some solution for my current problem. I don't think it is matters which FE and BE I use (anyway I will
provide information about it below)
FE and BE will use JSON for communication. FE will make requests, BE will send JSON responses. My application will have this structure (probably):
Frontend - probably AngularJS
Backend - probably Laravel (laravel will implement logic, also there is database in structure)
Maybe "service provider" like google.com, vk.com, twitter.com etc remembers state of user? And after successful auth on FE, I can just ask about user state from BE?
We have 3 main security concerns when creating an API.
Authentication: An identify provider like Google is only a partial solution. Because you don't want to prompt the user to login / confirm their identity for each API request, you must implement authentication for subsequent requests yourself. You must store, accessible to backend:
A user's ID. (taken from the identity provider, for example: email)
A user token. (A temporary token that you generate, and can verify from the API code)
Authorization: Your backend must implement rules based on the user ID (that's your own business).
Transport security: HTTPS and expiring cookies are secure and not replayable by others. (HTTPS is encrypting traffic, so defeats man-in-the-middle attacks, and expiring cookies defeats replay attacks later in time)
So your API / backend has a lookup table of emails to random strings. Now, you don't have to expose the user's ID. The token is meaningless and temporary.
Here's how the flow works, in this system:
User-Agent IdentityProvider (Google/Twitter) Front-End Back-End
|-----------------"https://your.app.com"---------->|
|---cookies-->|
your backend knows the user or not.
if backend recognizes cookie,
user is authenticated and can use your API
ELSE:
if the user is unknown:
|<--"unknown"-|
|<----"your/login.js"----------+
"Do you Authorize this app?"
|<------------------+
|--------"yes"----->|
+----------auth token--------->|
|<---------/your/moreinfo.js---|
|-------access_token ---------->|
1. verify access token
2. save new user info, or update existing user
3. generate expiring, random string as your own API token
+----------->|
|<-------------- set cookie: your API token --------------------|
NOW, the user can directly use your API:
|--------------- some API request, with cookie ---------------->|
|<-------------- some reply, depends on your logic, rules ------|
EDIT
Based on discussion - adding that the backend can authenticate a user by verifying the access token with the identity provider:
For example, Google exposes this endpoint to check a token XYZ123:
https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=XYZ123
I read through all the answers very carefully, and more than half the people who responded are missing the question completely. OP is asking for the INITIAL connection between FE & BE, after the OAuth token has been issued by the Service Provider.
How does your backend know that the OAuth token is valid? Well keep in mind that your BE can send a request to the Service Provider & confirm the validity of the OAuth token, which was first received by your FE. This OAuth key can be decrypted by the Service Provider only because only they have the secret key. Once they decrypt the key, they usually will respond with information such as username, email and such.
In summary:
Your FE receives OAuth token from Service Provider after user gives authorization. FE passes OAuth token to BE. BE sends OAuth token to Service Provider to validate the OAuth token. Service Provider responds to BE with username/email information. You can then use the username/email to create an account.
Then after your BE creates the account, your BE should generate its own implementation of an OAuth token. Then you send your FE this OAuth token, and on every request, your FE would send this token in the header to your BE. Since only your BE has the secret key to validate this token, your application will be very safe. You could even refresh your BE's OAuth token on every request, giving your FE a new key each time. In case someone steals the OAuth token from your FE, that token would be quickly invalidated, since your BE would have already created a new OAuth token for your FE.
There's more info on how your BE can validate the OAuth token. How to validate an OAuth 2.0 access token for a resource server?
let's use OAuth concept to begin,FE here is Client , BE here is Resource Server.
Since your client already authorized, Authorization server should grant
Access token to the client.
Client make request to the resource server with the Access token
Resource server validate the Access token, if valid, handle the request.
You may ask, what is the Access token, Access token was issued by authorization server, grant to client, and recognized by resource server.
Access token is a string indicate the authorization information(e.g. user info, permission scope, expires time...).
Access token may encrypted for security, and you should make sure resource server can decrypt it.
for more details, please read OAuth2.0 specification https://www.rfc-editor.org/rfc/rfc6749.
Well you don'y need User-System on your Front End side.
The front end is just a way to interact with your server and ask for token by valid user and password.
Your server supposed to manage users and the permissions.
User login scenario
User asking for token by entering his username and password.
The server-API accept the request because it's anonymous method (everyone can call this method without care if he's logged in or not.
The server check the DB (Or some storage) and compare the user details to the details he has.
In case that the details matches, the server will return token to the user.
From now, the user should set this token with any request so the server will recognize the user.
The token actually hold the user roles, timestamp, etc...
When the user request for data by API, it fetch the user token from the header, and check if the user is allowed to access that method.
That's how it works in generally.
I based on .NET in my answer. But the most of the BE libaries works like that.
As am doing a project for SSO and based on my understanding to your question, I can suggest that you create an end-point in your back-end to generate sessions, once the client -frontend- has successfully been authorized by the account owner, and got the user information from the provider, you post that information to the back-end endpoint, the back-end endpoint generates a session and stores that information, and send back the session ID -frequently named jSessionId- with a cookie back to the client -frontend- so the browser can save it for you and every request after that to the back-end considered an authenticated user.
to logout, simply create another endpoint in the back-end to accepts a session ID so the back-end can remove it.
I hope this be helpful for you.
You need to store the token in the state of your app and then pass it to the backend with each request. Passing to backend can be done in headers, cookies or as params - depends on how backend is implemented.
Follow the code to see a good example of all the pieces in action (not my code)
This example sets the Authorization: Bearer TOKEN header
https://github.com/cornflourblue/angular-registration-login-example

Google OAuth2 flow and id_token refresh

I am having troubles in implementing OAuth in the right way.
I use a client/API architecture (Angular for front and Node.js for back) and I would like user to sign in using Google OAuth authentication only.
Here is what I think is the right way for the moment (tell me if I misunderstood something) :
Angular open a Google popup asking user's consent.
Once the user agree, Google Authorization server sends back to angular a verification code.
This verification code is forwarded to an API endpoint.
Then, the API asks Google Authorization server to exchange this code for an access_token, an id_token and a refresh_token.
Google sends those 3 tokens.
The API uses access_token to retrieve user from Google API
The API persists the user
Here is the little dillema, in my opinion, the access_token and refresh_token should be stored into the database and the id_token should be sent back to Angular client.
This way, it would allow the API to ask for resource in Google API and if the token expires it can still ask for a new token thanks to the refresh_token.
Client-side, the id_token is embedded in all requests thus allowing the API to identify the client and verify his authentication with Google certs from https://www.googleapis.com/oauth2/v3/certs.
Supposing this is right way to use tokens, how could I deal with id_token expiration since client does not have any refresh token ?
Thanks !
I do it slightly different (I have the same basic architecture though).
Angular decides the user needs to log in and displays a login popup.
The url in the login popup is not serviced by angular, but is instead directly run off of the backend server: /auth/google . (I use hapijs and bell, personally).
/auth/google is serviced by a bell plugin and initiates the OAUTH dance.
the end of the OAUTH dance results in my node server generating a local token (I just generate random bytes and store them in redis mapped to user ids)
because the initial login popup was created by window.open, the success page (generated on the api side rather than in angular) can use window.opener.postMessage to communicate the token back to the angular runtime.
This way, all my sensitive google credentials (the user's oauth token, refresh token if needed, and my application's api ID and secret) are only on the server, except for during the OAUTH dance relay when they're in a URL string during the client redirects. This is reasonably secure.
Then for all the actual user interactions with the api, I use the token I generated in step four to authenticate. This could be a JWT if you wanted, but I don't do it that way; I just use redis to map from 'longrandostring' -> userId. That lets me (for example) force everyone to re-login if I wipe the redis database that has all the tokens stored, or I can write a lua script to delete all the entries that map to a certain userid.
If you need a refresh token, you can set access_type=offline in the initial request to oauth2/auth, and you'll get a refresh token as part of the response, unless you've previously gotten a refresh token. You can then persist it on the server side and get new access tokens as needed. If you set approval_prompt=force as well, you'll force a new consent screen and be guaranteed a refresh token (but after some small number of refresh tokens granted to a user, older ones expire on the same application so it's best to only request them if really needed).

Resources