I am developing a REST application with its own authentication and authorization mechanism. I want to use JSON Web Tokens for authentication. Is the following a valid and safe implementation?
- A REST API will be developed to accept username and password and do the authentication. The HTTP method to be used is POST so that there is no caching. Also, there will be SSL for security at the time of transit
- At the time of authentication, two JWTs will be created - access token and refresh token. Refresh token will have longer validity. Both the tokens will be written in cookies, so that they are sent in every subsequent requests
- On every REST API call, the tokens will be retrieved from the HTTP header. If the access token is not expired, check the privileges of the user and allow access accordingly. If the access token is expired but the refresh token is valid, recreate new access token and refresh token with new expiry dates (do all necessary checks to ensure that the user rights to authenticate are not revoked) and sent back through Cookies
- Provide a logout REST API that will reset the cookie and hence subsequent API calls will be rejected until login is done.
My understanding of refresh token here is:
Due to the presence of refresh token, we can keep shorter validity period for access token and check frequently (at the expiry of access token) that the user is still authorized to login.
Please correct me if I am wrong.
A REST API will be developed to accept username and password and do
the authentication. The HTTP method to be used is POST so that there
is no caching. Also, there will be SSL for security at the time of
transit
This is the way most do it, so you're good here.
At the time of authentication, two JWTs will be created - access token
and refresh token. Refresh token will have longer validity. Both the
tokens will be written in cookies, so that they are sent in every
subsequent requests
Storing the tokens in cookies i not dangerous in it self, but if you somehow get you JWT module on your server to read them from there you vulnerable to CSRF attacks where any webpage can trigger a users browser to send a form + you sites cookie to your server, unless you use CSRF tokens. So generally they are stored in localStorage and "manually" added to request headers every time.
On every REST API call, the tokens will be retrieved from the HTTP
header. If the access token is not expired, check the privileges of
the user and allow access accordingly. If the access token is expired
but the refresh token is valid, recreate new access token and refresh
token with new expiry dates (do all necessary checks to ensure that
the user rights to authenticate are not revoked) and sent back through
Cookies
Apart from the cookie dangers it seems safe.
Provide a logout REST API that will reset the cookie and hence
subsequent API calls will be rejected until login is done.
You dont even need to make an API call, you can simply just purge the cookies or the localStorage object and make sure you client doesnt break on missing tokens.
The standard for the express-jwt module expects the tokens to be in its own "Authorization: Bearer [Token]" header, which i would strongly recommend over cookies. The localStorage API is available all the way back to IE8 so you should be good.
Edit:
First it's important to know the difference between XSS and CSRF attacks since they're often believed to be the same thing.
XSS is when users get unsafe JS running on your domain in other users browsers, when that happens neither JWT in localStorage or sessions and JWT in cookies are safe. With httpOnly flag on cookies you can't directly access them, but the browser will still send them with AJAX requests to your server. If this happens you generally out of luck. To prevent this, make sure to escape all user input if its sent to the browser.
If you load 3rd party JS with script tags or iframes this might compromise localStorage unless you are careful, but i haven't worked enough with this to help you here.
CSRF is only when other domains are trying to send normal HTML forms to your server by getting the browser to send cookies automatically. Frameworks prevent this by inserting unique random strings as hidden fields and checking them again when its submitted. JWT's in localStorage are safe from this since each domain gets it own seperate localStorage area.
But ultimately all this depends on if your service will be using one single domain, in which case httpOnly cookies will be plenty secure and easier to set up, but if you wanna spread your service out on multiple domains like api.domain.com + app.domain.com or add a native app you're forced to store you're JWTs in localStorage or some other native storage area.
Hope this helps!
My understanding of refresh token here is:
Due to the presence of refresh token, we can keep shorter validity period for access token and check frequently (at the expiry of access token) that the user is still authorized to login.
Please correct me if I am wrong.
Assuming you're talking about using JWT as Bearer-token in OAuth (and I would strongly advice to follow the OAuth 2.0 protocol), that's right.
With an additional auth-time (timestamp of authentication) claim in your JWT, you could even drop the second token and sent your access- as a refresh-token (the auth-server could then issue a new access-token if token is valid & auth-time within allowed range)... but sure, it's also good to follow the standard ;)
Anyway, there are certain additional aspects (that tend to get difficult or are even against the fundamental ideas of JWT) you should consider before using JWTs as refresh-token, as this basically means you introduce long-living JWT:
- do you need to have something like forced user logout/ token revocation by subject (e.g. if user got identified as fraudulent)?
- do you need to have something like revocation of a specific token (e.g. if a user looses a device)?
- ...
Dependent on your use-case you should consider all the possible implications, long-living tokens have as they usually require you to introduce some kind of state on your server-side (e.g. to allow revocation/ blacklisting). Keep in mind the beauty and security of the JWT concept lies within JWTs being short-lived.
I asked this question two years back and also accepted the answer. However, based on my experience and study in the last two years, I'd like to add some points just in case someone stumbles on this thread with the same question.
The approach mentioned in the question is similar to the "resource owner password credentials grant" type of OAuth 2.0. However, I think it is better to use the "authorization code grant" type instead. Given that in a browser, the client secret cannot be safely stored, a back-end proxy component can be used such that:
The browser will redirect the page to the authorization server
The user will authenticate himself/herself by providing her credentials in the authorization server login page
On successful authentication, the user would provide authorizations / permissions to allow the SPA access the resources on his/her behalf
The authorization server would then redirect the user to the proxy component with the authorization code in the query param
The proxy component will then exchange the client secret and the authorization code with the authorization server for an access token and a refresh token
Once the tokens are obtained the proxy component would redirect the user to the SPA by setting the cookies containing the access token and refresh token
Thus with this approach we can implement authorization code grant type without entrusting the browser with the client secret.
Note: I'm yet to try this approach on my own
The reason I think the "Implicit flow" is not suitable is because
The client authentication step by providing client secret and authorization code is missing
The access token is sent back as a URL fragment which will continue to stay in browser history
If XSS attack occurs, the malicious script can very well send the token to the remote server
In all the cases, we need to protect the SPA from CSRF and XSS. I got quite a few insightful inputs on CSRF from here this Stanford article ] and QWASP CSRF Cheat Sheet.
In the accepted answer the recommendation is to store the tokens in the browser local storage or session storage. The argument for that is if XSS occurs, the application's security is anyway compromised beyond repair. However, if we store the tokens in browser storage, the XSS script can send the tokens to a remote server owned by the attacker who can then easily impersonate the victim user for as long as he wants. If we, on the other hand, use secure
and httpOnly
cookies, the XSS cannot read and send them to remote server. Granted that XSS can still impersonate the user from the user's browser, but if the browser is closed, the XSS is helpless. (Needless to say that the app dependencies and the transitive dependencies need to be upgraded to the latest versions as and when new versions with security patches are made available to keep XSS away in the first place)
In order to protect the user from CSRF, we can follow the approach followed in frameworks like Angular (as explained in the Angular HttpClient
documentation where the server has to send a non-HttpOnly
cookie (in other words a readable cookie) containing a unique unpredictable value for that particular session. It should be a cryptographically strong random value (consider using SecureRandom
in Java). The client will then always read the cookie and send the value in a custom HTTP header (except GET & HEAD requests which are not supposed to have any state changing logic. Note CSRF cannot read anything from the target web app due to same origin policy) so that the server can verify the value from the header and the cookie. Since the cross domain forms cannot read the cookie or set a custom header, in case of CSRF requests, the custom header value will be missing and the server would be able to detect the attack
To protect the application from login CSRF, always check the referer
header and accept requests only when referer is a trusted domain. If referer
header is absent or a non-whitelisted domain, simply reject the request. When using SSL/TLS referrer
is usually present. Landing pages (that is mostly informational and not containing login form or any secured content may be little relaxed and allow requests with missing referer
header
TRACE
HTTP method should be blocked in the server as this can be used to read the httpOnly
cookie
Also, set the header
Strict-Transport-Security: max-age=<expire-time>; includeSubDomains
to allow only secured connections to prevent any man-in-the-middle overwrite the CSRF cookies from a sub-domain
The authorization server can also maintain the list of all issued access-refresh token pairs, the client IP, CSRF token value so, if required, the following can be done from the server: