Feathers: Add support for refresh tokens

Created on 22 Dec 2015  ·  64Comments  ·  Source: feathersjs/feathers

We currently allow getting a new token by posting a valid auth token to <loginEndpoint>/refresh. Refresh tokens have a slightly different workflow as explained here:
https://auth0.com/learn/refresh-tokens

Authentication Feature

Most helpful comment

This feature is a MUST when it comes to React Native apps. The user logs in at the beginning and when he opens the app after several weeks he expects to be still logged in.

All 64 comments

:+1: @corymsmith and I were talking about this. Hoping to help kick some of this over the finish line over the "holidays".

We have support for this in master but also have support for this in the decoupling branch. To refresh a token you have 2 options:

  1. You can either re-authenticate using email/password, twitter, etc.
  2. You can pass a valid token to GET /auth/token/refresh

We do have a token renewal process in place, but not quite full refresh token support as described in the Auth0 link I posted above. An actual refresh token works similar to a GitHub auth code/password, but can only be used to get a new JWT token. So even if your JWT token expires, if you have a refresh token you can use that to login again. They are persisted to the database with userId intact and can be revoked at any time. At least, that's what I'm gathering from the Auth0 article.

Ah you are right @marshallswain. Guess I should have clicked the link :wink:

I think for the first cut we'll leave this off the 1.0 milestone then. It's easy enough for people to just re-authenticate.

I kinda vote that we make this a feathers-authentication 2.0 thing.

Great minds.

I am still quite confused as it is not clear of how exactly the authentication workflow works.

What I am currently doing now is.
1.) Client send username & password

 curl -X POST https://xxx/auth/local   -H "Content-Type: application/json"   -d '{ "email":"xxx", "password":"yyy"}'

This returns JWT token.

{"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI1NzhhNjUyN2RkMTZiMjIwMDRhY2ZjNmEiLCJpYXQiOjE0NzAzMjYyODUsImV4cCI6MTQ3MDQxMjY4NSwiaXNzIjoiZmVhdGhlcnMifQ.OVvQbnxfoDGxPFm3Y6tBhRae2Qa6_mDq-PVIo8RcC8Y"}

2.) Then, I put this token in Authorizatin http header to access API.

curl -X GET https://xxx/users  -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI1NzhhNjUyN2RkMTZiMjIwMDRhY2ZjNmEiLCJpYXQiOjE0NzAzMjU1NzYsImV4cCI6MTQ3MDQxMTk3NiwiaXNzIjoiZmVhdGhlcnMifQ._CHdx3RpEuI189t90mXq-IMPXRNuoVh7nBwY1ON7xCY'

