Skip to content

feat(auth): add login tokens for authentication#2703

Open
Orvanix wants to merge 12 commits intoseerr-team:developfrom
Orvanix:feature/token-auth
Open

feat(auth): add login tokens for authentication#2703
Orvanix wants to merge 12 commits intoseerr-team:developfrom
Orvanix:feature/token-auth

Conversation

@Orvanix
Copy link

@Orvanix Orvanix commented Mar 15, 2026

Description

Adds login token authentication to Seerr, allowing users to authenticate via a direct URL without entering credentials.

This feature is intended for environments where providing secure credentials is difficult or impractical. For example, if you would like to use seerr on a home management tablet.

Features

Token Management

  • Generate and revoke login tokens per user from the user settings page
  • Token is displayed once when generated and is not recoverable afterwards
  • Two copy-to-clipboard buttons: one for the raw token, one for the full login URL
  • Button label reflects current token state (Generate / Regenerate)
  • Token section is hidden for users when allowSelfLoginToken is disabled

Admin Setting

  • New setting allowSelfLoginToken in Users settings controls whether users can generate their own tokens
  • When disabled, only admins (MANAGE_USERS permission) can manage tokens on behalf of users

Token Login

  • User opens https://your-instance/?token=your-token
  • Next.js SSR layer forwards the token to POST /api/v1/auth/token
  • Token is SHA-256 hashed and matched against the database
  • Invalid or missing token returns 400 / 403
  • On success: session is created and user is redirected to the original path without the token parameter

Header-based API Access

  • Existing X-Login-Token header support extended to use the new loginToken column

API Endpoints

Endpoint Method Description
/api/v1/user/{id}/settings/token GET Check whether a login token exists for a user
/api/v1/user/{id}/settings/token POST Generate a new login token for a user
/api/v1/user/{id}/settings/token DELETE Revoke the login token for a user
/api/v1/auth/token POST Authenticate via token and create a session

Usage

```
https://your-instance/?token=your-uuid-token
```

Security Considerations

  • Login tokens are stored as SHA-256 hashes in the database and are never stored in plain text
  • Tokens are only shown once at creation and cannot be retrieved afterwards
  • Revoking a token immediately invalidates it
  • allowSelfLoginToken is disabled by default — token management is admin-only unless explicitly enabled
  • The select: false column option prevents the hash from appearing in any API response

How Has This Been Tested?

  • Verified that login tokens can be generated and deleted from user settings
  • Verified that the generated token URL (/?token=...) logs in the user and redirects correctly
  • Verified that the token is not shown again after page refresh (one-time display)
  • Verified that the allowSelfLoginToken setting correctly shows or hides the token section for non-admin users
  • Verified that admins can generate tokens for other users regardless of the setting
  • Verified that an invalid or missing token returns the correct error response

Screenshots / Logs (if applicable)

image

Fresh generated Token
image

AI / Claude

Claude was used to help with implementation ideas, documentation, and drafting this summary and commit messages. It was also used to review parts of the code and suggest adjustments to better follow the conventions used in Seerr.

Checklist:

  • I have read and followed the contribution guidelines.
  • Disclosed any use of AI (see our policy)
  • I have updated the documentation accordingly.
  • All new and existing tests passed.
  • Successful build pnpm build
  • Translation keys pnpm i18n:extract
  • Database migration (if required)

Summary by CodeRabbit

  • New Features

    • Login token authentication: sign in with a personal token via header or URL.
    • Per-user token management in profile: generate, regenerate (previous token invalidated), copy token or login URL, and delete tokens.
    • Admin setting to allow or restrict users from self-generating tokens (disabled by default).
  • Documentation

    • Added docs describing token usage, lifecycle, generation rules, and admin controls.

@Orvanix Orvanix requested a review from a team as a code owner March 15, 2026 14:26
@coderabbitai
Copy link

coderabbitai bot commented Mar 15, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7f8d12a6-bf5c-4533-9953-bf36711ebf11

📥 Commits

Reviewing files that changed from the base of the PR and between bd2af99 and 8290a60.

📒 Files selected for processing (2)
  • src/components/Settings/SettingsUsers/index.tsx
  • src/i18n/locale/en.json
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/components/Settings/SettingsUsers/index.tsx
  • src/i18n/locale/en.json

