> ## Documentation Index
> Fetch the complete documentation index at: https://notifizz.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Publicly-Signed JWT Widget Authentication

> Authenticate the Notifizz widget with a JWT issued by Auth0, Clerk, Cognito, Supabase Auth, or any provider that exposes a JWKS — dashboard-configured.

# Publicly-signed JWT widget authentication

`PubliclySignedJwt` is the widget auth mode for apps that already use a managed identity provider — Auth0, Clerk, Cognito, Supabase Auth, or any provider that publishes a JWKS endpoint. Unlike the other three modes, it has **no SDK shorthand**: the strategy is configured server-side via the dashboard, and your widget call site stays minimal.

## TL;DR

* Configured via the dashboard `notificationCenterSetup` dev-task — JWKS URL + claim paths for `id` and `email`.
* The widget passes a JWT issued by your IDP to `/v1/users/auth`; Notifizz verifies it against the JWKS.
* No `authType: "publiclySignedJwt"` in the SDK — the call site uses your existing IDP session, the dashboard wires the strategy.
* In backend code and logs, the strategy is named `CustomRsaJwt` — same thing.

<Note>
  Customer-facing prose always uses `PubliclySignedJwt`. Backend code, logs, and a few dashboard internals show `CustomRsaJwt` — they're the same strategy. You'll see the alias once if you trace requests through backend logs; the rest of the time, ignore it.
</Note>

## When to pick this mode

| Situation                                                                   | Recommended mode                                          |
| --------------------------------------------------------------------------- | --------------------------------------------------------- |
| Already using Firebase Auth                                                 | [Firebase](/docs/sdks/authentication/firebase)            |
| Custom auth, want server-minted token                                       | [Backend Token](/docs/sdks/authentication/backend-tokens) |
| **Using Auth0 / Clerk / Cognito / Supabase Auth / any JWKS-publishing IDP** | **`PubliclySignedJwt`**                                   |
| Local dev only                                                              | [None](/docs/sdks/authentication/no-auth)                 |

`PubliclySignedJwt` shines when you want to decouple Notifizz from your backend availability — the widget verifies against the IDP's JWKS, so widget auth keeps working through partial backend outages. It also reduces the surface area of `authSecretKey` (you don't need it; only the public JWKS URL is configured).

## Dashboard setup

The `notificationCenterSetup` dev-task in the dashboard guides you through:

<Steps>
  <Step title="Pick the strategy">
    In environment settings, set the widget auth strategy to `PubliclySignedJwt`.
  </Step>

  <Step title="Provide the JWKS URL">
    Paste your IDP's JWKS endpoint. Examples:

    * **Auth0** — `https://<tenant>.auth0.com/.well-known/jwks.json`
    * **Clerk** — `https://<frontend-api>.clerk.accounts.dev/.well-known/jwks.json`
    * **Cognito** — `https://cognito-idp.<region>.amazonaws.com/<userPoolId>/.well-known/jwks.json`
    * **Supabase Auth** — `https://<project-ref>.supabase.co/auth/v1/jwks`
  </Step>

  <Step title="Set the expected issuer and audience">
    The dashboard captures the `iss` (issuer) and `aud` (audience) claims your IDP emits. Notifizz rejects tokens whose claims don't match.
  </Step>

  <Step title="Map the identity claims">
    JWTs vary in where they put the user id and email. Configure two paths:

    * `paths.id` — JSON path to the user identifier in the token payload (e.g. `sub`, `https://your-app/user_id`).
    * `paths.email` — JSON path to the email (e.g. `email`, `https://your-app/email`).

    These are evaluated with lodash `_.get` — dotted paths and bracketed segments work the same.
  </Step>
</Steps>

Once configured, your frontend calls remain minimal — the widget reads the token from your existing IDP session.

## Frontend setup

Your call site doesn't reference `authType: "publiclySignedJwt"` (the SDK union doesn't include it). Instead, you pass a `jwtToken` field through your IDP integration; the widget submits it to `/v1/users/auth` and Notifizz applies the configured strategy.

The minimal pattern with each provider:

<Tabs>
  <Tab title="Auth0">
    ```javascript theme={null}
    import { useAuth0 } from "@auth0/auth0-react";

    const { getAccessTokenSilently } = useAuth0();
    const token = await getAccessTokenSilently();

    // Pass `token` to the widget. The strategy on the dashboard applies.
    ```
  </Tab>

  <Tab title="Clerk">
    ```javascript theme={null}
    import { useAuth } from "@clerk/clerk-react";

    const { getToken } = useAuth();
    const token = await getToken();
    ```
  </Tab>

  <Tab title="Cognito">
    ```javascript theme={null}
    import { fetchAuthSession } from "aws-amplify/auth";

    const session = await fetchAuthSession();
    const token = session.tokens?.idToken?.toString();
    ```
  </Tab>

  <Tab title="Supabase">
    ```javascript theme={null}
    import { createClient } from "@supabase/supabase-js";

    const supabase = createClient(URL, KEY);
    const { data: { session } } = await supabase.auth.getSession();
    const token = session?.access_token;
    ```
  </Tab>
</Tabs>