The thing I don't understand is next how to actually refresh this token.
What I tried is I send this token to xxx/auth/token/refresh
What I got is just another very long token. I then tried to use both old and this new token to access API. both works... (shouldn't old one be disabled?)

curl -X GET https://xxx/auth/token/refresh  -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI1NzhhNjUyN2RkMTZiMjIwMDRhY2ZjNmEiLCJpYXQiOjE0NzAzMjU1NzYsImV4cCI6MTQ3MDQxMTk3NiwiaXNzIjoiZmVhdGhlcnMifQ._CHdx3RpEuI189t90mXq-IMPXRNuoVh7nBwY1ON7xCY'
{"query":{"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI1NzhhNjUyN2RkMTZiMjIwMDRhY2ZjNmEiLCJpYXQiOjE0NzAzMjU1NzYsImV4cCI6MTQ3MDQxMTk3NiwiaXNzIjoiZmVhdGhlcnMifQ._CHdx3RpEuI189t90mXq-IMPXRNuoVh7nBwY1ON7xCY"},"provider":"rest","token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJxdWVyeSI6eyJ0b2tlbiI6ImV5SjBlWEFpT2lKS1YxUWlMQ0poYkdjaU9pSklVekkxTmlKOS5leUpmYVdRaU9pSTFOemhoTmpVeU4yUmtNVFppTWpJd01EUmhZMlpqTm1FaUxDSnBZWFFpT2pFME56QXpNalUxTnpZc0ltVjRjQ0k2TVRRM01EUXhNVGszTml3aWFYTnpJam9pWm1WaGRHaGxjbk1pZlEuX0NIZHgzUnBFdUkxODl0OTBtWHEtSU1QWFJOdW9WaDduQndZMU9ON3hDWSJ9LCJwcm92aWRlciI6InJlc3QiLCJ0b2tlbiI6ImV5SjBlWEFpT2lKS1YxUWlMQ0poYkdjaU9pSklVekkxTmlKOS5leUpmYVdRaU9pSTFOemhoTmpVeU4yUmtNVFppTWpJd01EUmhZMlpqTm1FaUxDSnBZWFFpT2pFME56QXpNalUxTnpZc0ltVjRjQ0k2TVRRM01EUXhNVGszTml3aWFYTnpJam9pWm1WaGRHaGxjbk1pZlEuX0NIZHgzUnBFdUkxODl0OTBtWHEtSU1QWFJOdW9WaDduQndZMU9ON3hDWSIsImRhdGEiOnsiX2lkIjoiNTc4YTY1MjdkZDE2YjIyMDA0YWNmYzZhIiwiaWF0IjoxNDcwMzI1NTc2LCJleHAiOjE0NzA0MTE5NzYsImlzcyI6ImZlYXRoZXJzIiwidG9rZW4iOiJleUowZVhBaU9pSktWMVFpTENKaGJHY2lPaUpJVXpJMU5pSjkuZXlKZmFXUWlPaUkxTnpoaE5qVXlOMlJrTVRaaU1qSXdNRFJoWTJaak5tRWlMQ0pwWVhRaU9qRTBOekF6TWpVMU56WXNJbVY0Y0NJNk1UUTNNRFF4TVRrM05pd2lhWE56SWpvaVptVmhkR2hsY25NaWZRLl9DSGR4M1JwRXVJMTg5dDkwbVhxLUlNUFhSTnVvVmg3bkJ3WTFPTjd4Q1kifSwiaWF0IjoxNDcwMzI2NDQyLCJleHAiOjE0NzA0MTI4NDIsImlzcyI6ImZlYXRoZXJzIn0.TqUv3051TTGbX4cPfkN-6pOOB5SN9nH-E7TU1HHSsb8","data":{"_id":"578a6527dd16b22004acfc6a","iat":1470325576,"exp":1470411976,"iss":"feathers","token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI1NzhhNjUyN2RkMTZiMjIwMDRhY2ZjNmEiLCJpYXQiOjE0NzAzMjU1NzYsImV4cCI6MTQ3MDQxMTk3NiwiaXNzIjoiZmVhdGhlcnMifQ._CHdx3RpEuI189t90mXq-IMPXRNuoVh7nBwY1ON7xCY"}}

Even weirder things is I tried to use this new token and send to /auth/token/refresh again.
I got even longer token than this one.

I am not sure what I did wrong or misunderstand here. Please suggest.

@parnurzeal we don't really have refresh token support yet. That's why this is a proposed feature.

The way to get a new token is to do a POST to /auth/token with your existing valid JWT or login using another auth mechanism. Looks like you are doing everything correct.

RIght, but please look closely on how I did and the result I got.

Let's use a easy example.
When I request a new token using abcdefghijklmno (just random nonsense token).
Response back is just a longer version of the previous token -> abcdefghijklmnopqrstuvwxyz
If I try to do it again using abcdefghijklmnopqrstuvwxyz, I will get a longer version of it ->
abcdefghijklmnopqrstuvwxyz1234567890 and loop goes on (requesting more you get longer longer version of the previous one).

Also, all three tokens above are all usable at the same time.
Shouldn't the previous token become expired after we request for a new token?

@parnurzeal what I'm saying is do not do what you did because that feature isn't really implemented. Based on the implementation (thus far) the fact that the token keeps growing every time you hit /auth/token/refresh is because we are just shoving the data back into the token. This isn't how it is intended to work and we haven't had time to finish it and why this isn't documented. You are not supposed to use it.

Shouldn't the previous token become expired after we request for a new token?

This is the nature of JWT. They expire on their own from their TTL. If you want to prevent old tokens from being used that have not expired yet then you need to maintain a blacklist. Currently, this is left up to you and we have an open issue (#133) around that but likely won't get to that soon (if ever).

Hi there, I looked into /auth/refresh/token and I came out with something like that:

...
function pick (o, ...props) {
  return Object.assign({}, ...props.map(prop => ({[prop]: o[prop]})));
}

// Provider specific config
const defaults = {
  payload: ['id', 'role'],
  passwordField: 'password',
  issuer: 'feathers',
  algorithm: 'HS256',
  expiresIn: '1d', // 1 day
};
...
// GET /auth/token/refresh
  get (id, params) {
    if (id !== 'refresh') {
      return Promise.reject(new errors.NotFound());
    }

    const options = this.options;

    // Add payload fields
    const data = pick(params.payload, options.payload);

    return new Promise(resolve => {
      jwt.sign(data, config.get('auth').token.secret, options, token => {
        return resolve({token: token});
      });
    });

  }

Is it too naive as implementation? If not I could try to polish, add a couple of tests and create a PR.

@aboutlo thanks for the effort! It's best to wait until v0.8 is out (it's been in alpha for a while now) as there are a bunch of changes that have happened and that route might be going away this week.
I'm cutting a beta release today and currently wrapping up the migration guide. So it won't be long and v0.8 addresses a lot of the current issues with auth.

We've given a lot of thought to refresh tokens so once 0.8 is released (this week) I'd love to take to this issue to discuss. I'll likely put up our preliminary thoughts later this week.

fair enough @ekryski, I will wait for the 0.8 :)