📝 Walkthrough

Walkthrough

Adds login-token authentication: DB column and migrations, middleware and /auth/token endpoint, per-user token lifecycle routes, settings flag to allow self-generation, admin and user UI for token management, and OpenAPI documentation updates.

Changes

Cohort / File(s) Summary
Documentation
docs/using-seerr/settings/users.md, docs/using-seerr/users/editing-users.md
New docs describing the "Allow Users to Generate Login Tokens" setting, token usage via X-Login-Token or token URL param, and token lifecycle notes (one-time display, invalidation on regen).
API Specification
seerr-api.yml
Added Login Token Authentication to global description and new endpoints: POST /auth/token, and `GET
Database & Entity
server/entity/User.ts, server/migration/postgres/...-AddLoginToken.ts, server/migration/sqlite/...-AddLoginToken.ts
Added loginToken column to User entity (varchar, nullable, select:false) and migrations for Postgres/SQLite to add/remove the column (SQLite migration performs table rewrite and constraint rework).
Settings Backend
server/interfaces/api/settingsInterfaces.ts, server/lib/settings/index.ts
Introduced allowSelfLoginToken: boolean in settings interfaces and main settings (default false); exposed in full public settings.
Auth Middleware & Routes (Backend)
server/middleware/auth.ts, server/routes/auth.ts, server/routes/user/usersettings.ts
Middleware checks X-Login-Token (SHA-256 hashed) prior to session auth; added /auth/token POST endpoint; added token management routes (GET/POST/DELETE /user/{userId}/settings/token) with permission gating, token generation (randomUUID + SHA-256), storage, deletion, and logging.
Frontend — Settings & Context
src/components/Settings/SettingsUsers/index.tsx, src/context/SettingsContext.tsx, src/pages/_app.tsx
Admin settings: checkbox for allowSelfLoginToken persisted to API; SettingsContext default includes flag; _app.tsx server-side flow handles ?token= by POSTing to /api/v1/auth/token, issuing a 307 redirect without the token.
Frontend — User Profile UI
src/components/UserProfile/UserSettings/UserGeneralSettings/index.tsx
User token UI: generate/regenerate/delete flows, one-time plaintext token display, copy and token-URL copy buttons, SWR for state, toasts for feedback, UI respects allowSelfLoginToken and permissions.
Frontend — Small Component
src/components/Settings/CopyButton.tsx
CopyButton gains optional icon prop and renders it when provided.
i18n
src/i18n/locale/en.json
Added localization keys for token generation, deletion, copying, tips, and related UI messages.

Sequence Diagram(s)

sequenceDiagram
    participant User as User/Client
    participant FE as Frontend
    participant Server as Server
    participant DB as Database

    User->>FE: Click "Generate Token"
    FE->>Server: POST /user/{userId}/settings/token
    activate Server
    Server->>Server: Generate random token (randomUUID)
    Server->>Server: Hash token (SHA-256)
    Server->>DB: Store hashed loginToken
    DB-->>Server: Confirmation
    Server-->>FE: Return plaintext token
    deactivate Server
    FE->>User: Display token once (copy option)
Loading
sequenceDiagram
    participant Browser as Client/Browser
    participant FE as Frontend Server
    participant Auth as Auth Route (/auth/token)
    participant DB as Database

    Browser->>FE: GET /some-page?token=abc123
    FE->>Auth: POST /auth/token { token: "abc123" }
    activate Auth
    Auth->>Auth: Hash token (SHA-256)
    Auth->>DB: Query user WHERE loginToken = hash
    DB-->>Auth: Return user
    Auth-->>FE: Set session (cookie) / return user
    deactivate Auth
    FE-->>Browser: 307 Redirect to same URL without token query
    Browser->>FE: Follow redirect with session cookie
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • fallenbagel
  • gauthier-th

Poem

🐇 I chewed a key from randomUUID,
hashed it snug with SHA so true,
A single glimpse, then hid away—
Hop, copy, hop, the token's play. ✨🔐

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(auth): add login tokens for authentication' directly and clearly summarizes the main change: introducing a new login token authentication feature.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/entity/User.ts`:
- Around line 103-104: User.filteredFields does not include the new sensitive
persisted field loginToken, so any explicit select of loginToken can leak its
value via User.filter(); update the static User.filteredFields array to include
"loginToken" (or add it to whatever filtering set is used) so that User.filter()
and similar redaction logic will omit the loginToken value; ensure you update
any related serialization or toJSON paths that rely on filteredFields to prevent
accidental exposure.