The token then flows into the widget exactly as for the other modes — pass it as `token` in your widget options. Behind the scenes the widget submits a `PublicJwtSignedParams` payload to `/v1/users/auth` and the configured `RsaJwtAuthenticationStrategy` handles verification.

## How verification works

```mermaid theme={null}
sequenceDiagram
    participant App as Your app
    participant IDP as Your IDP
    participant Notifizz
    participant JWKS

    App->>IDP: User signs in
    IDP->>App: JWT (signed with IDP private key)
    App->>Notifizz: POST /v1/users/auth { jwtToken, ... }
    Notifizz->>JWKS: GET <jwks_url> (cached)
    JWKS-->>Notifizz: public keys
    Notifizz-->>Notifizz: verify(jwt, key, { iss, aud })
    Notifizz-->>Notifizz: extract id, email via configured paths
    Notifizz->>App: Firebase custom token (widget session)
```

The verifier does:

1. Resolve the public key from the JWKS URL using the JWT's `kid` header.
2. `jsonwebtoken.verify(token, key, { issuer, audience })` — rejects on signature mismatch, wrong issuer, or wrong audience.
3. Extract `id` and `email` from the verified payload via the configured paths.
4. Mint a Firebase custom token for the widget session, embedding `{ user, apiKey, organisation }` claims.

If the environment doesn't have a public key configured, the request fails with `auth/no-public-key-registered` (`401`). If the token verifies but `id`/`email` extraction fails, the request fails with `auth/invalid-token-signature`.

## Required JWT claims

The token must contain — at the paths you configure in the dashboard:

| Claim                      | Used as                           | Notes                                                          |
| -------------------------- | --------------------------------- | -------------------------------------------------------------- |
| (configured `paths.id`)    | `userId`                          | String. Stable per user. Often `sub`.                          |
| (configured `paths.email`) | `userEmail`                       | String. Doesn't have to match anything else; used for display. |
| `iss`                      | matched against expected issuer   | Must equal the value configured in the dashboard.              |
| `aud`                      | matched against expected audience | Must equal the value configured in the dashboard.              |
| `exp`                      | standard expiry                   | Token rejected if expired.                                     |

Custom claims beyond these are ignored by Notifizz — but you can forward them via your event properties if a campaign needs them.

## FAQ

<AccordionGroup>
  <Accordion title="Auth0 — which claims should I configure?">
    By default, Auth0 puts the user id in `sub` and the email in `email` (when the `email` scope is requested). Configure `paths.id = "sub"` and `paths.email = "email"`. If you use Auth0's "namespaced custom claims" pattern, configure the namespaced paths instead (e.g. `paths.email = "https://your-app.com/email"`).
  </Accordion>

  <Accordion title="Clerk — same?">
    Clerk puts the user id in `sub`. Email needs to be added via a Clerk session token template — Clerk doesn't include it by default. Either add `email` to the template, or configure `paths.email` to point at the Clerk metadata path you use.
  </Accordion>

  <Accordion title="Why is there no SDK shorthand for this mode?">
    Because the strategy is decided per-environment and changing it shouldn't require a frontend deploy. The dashboard owns the JWKS URL, expected issuer, audience, and claim paths — your code just passes the token. This keeps the SDK surface to three options and pushes config to operators.
  </Accordion>

  <Accordion title="Why do I see `CustomRsaJwt` in dashboard logs?">
    Backend internal name for the same strategy. Customer-facing prose always uses `PubliclySignedJwt`; logs and a few dashboard internals haven't been renamed. Treat them as identical.
  </Accordion>

  <Accordion title="Verification fails with `kid not found`.">
    The JWT's `kid` header doesn't match any key returned by your JWKS URL. Three causes: (1) the JWKS URL is wrong; (2) your IDP rotated keys and the cached keys are stale (Notifizz refetches the JWKS automatically; wait a minute); (3) the JWT was issued in a different IDP environment than the one whose JWKS you configured.
  </Accordion>

  <Accordion title="My token verifies but the widget says `auth/invalid-token-signature`.">
    The verifier ran but the `paths.id` or `paths.email` lookups returned non-strings. Inspect the JWT payload — the path you configured doesn't resolve to a string. Use a JWT debugger to see exact paths, then update the dashboard config.
  </Accordion>

  <Accordion title="Can I rotate my IDP keys without downtime?">
    Yes — that's a strength of `PubliclySignedJwt`. JWKS endpoints typically expose multiple keys during rotation; tokens signed with the old key keep verifying until they expire, new tokens use the new key. Notifizz follows the JWKS — no manual coordination needed.
  </Accordion>
</AccordionGroup>

## See also

<CardGroup cols={2}>
  <Card title="Authentication overview" icon="key" href="/docs/sdks/authentication/overview">
    The four-mode model and when to pick each.
  </Card>

  <Card title="Backend tokens" icon="lock" href="/docs/sdks/authentication/backend-tokens">
    Mint a server-side HMAC token instead of a JWT.
  </Card>

  <Card title="Firebase auth" icon="fire" href="/docs/sdks/authentication/firebase">
    Skip the dashboard configuration — Firebase Auth has built-in support.
  </Card>

  <Card title="Notification Center overview" icon="bell" href="/docs/sdks/notification-center/overview">
    Lifecycle, state model, custom bell, headless mode.
  </Card>
</CardGroup>
