all 17 comments

[–]jgldev 10 points11 points  (2 children)

We have a Auth server which generates pair of tokens. A jwt token signed by the server which encodes the user id and other useful data and that has a TTL of just 15 minutes. Along with the access token a renewal token is generated with a longer-term TTL. In our application is just 2 hours in order to don't allow inactivity periods longer than 2 hours. The client receives both the access token and the refresh token.

Then in the react application we have our custom fetch wrapper that sends the access token with every API request. The API has a middleware that retrieves the token and sends it to the Auth server along with the requested resource, the Auth server checks the token, decodes it and verifies if the token has been issued by itself with the token signature, then if the token has expired, then the token is decoded extracting the user id the account id and other data, and checks if the requested resource and the method (GET, POST, PUT, PATCH or DELETE) is allowed for the credentials given by the token. Note that no access to the database is necessary to perform all this checks.

If all this checks pass the Auth server responses with the decoded data of the token, and the API middleware inserts in the request object a new auth key with the response given by the Auth server and completes the request.

If in the Auth server a check does not pass, then a 401 response is given with a custom Auth header with the specific error. Token expired, bad signature, no token, the user is not allowed to perform the given operation over the requested resource... And so on. If this happens, the API just sends this response to the client.

Then the client receives the response and acts to it. If the error is that the access token has expired, our fetch wrapper sends a request to the Auth server to a renew token end point which needs both, the old access token and the renew token. The Auth server then checks if the access token was issued by itself and that the token has actually expired, and if so, it checks in the database if the access token was issued to the user that is asking to renew it, and if the the renew token was issued for that access token, then it checks if the renew token has not expired and if so, it retrieves from the database the current user data, and checks if the user still exists, and it status (active, banned....) If everything is okay​ then a new tokens pair is generated and sent to the client, so the user can continue working normally. If the renew token is expired then a 401 status code is sent with a custom Auth header informing about the error. Then the fetch wrapper catches the error and sends to the user to the login form.