In `@server/migration/sqlite/1773570023580-AddLoginToken.ts`:
- Line 9: The SQLite migration reintroduces an unintended UNIQUE constraint
named UQ_f90ab5a4ed54905a4bb51a7148b on "auth" inside the CREATE TABLE for
temporary_user_push_subscription; either remove that constraint from the
migration (delete occurrences of CONSTRAINT "UQ_f90ab5a4ed54905a4bb51a7148b"
UNIQUE ("auth") and any related ALTER/CREATE statements in
1773570023580-AddLoginToken.ts) to match the entity UserPushSubscription (which
only has `@Unique`(['endpoint','user'])) and the Postgres migration
1743023615532-UpdateWebPush.ts, or alternatively add `@Unique`(['auth']) to
server/entity/UserPushSubscription.ts so the schema intentionally includes the
auth uniqueness—choose and apply one consistent fix across the migration and
entity.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 420dbdd9-4213-4e9d-b2ed-1dd44af4c75b

📥 Commits

Reviewing files that changed from the base of the PR and between 0be1896 and 337d9fe.

📒 Files selected for processing (16)
  • docs/using-seerr/settings/users.md
  • docs/using-seerr/users/editing-users.md
  • seerr-api.yml
  • server/entity/User.ts
  • server/interfaces/api/settingsInterfaces.ts
  • server/lib/settings/index.ts
  • server/middleware/auth.ts
  • server/migration/postgres/1773570292337-AddLoginToken.ts
  • server/migration/sqlite/1773570023580-AddLoginToken.ts
  • server/routes/auth.ts
  • server/routes/user/usersettings.ts
  • src/components/Settings/CopyButton.tsx
  • src/components/Settings/SettingsUsers/index.tsx
  • src/components/UserProfile/UserSettings/UserGeneralSettings/index.tsx
  • src/context/SettingsContext.tsx
  • src/pages/_app.tsx

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
server/migration/sqlite/1773589719678-AddLoginToken.ts (1)

6-42: Redundant user_push_subscription table recreation.

The migration recreates user_push_subscription twice in up() (lines 7-20 and 29-42). Since the only schema change is adding loginToken to the user table, the second recreation (lines 29-42) after the user table modification appears unnecessary—the foreign key reference to user remains unchanged.

This is likely auto-generated output from TypeORM's migration tooling. Consider simplifying to reduce migration runtime:

