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

Refresh authToken when it expires #192

Merged
merged 2 commits into from
Sep 28, 2023
Merged

Conversation

Fenn-CS
Copy link
Contributor

@Fenn-CS Fenn-CS commented Jun 20, 2023

Access tokens have lifetimes that might end before the user wants to end their authenticated session. Hence, we need to refresh the user access token after an expiry detection.

Resolves: #175

Copy link
Contributor

@jasonaowen jasonaowen left a comment

Choose a reason for hiding this comment

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

This approach seems reasonable, although there may be an alternative - see below.

src/classes/AuthenticationSession.ts Outdated Show resolved Hide resolved
src/classes/AuthenticationSession.ts Outdated Show resolved Hide resolved
@Fenn-CS
Copy link
Contributor Author

Fenn-CS commented Jun 28, 2023

So apparently, even if the generateRefreshToken is enabled on the FusionAuth dashboard, a request has to be made to have the refreshToken included in the initial login response.

Something like

{
this.fusionAuthClient.login({
      loginId: this.authContext.username,
      password,
      generateRefreshTokens: true,
    })
 }).then(() => {})

in

private processPasswordResponse([password]: string[]): void {
this.fusionAuthClient.login({
loginId: this.authContext.username,
password,

However, the LoginRequest type (interface) does not have that, neither does its parent classes do.

https://github.com/FusionAuth/fusionauth-typescript-client/blob/6f8d2e59362578e2f76b2e520818e3bec99e2a56/src/FusionAuthClient.ts#L6217-L6222

Next step is to expirement with OAuth/OIDC library to see these limitations in FusionAuth's typescript client can be bypassed.

@jasonaowen
Copy link
Contributor

private processPasswordResponse([password]: string[]): void {
this.fusionAuthClient.login({
loginId: this.authContext.username,
password,

It is concerning that this service is requesting passwords from users! I don't know how other rclone backends handle OAuth, but I worry that the inspiration for doing so here came from the back-end. That was a compatibility shim I added to the back-end until the front-end was able to delegate its sign-in screen to FusionAuth - it is not the recommended approach, as user passwords should be exposed to as few services as possible, and ideally none of them.

What I would have expected is for signing in to go through some kind of web browser flow, probably the device authorization grant, so that the user can provide their password exclusively to FusionAuth, as well as go through whatever MFA steps they have configured. Taking the responsibility of doing password and MFA into this app makes the process less secure and requires more code.

Unless you're doing anything FusionAuth-specific, you should hopefully be able to use any OAuth/OIDC client library.

Unfortunately, it looks like you are doing FusionAuth-specific things. So long as you use their vendor-specific APIs rather than the standard OAuth/OIDC APIs, you'll have to continue to use their client library. I'd suggest moving away from using their vendor-specific APIs.

@Fenn-CS
Copy link
Contributor Author

Fenn-CS commented Jun 30, 2023

It is concerning that this service is requesting passwords from users! I don't know how other rclone backends handle OAuth, but I worry that the inspiration for doing so here came from the back-end. That was a compatibility shim I added to the back-end until the front-end was able to delegate its sign-in screen to FusionAuth - it is not the recommended approach, as user passwords should be exposed to as few services as possible, and ideally none of them.

I am sure you know this from looking at the code, but the passwords are still passed to the FusionAuth api directly not passed via the back-end.

Now, I can't remember the specific challenges that Dan faced with trying to authenticate directly with FusionAuth without collecting passwords.

Unfortunately, it looks like you are doing FusionAuth-specific things. So long as you use their vendor-specific APIs rather than the standard OAuth/OIDC APIs, you'll have to continue to use their client library. I'd suggest moving away from using their vendor-specific APIs.

On this, I attempted avoid the FusionAuth typescript client in a bit to get this login method return with refreshToken but eventually realized that, the we have no control over that via the API calls at least according to FusionAuth docs.

https://fusionauth.io/docs/v1/tech/apis/login#request

The Login API is used authenticate a user in FusionAuth. The issuer of the One Time Password will dictate if a JWT or a Refresh Token may be issued in the API response.

So at this point, looks like I am stuck with the FusionAuth dashboard again or maybe one really needs to change the authentication mechanism to move away from this login type completely.

@jasonaowen
Copy link
Contributor

It is concerning that this service is requesting passwords from users! [...]

I am sure you know this from looking at the code, but the passwords are still passed to the FusionAuth api directly not passed via the back-end.

Yes, but my point is that this service should not have access to user passwords, either - only FusionAuth should. By accepting passwords, this service becomes a potential vector for compromising a user's credentials. I'm not saying that's likely, but it's an avoidable risk.

On this, I attempted avoid the FusionAuth typescript client in a bit to get this login method return with refreshToken but eventually realized that, the we have no control over that via the API calls at least according to FusionAuth docs.

https://fusionauth.io/docs/v1/tech/apis/login#request

The Login API is used authenticate a user in FusionAuth. The issuer of the One Time Password will dictate if a JWT or a Refresh Token may be issued in the API response.

So at this point, looks like I am stuck with the FusionAuth dashboard again or maybe one really needs to change the authentication mechanism to move away from this login type completely.

Using the FusionAuth APIs via direct HTTP calls would be the worst of both worlds, unfortunately. If you must use their proprietary APIs, you'll be best off with their client.

To address the specific concern you have, from reading the docs, it appears that in order to get a refresh token via the login API you must pass your application ID:

Because a refresh token is per user and per application, when [the applicationId] parameter is not provided a refresh token will not be returned.

https://fusionauth.io/docs/v1/tech/apis/login#request-body

@kfogel kfogel mentioned this pull request Jul 6, 2023
@slifty
Copy link
Contributor

slifty commented Jul 14, 2023

@jasonaowen I can talk a bit about why we chose to implement login the way we did, though I think that will be best explained by voice at least initially (I do allude to some of the reasons here: #175 (comment) -- ultimately it is because rclone requires pre-configured credentials to connect to a remote host. It can be set up to use the ssh-agent but I'm not certain how ssh-agent handles oauth at the time of writing this).

I don't think that it will be within scope to develop support for oauth as part of this ticket.

It does appear that login() doesn't support refresh tokens, which makes it seem to me that the only solution that doesn't involve changing the auth mechanism is to extend the life of the token. I kicked off a conversation about this over in #175

@slifty
Copy link
Contributor

slifty commented Jul 20, 2023

Jason pinged me to point out that we missed a key line of his point!

To address the specific concern you have, from reading the docs, it appears that in order to get a refresh token via the login API you must pass your application ID

@Fenn-CS it looks like we may well have a solution! Could you take a look at that?

@Fenn-CS
Copy link
Contributor Author

Fenn-CS commented Jul 23, 2023

Jason pinged me to point out that we missed a key line of his point!

To address the specific concern you have, from reading the docs, it appears that in order to get a refresh token via the login API you must pass your application ID

@Fenn-CS it looks like we may well have a solution! Could you take a look at that?

So I replaced the existing login method-call with a custom client call that accepts applicationId in #194. Authentication works.

However, this is what the response looks like :

{
  "token": "eyJhbxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "tokenExpirationInstant": 1692728941867,
  "user": {
    "active": true,
    "connectorId": "e3306678-a53a-0000-1111-1c96f36dda72",
    "data": {},
    "email": "nfebe@withheld.com",
    "fullName": "Nfebe",
    "id": "b3c6c1b6-ffff-cccc-9da2-91a16016e11a",
    "insertInstant": 1690112959586,
    "lastLoginInstant": 1690136941867,
    "lastUpdateInstant": 1690112959586,
    "memberships": [],
    "passwordChangeRequired": false,
    "passwordLastUpdateInstant": 1690000959595,
    "preferredLanguages": [],
    "registrations": [],
    "tenantId": "45586253-3e3b-xxx-xxx-44eae469ec35",
    "twoFactor": {
      "methods": [],
      "recoveryCodes": []
    },
    "usernameStatus": "ACTIVE",
    "verified": true
  }
}

Conclusion: Passing applicationId does not lead to the refreshToken being returned.

cc: @slifty

@Fenn-CS Fenn-CS force-pushed the 175-refresh-fusion-auth-token branch 4 times, most recently from e0fbb5f to d76e362 Compare August 6, 2023 00:19
@Fenn-CS Fenn-CS force-pushed the 175-refresh-fusion-auth-token branch from d76e362 to 4f6124c Compare August 20, 2023 21:17
@Fenn-CS Fenn-CS force-pushed the 175-refresh-fusion-auth-token branch 2 times, most recently from ce0e123 to 88ca5f8 Compare August 30, 2023 16:59
@Fenn-CS Fenn-CS marked this pull request as ready for review August 30, 2023 17:00
@Fenn-CS Fenn-CS force-pushed the 175-refresh-fusion-auth-token branch from 88ca5f8 to f9d50e9 Compare August 30, 2023 23:43
Access tokens have lifetimes that might end before the user wants to end
their authenticated session. Hence, we need to refresh the user access token
after an expiry detection.

Resolves: #175

Signed-off-by: fenn-cs <fenn25.fn@gmail.com>
@Fenn-CS Fenn-CS force-pushed the 175-refresh-fusion-auth-token branch 3 times, most recently from 02cc614 to 2e89e19 Compare September 8, 2023 01:21
@Fenn-CS
Copy link
Contributor Author

Fenn-CS commented Sep 8, 2023

Notes for testing

  • Token expiration time in FusionAuth for sftp-local is set to 6 minutes (and can be changed while testing to a desired value).
  • Our code will refresh the token when it has less than 5 minutes remaining (So with the current setting the tester should expect a new token roughly every 1 minute and 1 second).
  • During testing, monitor the logs to observe a new token being obtained approximately every minute.
  • If the expiration time in FusionAuth is set to 1 hour, expect a new token every 45 minutes, so a new token is obtained every time_set_in_fusion_auth - 5 minutes

The short 6-minute expiration is for local testing only.

Copy link
Contributor

@jasonaowen jasonaowen left a comment

Choose a reason for hiding this comment

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

Thanks for working on this, @Fenn-CS! Very cool to see so much progress.

src/classes/AuthenticationSession.ts Outdated Show resolved Hide resolved
src/classes/AuthenticationSession.ts Outdated Show resolved Hide resolved
src/classes/AuthenticationSession.ts Outdated Show resolved Hide resolved
src/classes/AuthenticationSession.ts Outdated Show resolved Hide resolved
src/classes/SftpSessionHandler.ts Outdated Show resolved Hide resolved
Copy link
Contributor

@slifty slifty left a comment

Choose a reason for hiding this comment

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

Thanks for the progress on this! I identified some changes I think would make this implementation even better / safer!

src/classes/AuthenticationSession.ts Outdated Show resolved Hide resolved
src/classes/AuthenticationSession.ts Outdated Show resolved Hide resolved
src/classes/AuthenticationSession.ts Outdated Show resolved Hide resolved
src/classes/AuthenticationSession.ts Outdated Show resolved Hide resolved
src/classes/AuthenticationSession.ts Outdated Show resolved Hide resolved
src/classes/AuthenticationSession.ts Show resolved Hide resolved
src/classes/SftpSessionHandler.ts Outdated Show resolved Hide resolved
src/fusionAuth.ts Show resolved Hide resolved
src/classes/AuthenticationSession.ts Outdated Show resolved Hide resolved
@Fenn-CS Fenn-CS force-pushed the 175-refresh-fusion-auth-token branch from 2e89e19 to 4f20528 Compare September 13, 2023 23:45
@Fenn-CS Fenn-CS force-pushed the 175-refresh-fusion-auth-token branch 3 times, most recently from c853692 to 1ae60e5 Compare September 16, 2023 21:40
@Fenn-CS Fenn-CS force-pushed the 175-refresh-fusion-auth-token branch 3 times, most recently from 47b9571 to 4df4750 Compare September 18, 2023 11:41
Copy link
Contributor

@slifty slifty left a comment

Choose a reason for hiding this comment

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

Phewf this is so close thank you for all the updates.

Some of my comments are style / naming suggestions which please feel free to take or leave. That said, there are some more important notes here:

  1. A small bug around time calculation (I think)
  2. We can't call authContext.reject when refreshing auth tokens

That second issue is a big one, and even led me to realize that there would be merit to extracting the auth token management from this class entirely (which I'm suggesting we do as a separate issue rather than addressing in this PR).

src/classes/AuthenticationSession.ts Outdated Show resolved Hide resolved
src/classes/AuthenticationSession.ts Outdated Show resolved Hide resolved
src/classes/AuthenticationSession.ts Outdated Show resolved Hide resolved
src/classes/AuthenticationSession.ts Outdated Show resolved Hide resolved
src/classes/AuthenticationSession.ts Outdated Show resolved Hide resolved
src/classes/AuthenticationSession.ts Show resolved Hide resolved
src/classes/AuthenticationSession.ts Outdated Show resolved Hide resolved
src/classes/AuthenticationSession.ts Outdated Show resolved Hide resolved
src/classes/AuthenticationSession.ts Show resolved Hide resolved
src/classes/AuthenticationSession.ts Outdated Show resolved Hide resolved
@Fenn-CS Fenn-CS force-pushed the 175-refresh-fusion-auth-token branch 4 times, most recently from 3f56d08 to 1c6f999 Compare September 27, 2023 10:24
@Fenn-CS Fenn-CS requested a review from slifty September 27, 2023 10:25
Copy link
Contributor

@slifty slifty left a comment

Choose a reason for hiding this comment

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

Sorry for missing this originally -- let's make sure our log calls are using the right levels.

src/classes/AuthenticationSession.ts Outdated Show resolved Hide resolved
Signed-off-by: Fon E. Noel NFEBE <fenn25.fn@gmail.com>
Copy link
Contributor

@slifty slifty left a comment

Choose a reason for hiding this comment

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

Woohoo!!! Well done @Fenn-CS thank you for all your excellent work on this.

@slifty slifty dismissed jasonaowen’s stale review September 28, 2023 18:08

This is now a stale review

@Fenn-CS Fenn-CS merged commit 50b86b6 into main Sep 28, 2023
2 checks passed
@Fenn-CS Fenn-CS deleted the 175-refresh-fusion-auth-token branch September 28, 2023 20:21
slifty added a commit to PermanentOrg/infrastructure that referenced this pull request Oct 26, 2023
The SFTP service needs some additional environment variables to be
populated, as a result of some recent changes to how refresh tokens are
used to generate auth tokens [1].

Issue #138

[1] PermanentOrg/sftp-service#192
slifty added a commit to PermanentOrg/infrastructure that referenced this pull request Oct 26, 2023
The SFTP service needs some additional environment variables to be
populated, as a result of some recent changes to how refresh tokens are
used to generate auth tokens [1].

A few of these vriables are redundant [2], and that's why we use the
same "source" variable to map them as late as possible in the
provisioning.  Eventually if the redundancy is removed from the sftp
service we'll want to update the provisioner to stop populating the
obsolete copies.

Issue #138

[1] PermanentOrg/sftp-service#192
slifty added a commit to PermanentOrg/infrastructure that referenced this pull request Oct 26, 2023
The SFTP service needs some additional environment variables to be
populated, as a result of some recent changes to how refresh tokens are
used to generate auth tokens [1].

A few of these vriables are redundant [2], and that's why we use the
same "source" variable to map them as late as possible in the
provisioning.  Eventually if the redundancy is removed from the sftp
service we'll want to update the provisioner to stop populating the
obsolete copies.

Issue #138

[1] PermanentOrg/sftp-service#192
[2] PermanentOrg/sftp-service#289
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Refresh FusionAuth token
3 participants