Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[POC] add refreshtoken to extend accessToken #537

Closed
wants to merge 2 commits into from

Conversation

derdeka
Copy link
Contributor

@derdeka derdeka commented Feb 7, 2020

Proof of concept for loopbackio/loopback-next#4573

Early feedback welcome.

Basic Idea:

  • Short-lived accessToken (e.g. 10 minutes) and very long-lived refreshToken (e.g. 48 hours) which are issued at login time
  • the accessToken giving access to resources
  • the refreshToken can be used to to create a new accessToken
  • the refreshToken can be revoked, e.g. by logout

@derdeka derdeka force-pushed the feature/refreshtoken branch 2 times, most recently from a8f6698 to 487e699 Compare February 10, 2020 15:14
@derdeka derdeka force-pushed the feature/refreshtoken branch from 487e699 to 980a065 Compare February 10, 2020 15:17
@derdeka derdeka marked this pull request as ready for review February 10, 2020 15:32
@derdeka
Copy link
Contributor Author

derdeka commented Feb 10, 2020

Requesting feedback on this PR. If this is the right direction, i'll add some tests.

@derdeka derdeka requested review from dougal83 and achrinza February 10, 2020 16:19
Copy link
Contributor

@dougal83 dougal83 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think perhaps dropping ttl and possibly even creation date from refresh token model and replacing with a straight expiry date might be better. Extending token could be a reissue with updated expiry date. Revocation of token would be setting the expiry date in the past, forcing a login. Hope that makes sense. I'll double check tomorrow. But it makes sense to me right now.