Simplified up() structure
   public async up(queryRunner: QueryRunner): Promise<void> {
-    await queryRunner.query(`DROP INDEX "IDX_03f7958328e311761b0de675fb"`);
-    await queryRunner.query(
-      `CREATE TABLE "temporary_user_push_subscription" ...`
-    );
-    // ... copy, drop, rename user_push_subscription ...
-    await queryRunner.query(
-      `CREATE INDEX "IDX_03f7958328e311761b0de675fb" ON "user_push_subscription" ("userId") `
-    );
+    // Only recreate user table with loginToken column
     await queryRunner.query(
       `CREATE TABLE "temporary_user" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, ... "loginToken" varchar, ...)`
     );
     await queryRunner.query(
       `INSERT INTO "temporary_user"(...) SELECT ... FROM "user"`
     );
     await queryRunner.query(`DROP TABLE "user"`);
     await queryRunner.query(`ALTER TABLE "temporary_user" RENAME TO "user"`);
-    // Remove second user_push_subscription recreation (lines 29-42)
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/migration/sqlite/1773589719678-AddLoginToken.ts` around lines 6 - 42,
The migration's up() duplicates the recreation of the user_push_subscription
table (the CREATE/INSERT/DROP/ALTER/CREATE INDEX sequence that builds
temporary_user_push_subscription and renames it to user_push_subscription
appears twice); remove the second redundant block so the user_push_subscription
changes happen only once and keep only the user table modification that adds
loginToken (ensure the DROP INDEX/CREATE INDEX for
"IDX_03f7958328e311761b0de675fb" is paired once). Locate the duplicated SQL
sequences referencing temporary_user_push_subscription, user_push_subscription,
and the index and delete the second occurrence, leaving a single, correct
sequence in up().
server/migration/postgres/1773589831561-AddLoginToken.ts (1)

6-10: Consider adding an index on loginToken for authentication lookups.

The migration correctly adds the nullable loginToken column. However, since token-based authentication performs lookups by the hashed token value (POST /api/v1/auth/token), queries will scan the user table without an index. For larger user bases, this could impact authentication latency.

Optional: Add index for token lookups
   public async up(queryRunner: QueryRunner): Promise<void> {
     await queryRunner.query(
       `ALTER TABLE "user" ADD "loginToken" character varying`
     );
+    await queryRunner.query(
+      `CREATE INDEX "IDX_user_loginToken" ON "user" ("loginToken") WHERE "loginToken" IS NOT NULL`
+    );
   }

Note: If the expected user count is small (<1000), this optimization may not be necessary.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/migration/postgres/1773589831561-AddLoginToken.ts` around lines 6 -
10, The migration AddLoginToken.up currently adds the loginToken column but
omits an index, causing full-table scans for token lookups; update the up method
(where QueryRunner.query is used) to also create a non-unique index on
"loginToken" (e.g., CREATE INDEX IF NOT EXISTS idx_user_login_token ON "user"
("loginToken")) and add the corresponding DROP INDEX (or DROP INDEX IF EXISTS)
in the down method so the migration is reversible, ensuring token-based auth
queries use the new index.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@server/migration/postgres/1773589831561-AddLoginToken.ts`:
- Around line 6-10: The migration AddLoginToken.up currently adds the loginToken
column but omits an index, causing full-table scans for token lookups; update
the up method (where QueryRunner.query is used) to also create a non-unique
index on "loginToken" (e.g., CREATE INDEX IF NOT EXISTS idx_user_login_token ON
"user" ("loginToken")) and add the corresponding DROP INDEX (or DROP INDEX IF
EXISTS) in the down method so the migration is reversible, ensuring token-based
auth queries use the new index.

In `@server/migration/sqlite/1773589719678-AddLoginToken.ts`:
- Around line 6-42: The migration's up() duplicates the recreation of the
user_push_subscription table (the CREATE/INSERT/DROP/ALTER/CREATE INDEX sequence
that builds temporary_user_push_subscription and renames it to
user_push_subscription appears twice); remove the second redundant block so the
user_push_subscription changes happen only once and keep only the user table
modification that adds loginToken (ensure the DROP INDEX/CREATE INDEX for
"IDX_03f7958328e311761b0de675fb" is paired once). Locate the duplicated SQL
sequences referencing temporary_user_push_subscription, user_push_subscription,
and the index and delete the second occurrence, leaving a single, correct
sequence in up().

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ce6f3e42-a511-468d-91d5-5a9ca25ac6dd

📥 Commits

Reviewing files that changed from the base of the PR and between 19f326f and 785eb5d.

📒 Files selected for processing (2)
  • server/migration/postgres/1773589831561-AddLoginToken.ts
  • server/migration/sqlite/1773589719678-AddLoginToken.ts

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/i18n/locale/en.json`:
- Line 1049: Update the tooltip value for key
"components.Settings.SettingsUsers.allowSelfLoginTokenTip" to accurately reflect
that token actions are governed by the MANAGE_USERS permission and cover more
than just generation (e.g., creation, management, and revocation), for example:
indicate that when disabled, only users with the MANAGE_USERS permission can
manage or create login tokens on behalf of other users. Ensure the new text
replaces the existing msg and mentions the MANAGE_USERS permission rather than
saying "admins" and broadens "generate" to "manage/create/revoke" (or similar
concise phrasing).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: eb1ccd3b-b0db-466e-96d2-d2ab6156e3f0

📥 Commits

Reviewing files that changed from the base of the PR and between 785eb5d and bd2af99.

📒 Files selected for processing (1)
  • src/i18n/locale/en.json

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.

1 participant