npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@camstack/addon-auth-oidc

v0.2.15

Published

OpenID Connect (OIDC) authentication provider for CamStack — Google, Microsoft, Okta, Keycloak, and any standards-compliant OIDC IdP.

Readme

@camstack/addon-auth-oidc

Generic OpenID Connect (OIDC) authentication provider for CamStack. Plugs into the existing auth-provider collection capability so the login screen renders an SSO button next to the local username + password form.

Supports any standards-compliant OIDC IdP: Authentik, Keycloak, Google, Microsoft Entra ID (formerly Azure AD), Okta, Auth0, and any other provider that publishes /.well-known/openid-configuration.


Quick start (Authentik — recommended for self-hosted)

Authentik is the most operator-friendly self-hosted IdP for home labs and small deployments. It also doubles as a SAML / LDAP gateway in front of your existing identity stores.

1. Create the OIDC application in Authentik

In the Authentik admin UI:

  1. Applications → Providers → Create

    • Type: OAuth2/OpenID Provider
    • Name: CamStack
    • Authorization flow: default-provider-authorization-implicit-consent
    • Client type: Confidential
    • Client ID: (leave the auto-generated value)
    • Client Secret: (leave the auto-generated value)
    • Redirect URIs / Origins (RegEx):
      https://your-camstack.example.com/addon/auth-oidc/callback
    • Signing Key: any key (e.g. authentik Self-signed Certificate)
    • Save
  2. Applications → Applications → Create

    • Name: CamStack
    • Slug: camstack
    • Provider: select the provider you just created
    • Launch URL: https://your-camstack.example.com/admin
    • Save

2. Install + configure the addon in CamStack

  1. Admin UI → System → Addons → Install → search @camstack/addon-auth-oidc → install
  2. Admin UI → System → Authentication — verify the new row appears (kind: oidc)
  3. Click the row → Configure → fill the global settings:

| Field | Value | |---|---| | Display Name | Continue with Authentik | | Icon | shield-check (or key, user, …) | | Issuer URL | https://authentik.your-domain.tld/application/o/camstack/ | | Client ID | paste from Authentik provider | | Client Secret | paste from Authentik provider | | Redirect URI | https://your-camstack.example.com/addon/auth-oidc/callback | | Scopes | openid profile email | | Username Claim | preferred_username | | Default Role | viewer |

Save. The login screen now shows an "Continue with Authentik" button.

Important: the issuer URL MUST end with the trailing slash that Authentik adds to its application slug (/application/o/<slug>/). Without it the .well-known/openid-configuration discovery fetch returns 404.

3. Test the flow

  1. Log out (or open an incognito window) → /admin/login
  2. Click Continue with Authentik
  3. Authentik prompts for credentials + consent → on success you land back on the CamStack admin UI as the OIDC user
  4. Admin UI → System → Users — the user is auto-provisioned with the configured defaultRole

Other providers — discovery URL templates

Configure exactly the same fields, only the Issuer URL changes:

Keycloak

Issuer URL: https://your-keycloak.example.com/realms/<realm-name>

In Keycloak: Clients → Create with type OpenID Connect, set Valid redirect URIs to https://your-camstack.example.com/addon/auth-oidc/callback. Username claim depends on your realm setup — preferred_username is the Keycloak default.

Google

Issuer URL: https://accounts.google.com

In Google Cloud Console → APIs & Services → Credentials → OAuth 2.0 Client IDs:

  • Application type: Web application
  • Authorized redirect URIs: https://your-camstack.example.com/addon/auth-oidc/callback

Username Claim: email (Google doesn't issue preferred_username).

Microsoft Entra ID (formerly Azure AD)

Issuer URL: https://login.microsoftonline.com/<tenant-id>/v2.0

In Entra → App registrations → New registration:

  • Redirect URI: Webhttps://your-camstack.example.com/addon/auth-oidc/callback
  • Generate a client secret under Certificates & secrets
  • Under API permissions add openid, profile, email

Replace <tenant-id> with your tenant GUID. For multi-tenant apps use common as the tenant ID.

Username Claim: preferred_username (Entra issues UPN-style logins) or email.

Okta

Issuer URL: https://<your-okta-domain>.okta.com/oauth2/default

In Okta: Applications → Create App Integration → OIDC - Web Application, sign-in redirect URI as above. Use the default Authorization Server (/oauth2/default) or substitute your custom one.

Auth0

Issuer URL: https://<your-tenant>.auth0.com

In Auth0: Applications → Create Application → Regular Web Applications, allowed callback URLs as above.


Configuration reference

| Setting | Default | Notes | |---|---|---| | displayName | OpenID Connect | Login button label | | icon | shield-check | lucide-react icon name | | issuerUrl | (empty) | IdP base URL — <issuer>/.well-known/openid-configuration MUST resolve | | clientId | (empty) | OAuth2 client ID issued by the IdP | | clientSecret | (empty) | OAuth2 client secret — server-side only, never exposed to the browser | | redirectUri | <CAMSTACK_PUBLIC_ORIGIN>/addon/auth-oidc/callback | Public callback URL the IdP redirects to. Override when the server is behind a reverse proxy with a different public origin | | scopes | openid profile email | Space-separated OAuth scopes | | usernameClaim | preferred_username | One of preferred_username / email / sub | | defaultRole | viewer | Role assigned to first-time SSO users — choose admin only when the IdP itself enforces operator vetting |


Multi-IdP deployments

Install the addon multiple times under different addon IDs to support multiple IdPs simultaneously. The Authentication page lists them all and the Login screen renders one button per provider.

Admin UI → Addons → Install → @camstack/addon-auth-oidc
   (becomes auth-oidc with default settings)
Admin UI → Addons → Install → @camstack/addon-auth-oidc-google
   (cloned manifest with id=auth-oidc-google)

Each instance has its own settings panel and its own redirect URI: /addon/<addonId>/callback.


Architecture

┌────────────────────────────────────────────────────────────────┐
│ Browser                                                        │
│                                                                │
│  /admin/login ──┬─► local-auth (username + password)            │
│                 └─► [SSO buttons rendered from                  │
│                      authentication.listProviders()]            │
└────────────────────────────────────────────────────────────────┘
            │
            ▼ click SSO button
┌────────────────────────────────────────────────────────────────┐
│ CamStack server                                                 │
│                                                                │
│  /addon/auth-oidc/start                                         │
│    ├─ build PKCE pair                                           │
│    ├─ generate state                                            │
│    └─ 302 redirect → IdP authorization endpoint                 │
└────────────────────────────────────────────────────────────────┘
            │
            ▼ user logs in at IdP
┌────────────────────────────────────────────────────────────────┐
│ IdP (Authentik / Keycloak / Google / …)                         │
│                                                                │
│  302 redirect with `code` + `state` parameters                  │
└────────────────────────────────────────────────────────────────┘
            │
            ▼
┌────────────────────────────────────────────────────────────────┐
│ CamStack server                                                 │
│                                                                │
│  /addon/auth-oidc/callback                                      │
│    ├─ verify state                                              │
│    ├─ POST token endpoint with code + code_verifier             │
│    ├─ decode id_token (no signature verify yet — see TODO)      │
│    └─ 302 → /api/auth/sso/finish?userId=…&username=…&roles=…    │
│                                                                │
│  /api/auth/sso/finish                                           │
│    ├─ authService.signToken(...) → 24h CamStack JWT             │
│    └─ 302 → /admin/login#token=<jwt>&provider=<addonId>         │
└────────────────────────────────────────────────────────────────┘
            │
            ▼
  SPA picks token off URL fragment, persists to localStorage,
  strips fragment → user is logged in.

Security notes

This addon is functional but not yet hardened for production. Known gaps:

  1. id_token signature is not verified against the IdP's JWKS. The token is base64-decoded and trusted because:

    • It arrived on the back-channel (server-to-server token exchange, not client-controlled)
    • The exchange was bound to a state parameter we generated

    For full RFC 7515 / OIDC Core compliance, replace decodeJwtPayload with jose.jwtVerify(idToken, await jose.createRemoteJWKSet(new URL(jwks_uri))).

  2. State + PKCE storage is in-memory. A 5-minute TTL bounds the exposure but multi-replica deployments will lose state if the user's redirect lands on a different replica. Move to a settings-backed store (ctx.api.settings.set/get) when this becomes operationally relevant.

  3. The nonce parameter is not implemented. Replay protection currently relies on the IdP-issued aud and iss claims being checked downstream; for paranoid setups generate a nonce, include it in the auth URL, and validate it from the id_token.

  4. The /api/auth/sso/finish endpoint trusts query parameters from the addon's own callback handler. A misconfigured reverse proxy that re-emits client query strings could in theory inject claims. Defense-in-depth fix: have the callback handler sign the redirect parameters with auth.jwtSecret (HMAC) and verify on /finish.

If you're running this in production, please open an issue or PR — these gaps are tracked in packages/addon-auth-oidc/src/auth-oidc.addon.ts (file-level docstring) and we'd like to close them.


Troubleshooting

"OIDC discovery failed: 404 Not Found"

The issuer URL is wrong. Hit <issuer>/.well-known/openid-configuration in your browser — you should see a JSON document. Common gotchas:

  • Authentik appends a trailing slash to application slugs (/application/o/<slug>/) — keep it
  • Keycloak is case-sensitive on realm names
  • Cloudflare in front of your IdP needs to allow the well-known path through

"Unknown or expired OIDC state"

Either:

  • The user took longer than 5 minutes to complete login at the IdP
  • The user's redirect landed on a different replica (multi-replica issue — see Security note #2)
  • The IdP corrupted the state value (rare; check IdP logs)

Just retry — this isn't a permanent failure.

"OIDC token exchange failed: 401 invalid_client"

The clientSecret is wrong, or the IdP rejected the redirect URI. Check:

  • The redirect URI configured in the addon settings exactly matches the one registered at the IdP (case-sensitive, trailing slash matters)
  • The IdP's client allows Confidential / Authorization Code grant type

"Login succeeds but the SPA shows a blank page"

Check the browser console — the most common cause is the JWT signing failed because auth.jwtSecret isn't set. CamStack auto-generates one on first boot but if config.yaml is read-only the secret can't persist. Set CAMSTACK_JWT_SECRET env var as a workaround.


License

MIT — same as the rest of CamStack.