This feature is a MUST when it comes to React Native apps. The user logs in at the beginning and when he opens the app after several weeks he expects to be still logged in.

@deiucanta the good news is that we kept this feature in mind while we designed [email protected]. I don't think it will be long before we get it in place and documented.

that's good news! 👍 looking forward for that

@marshallswain Looking forward for an update on this feature. Please let me know when can we expect this. Or is it already released? Thanks in advance.

@deiucanta In the meantime, until this feature gets released, you could use longer-lived tokens. Once released, you can rotate your auth secret to a new value to wipe out all existing sessions, and get all of your users on the shorter, renewing ones.

@atulrpandey it's not officially released but it's not hard to implement either. You simply add a hook to generate a new refresh token and store that on the user object in the DB and once it is used up or expired you remove it from the user.

@petermikitsh another thing you can do (if you are on mobile) is store a clientId and clientSecret securely on the client and if the JWT accessToken expires you just re-auth with those.

@ekryski can you please provide an example of either one of these strategies? that will be really helpful.
or, will it take long for the official support to be released? this will really help with mobile auth!

On the topic of refresh tokens:

Refresh tokens carry the information necessary to get a new access token. In other words, whenever an access token is required to access a specific resource, a client may use a refresh token to get a new access token issued by the authentication server. Common use cases include getting new access tokens after old ones have expired, or getting access to a new resource for the first time. Refresh tokens can also expire but are rather long-lived. Refresh tokens are usually subject to strict storage requirements to ensure they are not leaked. They can also be blacklisted by the authorization server. - https://auth0.com/blog/refresh-tokens-what-are-they-and-when-to-use-them/

So I got into React Native myself sooner than I thought I would. I'm thinking of possibly contributing this, but I'd want to be sure I fully understand the mechanics to be sure it's the correct implementation. Since refresh tokens are not stateless, there will be some constraints on usage (e.g., developers will need to supply a storage adapter).

Can you use a valid JWT to get a refresh token? Or are refresh tokens automatically returned in the authentication response (e.g., in addition to the accessToken)? It looks like Auth0 is including them in authentication responses (both accessToken and refreshToken) in their example at https://auth0.com/learn/refresh-tokens/.

@petermikitsh I don't think you should be able to use a valid JWT to get a refresh token. If you do that, anyone can get a JWT and keep access to an account.

Refresh Tokens are typically returned with login/signup responses and then the client can't really get access to them again for the specific session unless they login/signup again which gives them a new session and a new refresh token.

Refresh Tokens don't really need to expire but they can be evoked if they are being stored in the database and so this way, the user can also see how many active sessions they have. You could store more info when issuing refresh tokens (like os, ip, device name etc. to make them identifiable - like how Facebook, GitHub do).

At least that's how I do it.

It should be ok to use a valid JWT to get a refresh token as long as you check authorization both when you issue the refresh token and when you attempt to use the refresh token.

@marshallswain

We currently allow getting a new token by posting a valid auth token to <loginEndpoint>/refresh. Refresh tokens have a slightly different workflow ...

So then it should be called renew not refresh to avoid the confusion <loginEndpoint>/renew

As @abhishekbhardwaj said

accessToken, should not be refreshable by an accessToken, but only by a refresehToken or username/password, a refreshToken should only be refreshable by a user/password authentication, or some other secret, which is not accessible by the browser, like a 2 factor auth...