@@ -51,6 +52,9 @@ export class User extends Entity {
@hasOne(() => UserCredentials)
userCredentials: UserCredentials;

@hasMany(() => UserRefreshtoken)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not hasOne? Can user have multiple?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently the refreshToken is newly generated at login time. As you can login multiple times or from multiple browsers/devices this needs to be a hasMany relation.

Alternatively we could share the refreshToken accross browsers/devices, than a hasOne would fit. But this might decrease security!?

WDYT?

Copy link
Contributor

@dougal83 dougal83 Feb 14, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For simplicity, sharing a single token across devices would have a single point of failure, any detected breach would log out all devices. Could be annoying to user.

Your solution is more flexible/complex, but might think about identifying the authorised device linked to the refresh token maybe? Identifying devices, allows the token to be individually invalidated (i.e. Lost device can have access revoked, although any serious security will remote wipe devices). If one token is compromised, is there any practical difference in access compromised? To get updated refresh token we need to log in regardless don't we? Hmm will need to think more.

Both solutions allow:

  • Local - individual devices to be logged out(delete local token)
  • Server - all devices to be logged out(expire one token vs. expire all tokens)
  • Server - update permissions (update one token vs. update all tokens)

Multiple refresh token adds:

  • Server - log out single device(given identity is known, expire the individual token)

Do what you think fits the scenario best. I'm just offering thoughts for consideration.

Copy link
Member

@achrinza achrinza left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dougal83 +1 for using an expiration instead of TTL. As per-RF7519, the claim should be named exp.

properties: {
refreshtoken: {
type: 'string',
minLength: 8,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minLength: 8

Usually the jwt tokens we generate are very long. What is the reasoning for 8? Why not 1? Or why specify this at all? Just curious

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@emonddr If you look at the generateToken function in
https://github.com/strongloop/loopback4-example-shopping/pull/537/files#diff-90f4ee64422c3d610fc44a185e6d0781R47

A refresh token is the id of a UserRefreshToken record, not a token generated by jwt service.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer a pattern check to the token, but i'm still not sure what is the best way to generate a refreshToken. Currently it's a objectId which might not be the best practice. On a mysql/postgresql database i would just use a UUID as refreshToken and use a regular expression for validation.

Do you have any other ideas how to generate a revokeable token?

@@ -0,0 +1,69 @@
// Copyright IBM Corp. 2020. All Rights Reserved.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@derdeka , cool stuff. I was going to just increase the token lifetime from 10 minutes to 6 hours in my last PR ;).

I am wondering :

  • are you going to add mocha tests?
  • have you interacted with the shopping cart API Explorer to see how this will affect the user experience?
    Developers may want to use the API Explorer to interact with the REST API to debug how things work
    for extended periods of time. It would annoy me when the jwt token would expire in 10 minutes. So I would
    set it for 6 hours while I debug and play with the REST API. How will these changes affect this?
  • What needs to change in the Shoppy application (UI front-end) that was recently added?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@emonddr All good questions. Above @derdeka said he'd add tests after initial feedback.

I think the general idea is for the refresh token to hold the session open on a device for a period of time(say 6 hours, two days or even longer) without the need to log back in. When your access token runs out after 10 minutes, then the refresh token would be used to get new access token without the need to log in again. This would also need to be designed so that the refresh token can be revoked and force a login. Is that correct @derdeka?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • are you going to add mocha tests?

I'll add some tests.

  • have you interacted with the shopping cart API Explorer to see how this will affect the user experience?

On a angular application i would use a serviceworker or service to periodically refresh the accesstoken. Is there something similar in the shopping example?

Developers may want to use the API Explorer to interact with the REST API to debug how things work
for extended periods of time. It would annoy me when the jwt token would expire in 10 minutes. So I would
set it for 6 hours while I debug and play with the REST API. How will these changes affect this?

Good catch, i have currently no concept for this. My use case is a regular user on a angular SPA.

  • What needs to change in the Shoppy application (UI front-end) that was recently added?

I did not have a deep look how the frontend is currently working. I guess a serviceworker could work here too?

const {creation, ttl} = await this.userRefreshtokenRepository.findById(
refreshtoken,
{
where: {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

usually findById should return only one result, any reason adding an additional where filter here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea is to check if the refreshToken is valid for / belongsTo the current user, validated by the authorization system. It's a kind of additional security check.

Copy link
Contributor Author

@derdeka derdeka Feb 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recently learned from @raymondfeng that findById does not respect the where condition. I have to do the check differently.

})
@authenticate('jwt')
// TODO(derdeka) find out why @authorize.allowAuthenticated() is not working
async refresh(
Copy link
Contributor

@jannyHou jannyHou Feb 13, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When would the refresh function get called? I am trying to illustrate the flow of how a refresh token works:

  • first a user login
  • then it gets the token and refresh token
  • then how does it decide when to call the refresh endpoint to extend the login time?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

then how does it decide when to call the refresh endpoint to extend the login time?

In a angular application i would use a serviceworker or service with a timer to call this endpoint periodically in background.

Copy link
Contributor

@jannyHou jannyHou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@derdeka Appreciate your contribution 🎉 I left a comment in https://github.com/strongloop/loopback4-example-shopping/pull/537/files#r378960812, otherwise LGTM.

@raymondfeng
Copy link
Contributor

@derdeka Do see opportunity to add some code to loopbackio/loopback-next#4746?

@dhmlau
Copy link
Member

dhmlau commented Oct 7, 2020

@derdeka, the @loopback/authentication-jwt extension now has the refresh token capability. Would you be interested to rework this PR to use that instead? Thanks.

cc @jannyHou

@mrmodise
Copy link
Contributor

@derdeka, the @loopback/authentication-jwt extension now has the refresh token capability. Would you be interested to rework this PR to use that instead? Thanks.

cc @jannyHou

Based on this comment I recommend we change the status of the PR to draft?

@jannyHou
Copy link
Contributor

jannyHou commented Nov 9, 2020

@mrmodise I am ok with making the POC PR a draft, or if the gaps are big, creating a new PR is probably better. I would respect @derdeka 's decision on how to process the feature.

@mrmodise mrmodise marked this pull request as draft November 9, 2020 19:45
@stale
Copy link

stale bot commented Jul 9, 2021

This pull request has been marked stale because it has not seen activity within two months. It will be closed within 14 days of being stale unless there is new activity.

@stale stale bot added the stale label Jul 9, 2021
@stale
Copy link

stale bot commented Jul 23, 2021

This pull request has been closed due to continued inactivity. If you are interested in finishing the proposed changes, then feel free to re-open this pull request or open a new one.

@stale stale bot closed this Jul 23, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants