Skip to content

Add OIDC SSO login and account linking - #2077

Open
ACodingGenie wants to merge 5 commits into
koush:mainfrom
ACodingGenie:main
Open

Add OIDC SSO login and account linking#2077
ACodingGenie wants to merge 5 commits into
koush:mainfrom
ACodingGenie:main

Conversation

@ACodingGenie

@ACodingGenie ACodingGenie commented Jun 25, 2026

Copy link
Copy Markdown

Adds OpenID Connect (OIDC) authentication to Scrypted, enabling SSO via providers like Authelia. This is a server-side implementation (not a plugin) since it needs to set signed cookies and call UsersService directly.

Features

  • Authorization Code + PKCE flow using openid-client@^5.7.1
  • Flexible auth modes via SCRYPTED_AUTH_TYPE env var: basic (default), oidc, or basic,oidc (both methods enabled simultaneously)
  • Auto user provisioning — creates Scrypted users on first OIDC login using preferred_username → email → sub as the display username
  • Role-based access — configurable JWT claim (roleClaim) and value (adminRoleValue) maps provider roles to Scrypted admin/non-admin
  • Account linking — existing local users can link their account to an OIDC identity via GET /login/oidc/link, and unlink
    from user settings
  • OIDCCore settings device in the Core plugin for configuring discovery URL, client credentials, role claim, and auth type
    via the UI
  • Env var overrides for all config fields (useful for Docker/container deployments)

Configuration

  • SCRYPTED_AUTH_TYPE — basic, oidc, or basic,oidc
  • SCRYPTED_OIDC_DISCOVERY_URL — provider discova.example.com)
  • SCRYPTED_OIDC_CLIENT_ID — OIDC client ID
  • SCRYPTED_OIDC_CLIENT_SECRET — OIDC client sec
  • SCRYPTED_OIDC_ROLE_CLAIM — JWT claim to check for admin role
  • SCRYPTED_OIDC_ADMIN_ROLE_VALUE — value of tha
  • SCRYPTED_OIDC_REDIRECT_URI — override callback URL (for reverse proxy setups)

Security

  • oidcSubject stored as iss:sub (per OIDC spec — sub alone is only unique within an issuer)
  • Username-based account merge removed to prevexisting local accounts
  • oidc_state cookie uses secure, httpOnly, and a 10-minute TTL; contains PKCE verifier and CSRF state
  • Account linking validates the active session hat the OIDC subject isn't already linked to a different account

Code was generated with Claude but thoroughly tested locally. Please review :)

@ACodingGenie

Copy link
Copy Markdown
Author

Frontend code: koush/manage.scrypted.app#17

Adds PKCE (S256 code challenge) to the OIDC authorization flow and removes the usernameClaim and allowUnmappedUsers settings in favor of simpler defaults.
…Type to client

- Fix post-login redirect resolving relative to /login/oidc/callback
- Make all OIDC settings readonly in the UI (env var only config)
- Add ScryptedSettings interface to OIDCCore so it appears in Settings page
- Expose authType in checkScryptedClientLogin response for UI login flow
- Always run syncUsers on a 60s interval so OIDC-created users get
  discovered as plugin devices without requiring a restart
- Guard getDeviceByName('@scrypted/webrtc') against undefined when
  webrtc plugin is not installed
- Store oidcSubject as iss:sub per OIDC spec (Authelia uses UUID sub)
- Remove username-based account merge fallback to prevent account takeover
- Throw on username collision instead of silently merging into existing account
- Add secure flag to oidc_state cookie
- Sanitize preferred_username/email claims before use as user _id
- Add /login/oidc/link endpoint for linking existing accounts to OIDC
- Validate session matches linkUsername at callback to prevent account hijack
- Add linkOidcSubject/unlinkOidcSubject to UsersService
- Expose oidcLinked and unlink button in user settings
@koush

koush commented Jun 25, 2026

Copy link
Copy Markdown
Owner

I think a lot of this can be done already via magic headers accompanied with admin keys, ie a proxy uses admin auth, specifies a user, which is automatically created/logged in. I haven't looked at it in some time, but its a general purpose external auth mechanism that is managed by the proxy layer, and doesn't require server plumbing.

@ACodingGenie

Copy link
Copy Markdown
Author

I think a lot of this can be done already via magic headers accompanied with admin keys, ie a proxy uses admin auth, specifies a user, which is automatically created/logged in. I haven't looked at it in some time, but its a general purpose external auth mechanism that is managed by the proxy layer, and doesn't require server plumbing.

I get that and could try to look into it in the future, but what's wrong with implementing the full flow in the server? Most apps nowadays support full OIDC and it has become a pretty common method for managing users in one place.

@asosnovsky

Copy link
Copy Markdown

I think a lot of this can be done already via magic headers accompanied with admin keys, ie a proxy uses admin auth, specifies a user, which is automatically created/logged in. I haven't looked at it in some time, but its a general purpose external auth mechanism that is managed by the proxy layer, and doesn't require server plumbing.

Not really, at least not in any safe way, I've tried doing that a while back, and it introduces a lot more complexity than just supporting a proper standard.

@koush

koush commented Jun 26, 2026

Copy link
Copy Markdown
Owner

Gotcha, this can definitely be done as a plugin. It's how the cloud and core plugin work. They can create users as needed using the Scrypted APIs. Run your own frontend server and implement whatever endpoints you need and proxy the rest to the backend.

Though I am looking only at this change so far, I'll need to review the ui frontend too.

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.

3 participants