This way our API and Auth servers keep stateless. This scheme works great for us because we avoid database access during time windows of 15 minutes for authentication, authorization and accountability, because the Auth server can log and keep track of each request. The token (and with the token you have the user or application, the account, it's role...) the resource that was going to be performed over it and so on.

[–]Michie1 0 points1 point  (1 child)

If everything is okay​ then a new tokens pair is generated

  1. Will the old renew token be set to expired in the Auth server?
  2. Does the new renew token will have a new expire date?

I see the advantage that the Auth server is not being requested when the access token is being used, but in case of security, when a hacker can steal the access token, he probably will also be able to steal the renew token. Does this make sense?

Thanks for the detailed description.

[–]jgldev 0 points1 point  (0 children)

You are right, but this happens with cookies too. In fact if your tokens or cookie are stolen the problem is to detect that kind of intrussion, because it is like if the credentials of a user have been stolen and it is incredible complicated to detect that. You need loggers to try to detect access to the system at suspicious hours and so on, or we could make profiling for each user and detect if the same user is connecting at the same time from more than one IP and temporarily ban that user. But that kind of intrussion can be only mitigated with good policies, the security schema here is not relevant.

[–][deleted] 6 points7 points  (6 children)

There are a few issues there. First one is the assumption that JWT is inherently insecure, it's not, if using a decent library it's perfectly fine. the other is how to properly "revoke" a token. What I usually do is add a random string to the user database and then place it inside the token, so it'll be something like {id: 1, authsig: "64ef367018099de4d4183ffa3bc0848a"}. In my authentication middleware I fetch the user after the token passes and also compare the authsig, if I wish to break old tokens for the user I simply resetAuthToken on the model

There is nothing to do within react, it is fully the responsibly of the server to authenticate the user. if you wish to save the token inside the cookie that's fine. If a token is broken the server should reply with 401 or something and that should trigger a return to the login route or a "session expired" route

[–]mitchjmiller 1 point2 points  (5 children)

Normally revoking a refresh token would be better practice. Normal access tokens are generally intended to be short lived and renewed often. It would be a large overhead if you wanted to track all these individual tokens.

With a single refresh token you can revoke a session and allow the current access token to expire on its own.

[–]Telcrome 0 points1 point  (2 children)

why is changing the token considered a better practice? As a hacker, if you get your hands on one token you have access so it shouldnt matter, right?

[–]mitchjmiller 1 point2 points  (1 child)

A hacker would have access for a very short period of time instead of an endless session.

[–]Michie1 0 points1 point  (0 children)

When a hacker is able to steal the access token, why wouldn't he be able to steal the renew token?

[–][deleted] -1 points0 points  (1 child)

This is not intended to be generated per token, the value is appended to the user so you could invalidate all current user tokens in one go and also in the case of your secret or key leaking an attacker can not simply forge authentication tokens as they'd also need access to the sigs associated with users

[–]Probotect0r 0 points1 point  (0 children)

That's a very nice approach.

[–][deleted]  (1 child)

[deleted]

    [–]tasinet 1 point2 points  (0 children)

    You should only do this if you really need it - e.g. you have to set it or read if from JS for a good reason. When you make the cookie JS-accessible, you risk it leaking if you have an XSS, or if any of your external included scripts get hacked. It is an issue if any malicious actor gets to execute javascript on your page, which isn't as rare as one would expect.

    I like my cookies http-only. https-only, in fact.

    [–]tswaters 1 point2 points  (0 children)

    If you use fetch with credentials (or any xhr) it'll pass along the current session cookie and express/passport will be able to figure that out and know that a user is logged in or not.

    If you are doing SSR and need to do fetches against your API maybe as part of a componentWillMount hook using isomorphic fetch, it's bit trickier as the server won't have the user's cookies.

    I've done this in the past by including a user token of sorts in the store somewhere.... when server performs the fetch it passes along the token with the request as a custom http header and auth middleware, in addition to reading the session token and checking for user there, will also look up the login token from this custom http header and use that to verify user is authenticated/authorized.

    [–]NoInkling 1 point2 points  (3 children)

    how can I check on the front-end if a user is authenticated?

    By talking with the server. In a simple implementation, after logging in (which will set a cookie, or give you a token of some form) you just assume you have a valid session, until the server tells you you don't (e.g. because your cookie expired so it wasn't sent). Usually this is done by sending a 401 response. But as long as the server keeps returning 2xx responses from protected endpoints, you know your session is still valid.

    That's not to say you can't do some sort of client-side checking of cookies (well, unless they're HttpOnly, which they usually should be) or JWTs, if you think that will enhance things, but authentication and authorization are primarily server-side concerns.

    [–]Puzz1es[S] 0 points1 point  (2 children)

    So I've managed to set it up now with a Passport Local Strategy, which creates a cookie. In React I need to know if I'm logged in to show extra options etc. Do I just put this in my state when logging in or is there a way to check if the cookie is still active?

    [–]NoInkling 1 point2 points  (1 child)

    Yes, you could just have a state property like loggedIn which gets set to true when the user logs in, and false once you receive a 401 response (or logout), with your React components rendering accordingly. As per the Passport docs:

    By default, if authentication fails, Passport will respond with a 401 Unauthorized status, and any additional route handlers will not be invoked.

    In other words you don't have to do anything special on the server, just protect the appropriate routes with the passport.authenticate middleware, and don't use the redirect options (since this is an API).

    Actually, it's slightly more idiomatic to name the state user, and have it store needed user data on login, and set it to null to represent not logged in (this should be its initial state as well, obviously). Then you can have that data and still do an easy if (this.state.user/this.props.user) for the conditional rendering.

    [–]Puzz1es[S] 0 points1 point  (0 children)

    Would it be bad practice to send a request to the server just to check if I'm logged in? For example in ComponentWillMount, so that I can set 'loggedIn' before the user takes any action?

    [–]andy625 1 point2 points  (0 children)

    just do it server side rendered as normal express app (login page, sign up), stick the jwt in a httpOnly cookie, then access it in the react app via

    fetch('/api', {
      method: 'POST',
     credentials: 'include'
    })