Skip Navigation
Issue Tracker @lemm.ee issue_tracking_bot @lemm.ee
BOT

[BE] Auth overhaul (access tokens, refresh tokens, api tokens) #3636

github.com Auth overhaul (access tokens, refresh tokens, api tokens) by sunaurus · Pull Request #3636 · LemmyNet/lemmy

This PR is not complete (missing items detailed below), but I am submitting it already as a draft to get some early feedback. Please check the description below before checking code - I would reall...

Auth overhaul (access tokens, refresh tokens, api tokens) by sunaurus · Pull Request #3636 · LemmyNet/lemmy

This PR is not complete (missing items detailed below), but I am submitting it already as a draft to get some early feedback. Please check the description below before checking code - I would really appreciate feedback on the overall design which is included in the description. But comments on the partially complete code are of course welcome as well.


Introduction

This PR contains an overhaul of Lemmy authentication. It introduces three new authentication tokens: access tokens, refresh tokens, and api tokens (more details below).

The changes are intended to be backwards compatible - the existing /login endpoint will become deprecated but will remain operational until we are ready to remove it in a future version.

What is wrong with our current authentication?

  1. Auth tokens never expire: https://github.com/LemmyNet/lemmy/issues/3364
  2. Auth sessions can't be revoked by users
  3. There is no support for httpOnly cookie based auth: https://github.com/LemmyNet/lemmy-ui/issues/1252
  4. There is no support for api token based auth - all 3rd party apps require user passwords
  5. All auth tokens have full access to everything, their scope can't be limited

This PR contains intends to solve all these issues.

Proposed solution

This PR proposes to replace the existing auth token with 3 new types of tokens:

Access token

This token can be acquired with either a refresh token or an API token.

The new access token is intended to be a backwards compatible drop-in replacement for the existing auth token, with a few key differences:

  • It expires within 5 minutes (so even if it leaks, it can only be abused within 5 minutes of the leak)
  • It contains a method claim, which can be used later to limit certain activities to specific methods (for example, disallow password changes if the access token was obtained via an API token)

Refresh token

This token can be acquired using username + password (+ 2fa).

It lives in a secure httpOnly cookie (can't be read from browser js), which is limited only to the /api/v3/get_access_token path.

This is intended only for trusted web interfaces (such as lemmy-ui) and can be used to create access tokens with full access to the user. Each refresh token can be considered a separate "session". Each token records its last use time, as well as last use ip address - these values can be displayed to users in some new security UI so they get an overview of their active sessions. Each refresh token expires 2 weeks after it was last used, or when revoked manually by a user.

API token

This token must be manually created by users with a specific label and expiry date.

This is intended for 3rd party apps to avoid users from entering their passwords directly into untrusted code. The api token can be used similarly to refresh tokens to request access tokens, but the created access tokens would have limited access. Each API token will also record their last use time as well as last use ip address. API tokens expire after their user defined expiry date, or when revoked manually.


To summarize the general flow:

  1. Acquire either a refresh token (if trusted web ui) or an API token (if 3rd party app)
  2. Request access token using the token from step 1
  3. Make all API requests with access token from step 2
  4. If access token is close to expiry (or last request failed due to token), get a new access token (and retry last request)
  5. If getting access token fails due to a token error, assume the (refresh or api) token has expired and go back to step 1

Rollout plan

  1. Release the new logic in a minor Lemmy version
  2. Add a migration guide to release notes to allow app developers to migrate to the new APIs
  3. Update Lemmy-ui to use the new endpoints
  4. After some time has passed, remove the old /login endpoint in a backwards-incompatible Lemmy update

TODO in this PR

  • Add refresh token list & revoke endpoints
  • Add api token create & list & revoke endpoints
  • Disallow some actions (new api token creation + password change + reading user e-mail?) when access token method is Api
  • Add some tests

TODO in future PRs

  • Switch lemmy-ui to use new authentication
  • Add security page to lemmy-ui, where users can see and revoke their sessions (refresh tokens), as well as see/revoke/create API tokens
  • Add method for 3rd party apps to redirect users to an API token creation page (with a potential return_url to automatically get back to the app with the created token)
1
Issue Tracker @lemm.ee issue_tracking_bot @lemm.ee
BOT
[BE] Auth overhaul (access tokens, refresh tokens, api tokens) #3636
1 comments
  • As discussed in matrix, there is an issue in the logic that I briefly described in the ticket https://github.com/LemmyNet/lemmy/issues/3364 since it does not check for token reuse.

    The proposed PR switch from never ending access token, to medium lifespan refresh tokens and short lived access tokens. While this is far better, the issue is that since the refresh token has to be stored in the client, it's usually still vulnerable to a lot of different attack vectors, one of the worst being a malicious add-on or even some injections that could bypass the httponly protection for the cookie. In the end, since the browser has to read this cookie, anything running on the browser could read it with enough effort.

    The mitigation I highlighted in the previous ticket was to implement re-use checks for the refresh token, and make them one time only.

    Every time an access token is generated, it should be accompanied by a new refresh token. This refresh token will be silently sent to the backend before the access token expires to get a new access token, and a new refresh token for the next silent refresh.

    If this refresh token has been used before, it means that someone else is using a stolen token, therefore you need to wipe all refresh tokens linked to the user, killing all further silent refresh and forcing a hard re-auth.

    If this keeps happening, a warning should be sent to the user since his browser/account has most likely been compromised.

    This would mean that the DB schema should be modified to have a separate last_used_date and created_date, to be able to check for the following scenarios:

    • User has a valid token that can still be used -> do nothing
    • User has a valid token that has expired but was never used -> remove it when created + lifespan < today
    • User has already used this token -> remove all user's token and warn if necessary

    Originally posted by 0xAnansi in #3636