Currently, it is possible to refresh your accessToken with an accessToken, like mentioned here:

https://github.com/feathersjs/authentication-jwt/issues/61

Another approach is to store refresh token inside accessToken's payload, then current refresh api checks if refresh token is not revoked (through database or redis call). This way refresh token could be a simple auto-increment id. Also refresh api should not check expiration anymore. Since refresh token (simple integer) is signed with access token, it's secure.

This way changes to current code base should be minimal: For refresh api provide a way so that user can provide a hook to check if a access token is not revoked (through checking refresh token inside it's payload), if this hook is provided don't validate expiration time anymore.

Could you explain this approach some more @arash16 ?

If you store the refresh-token in the accessTokens payload, and the refresh API doesn't check expiration, haven't you just effectively made every accessToken "non-expiring"

Because any accessToken could be used to get a new access token, right?

Am I missing something?

@BigAB
I meant don't check expiration only for refresh api, the same accessToken is used as both refresh-token and access-token. This token is non-expiring only for refreshing and getting a new access-token, the refresh-id itself could be revoked by user manually.

Developer should have a db-table/redis to store all refresh-ids. When a user needs to revoke or sign out of some (or all) other sessions, we can provide him a list of all refresh-ids (plus some other extra info such as browser or creation date etc) and he chooses to remove (sign-out of) them selectively. After that once the actual token containing those refresh-ids is expired, refresh api refuses to give a new one.

The refresh-id inside token is not used most of the time and authorization is stateless until the token expires, after that we may have a single call to db to validate refresh id and return a fresh access-token.

Access token's expiration time could be short (less than 10 minutes), user may close the page and walks away, later when he opens the page access-token is already expired and he is logged out. But refresh-id inside the token has a much longer time-to-live managed by database (for example 7 or 30 days), and also manually revocable.

From security point of view, the access-token used this way should be treated like an old session-key, with extra benefit that we won't have to call database to validate it every time (only once expired).

@arash16, I like your idea to store the refresh token inside the access JWT. Is there any example, how to retrieve this refresh token on the server side?

My current problem: If the access token is expired, the payload is not available in feathers's hook context. I guess, a way would be to use the verifyJWT() utility function of the @feathersjs/authentication package, e.g. in the very beginning of app.service('authentication').hooks({ before: { create: ... } })?

Is there any mindful way of using refresh tokens in feathers right now? Any plans of adding support for them?

Hello All ,

Looks like it is still not available ( or Am I missing some thing ) . Could you please let us know when it will be available ? I see there is passport refresh token repository available . Any one tried this ?
https://github.com/fiznool/passport-oauth2-refresh

Hello @daffl ,
Can anyone help me to understand how token can be refresh for the google strategy? Because I will not have password for the google login scenario?

Thanks

Another approach is to store refresh token inside accessToken's payload

So anyone who have even one of your even expired access tokens will be able to easily generate an endless count of a new access tokens or do I miss something?

Refresh tokens should be securely stored on the client and nobody except this client should have access to them!

@deiucanta the good news is that we kept this feature in mind while we designed [email protected]. I don't think it will be long before we get it in place and documented.

This was posted in 2016. Guys, do you still have plans to support this must-have feature?

No. You can't do anything with an expired token. Also, refresh tokens are much more easily possible in the v4 prerelease. A cookbook entry for how to do it will be part of the final release.

Also, refresh tokens are much more easily possible in the v4 prerelease.

Is there an approximate release date?

A cookbook entry for how to do it will be part of the final release.

Until this guide is released, could you explain in a few words how it can be done in v4 prerelease?

@daffl ping

Is there an official way to do this yet? v4 is here and I don't see anything in the docs

@daffl could you elaborate how this is achievable with 4.0 and without hacks to the authentication service?

@MichaelErmer as a workaround you can use local or any custom strategy to renew jwt, not ideal, but works fine for internal communication, let's say between worker and api.

function initAuth() {
  return async (ctx) => {
    if (ctx.path !== 'authentication') {
      const [authenticated, accessToken] = await Promise.all([
        ctx.app.get('authentication'),
        ctx.app.authentication.getAccessToken(),
      ]);

      if (!accessToken || !authenticated) {
        const result = await ctx.app.authenticate(apiLocalCreds);
        ctx.params = {
          ...ctx.params,
          ...result,
          headers: { ...(ctx.params.headers || {}), Authorization: result.accessToken },
        };
      } else {
        const { exp } = decode(accessToken);
        const expired = Date.now() / 1000 > exp - 60 * 60;
        if (expired) {
          const result = await ctx.app.authenticate(apiLocalCreds);
          ctx.params = {
            ...ctx.params,
            ...result,
            headers: { ...(ctx.params.headers || {}), Authorization: result.accessToken },
          };
        }
      }
    }
    return ctx;
  };
}

client
  .configure(rest(apiHost).superagent(superagent))
  .configure(auth(authConfig))
  .hooks({ before: [initAuth()] });

Currently I'm using this after hook in v4 authentication, to update my accessToken after 20 days...

````javascript
const {DateTime} = require('luxon')
const renewAfter = {days: 20}

module.exports = () => {
return async context => {
if (
context.method === 'create' &&
context.type === 'after' &&
context.path === 'authentication' &&
context.data && context.data.strategy === 'jwt' &&
context.result &&
context.result.accessToken) {
// check if token needs to be renewed
const payload = await context.app.service('authentication').verifyAccessToken(context.result.accessToken)
const issuedAt = DateTime.fromMillis(payload.iat * 1000)
const renewAfter = issuedAt.plus(renewAfter)
const now = DateTime.local()
if (now > renewAfter) {
context.result.accessToken = await context.app.service('authentication').createAccessToken({sub: payload.sub})
}
}
return context
}
}
````

It's important to have this hook in after and as last hook, so that all the verifications etc have passed

Any plans to integrate refresh tokens in feathers?

I second that question one message earlier.

Am wondering about the refresh token workflow as well. Is the solution drafted by @m0dch3n a good practice? Should we implement it another way ?

The whole refreshToken workflow in my opion only protects only a little bit against man in the middle attacks, so that if the middle man steals the accessToken he can at least not refresh it and have infinite access to the ressources.

It does not protect against XSS, because in that case, the attacker is able to steal anything stored on the client side. So also the refreshToken...

The problem now is, that if you make your accessToken expiration time too small (i.e. 5 minutes), you also have too refresh it more often. The man in the middle only needs to listen during 5 minutes to the clients requests in order to intercept the refreshToken then... If you make the expiration longer, he has longer access with just the accessToken...

Honestly if some client tells me, his access got stolen, I need to blacklist accessToken AND refreshToken anyway to be sure. So I'm forced to make a DB request on each request anyway.

In my case, when I'm aware of such a case, I blacklist all the accessTokens from the last 40 days, because my accessTokens have a validity of 40 days...

Using HTTPS request makes man in the middle attacks really difficult. Aren't you using HTTPS requests?

Of course I'm using https, but there are 3 possibilities to steal the accessToken. First is on client side (XSS i.e.), second on transport (man in the middle), and third on server side.

On client and on transport, I'm only half responsible for the security, and the other half is the client, which is not totally under my control. But I can help the client, to avoid security risks, by making XSS impossible and by securing the transport with https...

The goal of a refreshToken is, to make the expiration of an accessToken shorter AND to not transmit a longer or infinit valid token on EACH request

So the only security it brings, is that from 100 requests i.e, you don't make all the 100 vulnerable on transport, but only 1 request

So basically a man in the middle attack, can't be protected by a refeshToken and of course not by an XSS... It can only be reduced, by how many times you transmit this refreshToken... The cost of transmitting it lesser however is, that the accessToken needs to be longer valid...

I just copy/past my comments from Slack channel:

I think refresh token is a must support feature and it is not about automatically renewing existing access token. Access token is stateless and won’t be stored in server side. The down side is it is valid forever! The longer of access token, more risk is imposed. But if access token is too short, then your users have to login quite often, that will greatly impact usability.

That’s where refresh token comes in, the token used to refresh access token and it is a long live token. When access token expired, client can use refresh token to get a new access token, and that’s the only purpose of refresh token.

Refresh token is revokable in case of user account has been compromised. And that’s the big difference between access token and refresh token. To revoke issued refresh token, server must store all issued refresh tokens. In other words, refresh token is stateful. Server needs to know which one is valid which one is invalid.

To properly implement refresh token, we need some sort of token store to persist refresh token. We also need to implement at least three flows:

Refresh token validation
Refresh access token with valid refresh token
Revoke compromised user’s refresh token

There are other management functionalities also nice to have such as token usage stats.

Above is my current understanding regarding how to implement refresh token. It is not easy but it definitely necessary to build a more secure system.

It turns out Feathers already built-in all functionalities/modules required to properly implement refresh-tokens:

  1. Refresh-token store: can be easily supported by Feathers Service.
  2. Issuing and validating refresh token: can just re-used existing JWT support which built-in AuthenticationService.

Based on the work done by TheSinding (https://github.com/TheSinding/authentication-refresh-token), I implemented my own version of refresh-tokens with one custom service and three hooks (https://github.com/jackywxd/feathers-refresh-token) which enables basic refresh-tokens functionalities:

  1. Issue refresh-token after user authentication successfully;
  2. Refresh access token with a valid JWT refresh-token;
  3. Logout user by deleting the refresh-token

While fully leverage existing code base in Feathres, the actually coding effort is minimum, and it integrates with current Feathers architecture nicely. It proves that current Feathers architecture is very extendable.

But a full feature of Refresh-token also requires support at Client side, such as store refresh-token in client side, reAuthenticate user after access-token expiration, logout user with refresh-token.

After review the source code of feathers-authentication and authentication-client, I believe refresh-token could be tapped into existing Features code based to allow turning on refresh-token support as easy as turning on authentication.

I already ported my hooks version refresh-token code base into @feathersjs/authentication. Next I would try to make change on authentication-client to enable client side features. My ultimate goal is to enable refresh-token support in both server and client side.

My question/concern is how would the refresh token be stored in the client?

See https://auth0.com/blog/securing-single-page-applications-with-refresh-token-rotation/

Unfortunately, long-lived RTs are not suitable for SPAs because there is no persistent storage mechanism in a browser that can assure access by the intended application only. As there are vulnerabilities that can be exploited to obtain these high-value artifacts and grant malicious actors access to protected resources, using refresh tokens in SPAs has been strongly discouraged.

See https://afteracademy.com/blog/implement-json-web-token-jwt-authentication-using-access-token-and-refresh-token

So, what is the best possible place to store the tokens securely? You can read more about it on the internet if you are passionate to achieve completely secure storage. Some of the solutions are ideal but not very practical. Practically I would store it in the Cookies with httpOnly and Secure flags. It is not 100 percent secure but it gets the job done.

See this long discussion on cookie - https://github.com/feathersjs-ecosystem/authentication/issues/132 too

@bwgjoseph I would suggest to use regular express session, and store all tokens there, instead of client side. That's what I do and works perfectly fine with all types of apps including SPA

@sarkistlt You mean to say to store all client JWT token on server-side? Any reference/article material for that? I'm not exactly sure how the process would be like. So what does client sent, when they request for data (CRUD)?

@bwgjoseph same as always, cookie, just add middlewere before registerring your services:

app.use('* | [or specific rout]', session(sess), (req, res, next) => {
      req.feathers.session = req.session || {};
      next();
    });

then in your server, for let's say customer login service, when customer is authenticated, you just store token in the session like ctx.params.session.token = token, where token is your JWT access or refresh token, depends on your application logic.
And with any new request from client you will check if token exist in the session and will use it for authentication. This is much safer and secure approach, since none of the tokens are exposed on the client side at all.

I'll just add that this works best for client (browser) - server applications. When communicating internally between servers, or worker/server, you don't need session.

This has been discussed _a lot_ before (I also added an entry to the FAQ) and it is not necessarily more secure to store a token in a session. If someone gets access to your page to be able to execute scripts they have also hijacked the session and can make authenticated requests anyway.

Hence it is usually ok to store a token in e.g. localStorage (which only the current page has access to as well) and it also works seamlessly with other non-browser platforms (like native mobile apps, server-to-server etc.) __and websockets__ (I can't stress enough how painful it is to make websockets work seamlessly and securely with HTTP cookies - my life has been a lot easier since we stopped trying to do so). In general, a refresh token should be revokable though since it is usually a lot more long lived.

Either way, a pull request for this would be very welcome, I find it makes ironing out the details a lot easier.

@jackywxd - Great job, took a brief look at it and it seems like some great additions.
Is there anyway of making it easier for the developer implementing this?
Like integrating the hooks you've created into the library, so we wouldn't need to add the hooks later ?

I think you should create a pull request and we could have the discussion there.

@daffl yes agree, especially with WS. And if both client and backend build by you or your team, yes it's best to just use JWT and avoid extra dependancies and complexity in your application.
But in some cases, for example when building storefront REST-API that will be used by 3rd party companies / developers, it's easier to use regular session, and ask developers to include credentials with their requests then to describe how to retrieve access (and refresh) tokens, store it, and pass it with each requests. Which handled perfectly by feathers client, but in most cases when developer is not familiar with how backend built, they will use request, superagent, fetch or axios to connect their application to the backend. At least in my case this was the main reason to move storefront part of API to work with regular sessions instead of JWT directly.

But wouldn't that be the design decision and responsibility, of the maker of said storefront, to implement into their own API and then properly document this feature, instead of "forcing" this decision onto the community ?

@TheSinding I think we can say 'forcing' about less commonly used approach, not about cookie which have been (and still is) most commonly used approach to manage user sessions.

And yes it is a design decision made after developers feedback that have been using the API. When you are running multi-tenant system or API that used by multiple teams, sometime best is to follow general industry practice to avoid additional confusion and extra time spent by developers, especially if alternative solution doesn't provide any advantage for end user.

Again to be clear, using JWT is great, and way easier to utilize especially for real time API and that's what we are using in 90% of cases + you don't need to run redis or something else to manage cookie sessions.
But there some exceptional cases where it may not be the best chose, I brought example of that situation in my previous comment.

I wasn't saying you were wrong, I was just thinking "out loud" :)

IMO, I think the approach with JWT would be a better approach, since that is what is already supported and as @daffl it's also easier to work with

Thanks for all your feedback! JWT vs session is a different topic we can discuss separately.

I think one thing we all agree is that access token + refresh token is a much more secure solution than merely access token. It has been widely adopted by major Internet giants. It is fair to say the Feathers community would love to see Feathers main code base to provide built-in support for refresh token. Actually it surprised me when I first realized that Feathers doesn't support refresh token.

I have been using AWS cognito in couple of my projects, AWS Amplify will save three tokens in localStorage: ID token, access token and refresh token, Amplify handles all the dirty-works related to token management, and provides couple APIs that enables easy and straight forward interface working with Cognito backend. I would like to see similar development experience with Feathers.

@TheSinding Thanks for your previous great work!

Because existing authentication is implemented as normal service, we could easily enable refresh-token support by extending the class and adding couple hooks:

this.hooks({ after: { create: [issueRefreshToken(), connection('login'), event('login')], remove: [logoutUser(), connection('logout'), event('logout')], patch: [refreshAccessToken()], },

Just like we turn on Authentication by using the CLI, we could offer the similar option in CLI for refresh-token support. developer can simply answer YES, the CLI then automatically creating a customer "refresh-tokens" service and update refresh-token related configurations in default config file. So it is like a turn-key solution for developers.

@daffl David, thanks for creating Feathers! just wondering is there any "contributing guid", "coding guideline", "style guide" for Feathers?

As I mentioned before, a PR with the implementation for refresh tokens (or even just some ideas) would be very welcome. I didn't get a chance yet to check out the linked repos and this discussion is getting quite long. Having it all up to date and in one place would make things a lot easier. Some important bullet points:

  • Refresh tokens can be issued by extending the Authentication Service
  • Refresh tokens should be stored in localStorage
  • Refresh tokens need to be revokable (so there needs to be a more general purpose implementation of the revocation mechanism described in https://docs.feathersjs.com/cookbook/authentication/revoke-jwt.html)
  • Because of the additional complexity and setup (like Redis for storing revoked tokens) it can be available as a feature but shouldn't be enabled by default (i.e a standard generated app - can be part of the CLI but before doing anything on that, I'm still really looking for help in starting a hygen based generator)
  • Contributing info can be found in the contributors guide

If someone is using refreshToken, we create a library that handles in frontend, accessToken and refreshToken like "sessions", too easy to use.

Only need to pass the tokens and the library tries to obtain a new acessToken with refreshToken when will expire. Currently is used in Videsk.

Repository: Front Auth Handler

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Vincz picture Vincz  ·  4Comments

stephane303 picture stephane303  ·  3Comments

corymsmith picture corymsmith  ·  4Comments

davigmacode picture davigmacode  ·  3Comments

rstegg picture rstegg  ·  3Comments