# Cognito setup

The service verifies OIDC JWTs from your existing Cognito user pool(s). One-time setup per pool.

You will need these values later:

Value Where it comes from Used for
AUTH_ISSUER Pool id + region Worker config
Cognito domain App integration → Domain Token URL host
Client ID App client detail page $CLIENT_ID in the token curl
Client secret Shown once when the app client is created (or “View” / rotate later) $CLIENT_SECRET in the token curl

# Publisher settings at a glance (PUBLISHER_SCOPES vs PUBLISHER_GROUPS)

These two Worker env vars control who is allowed to call POST /v1/notifications (publish to any user). They are not required for reading an inbox or for POST /v1/notifications/self.

Setting What it is Do I need it? What you create in Cognito
PUBLISHER_SCOPES=notifications/publish Allowlist of OAuth scopes on the access token Yes — this is the normal path for backends / jobs (M2M) A resource server + custom scope, then an app client allowed to request that scope. No Cognito group.
PUBLISHER_GROUPS=notification-publishers Allowlist of Cognito group names on a user token (cognito:groups) Optional. Only if humans/admins should publish with their interactive login A Cognito user group with that exact name, and users added to it

Rules of thumb:

  1. Most setups only need PUBLISHER_SCOPES. Machine publishers (Assess, cron jobs, etc.) use client-credentials tokens that carry scopes, not groups — so groups would never help them.
  2. You do not need to create the notification-publishers group unless you deliberately want human publishers. If you are not using groups, omit PUBLISHER_GROUPS entirely (or leave it unset).
  3. Regular end users need neither. Any valid user token can list / mark-read / delete their own inbox, and can self-notify via /self. They must not be given the publish scope just to “make the SPA work.”
  4. Publishing is allowed if the token matches either list (scope or group). At least one of the two env vars must be set and match the token, or every cross-user publish returns 403.
# Typical production / local .dev.vars — scopes only:
PUBLISHER_SCOPES=notifications/publish

# Only add this if you also created the Cognito group and put admin users in it:
# PUBLISHER_GROUPS=notification-publishers

The strings must match exactly what Cognito puts on the token (notifications/publish in scope, or notification-publishers in cognito:groups). You can comma-separate multiple values if needed.


# 1. Point the service at the pool

In AWS Console → Cognito → User pools → (your pool):

  • Region is in the URL / top-right (e.g. eu-west-2)
  • User pool ID is on the pool overview (e.g. eu-west-2_AbCdEf123)
AUTH_ISSUER=https://cognito-idp.<region>.amazonaws.com/<userPoolId>
# example:
# AUTH_ISSUER=https://cognito-idp.eu-west-2.amazonaws.com/eu-west-2_AbCdEf123

Multiple pools hitting one deployment: comma-separate the issuers. JWKS URLs are derived automatically; keys are cached and rotation is handled (refetch on unknown kid).

Optionally pin accepted clients: AUTH_AUDIENCE=<appClientId1>,<appClientId2> (matches aud on id tokens and client_id on access tokens).


# 2. Users (inbox access)

Nothing to configure. Any valid token from an accepted issuer can read/manage its own inbox — the sub claim is the inbox key. Producers must address notifications to that same sub.


# 3. Server-to-server publishers (the normal case)

Cognito client-credentials tokens carry scopes, not groups, so publishers are authorized by scope.

# 3.1 Create a resource server + scope

  1. Open the user pool → App integrationResource serversCreate resource server.
  2. Set:
    • Resource server identifier: notifications (exact string)
    • Resource server name: anything readable, e.g. Notifications
  3. Under Custom scopes, add:
    • Scope name: publish
    • Description: e.g. Publish notifications
  4. Save.

The full scope string apps request is:

notifications/publish

(identifier + / + scope name)

# 3.2 Create an app client (this is where Client ID + Client secret come from)

Do this once per producer app (e.g. one client for Assess, one for another service).

  1. Same user pool → App integrationApp clientsCreate app client.
  2. App type: choose Confidential client (machine-to-machine).
    Public / SPA clients do not get a client secret and cannot use client-credentials.
  3. App client name: e.g. notifications-totum-assess.
  4. Client secret: ensure Generate a client secret is enabled (default for confidential clients).
  5. Authentication flows: enable Client credentials only (uncheck authorization-code / implicit if you do not need them for this client).
  6. Under Allowed OAuth scopes / custom scopes, select notifications/publish (the resource-server scope from 3.1).
  7. Create the client.

On the next screen (or App client → your client):

Field Where
Client ID Shown as Client ID / Client id on the app client detail page. Copy it → this is $CLIENT_ID.
Client secret Shown as Client secret. Cognito often shows it only once at creation — copy it immediately into a password manager / secrets store. That is $CLIENT_SECRET. If you lost it: open the app client → Actions → regenerate / rotate client secret (invalidates the old one).

The Client ID also becomes the notification source on every publish from this client (visible to consumers; used in dedupe). One app client per producer keeps sources meaningful.

# 3.3 Find your Cognito domain (for the token URL)

Still under App integrationDomain:

  • Either a Cognito domain prefix: https://<prefix>.auth.<region>.amazoncognito.com
  • Or a custom domain you configured

That host is what goes in the token curl (not the issuer URL).

Example token endpoint:

https://mycompany-dev.auth.eu-west-2.amazoncognito.com/oauth2/token

# 3.4 Fetch an M2M access token

Service config must include:

PUBLISHER_SCOPES=notifications/publish

PowerShell (Windows) — use curl.exe so PowerShell does not alias curl to Invoke-WebRequest:

$ClientId = "xxxxxxxxxxxxxxxxxxxxxxxxxx"          # from app client detail
$ClientSecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" # from app client secret
$CognitoDomain = "https://<prefix>.auth.<region>.amazoncognito.com"

curl.exe -X POST "$CognitoDomain/oauth2/token" `
  -H "Content-Type: application/x-www-form-urlencoded" `
  -u "${ClientId}:${ClientSecret}" `
  -d "grant_type=client_credentials&scope=notifications/publish"

macOS / Linux (bash):

export CLIENT_ID="xxxxxxxxxxxxxxxxxxxxxxxxxx"
export CLIENT_SECRET="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
export COGNITO_DOMAIN="https://<prefix>.auth.<region>.amazoncognito.com"

curl -X POST "$COGNITO_DOMAIN/oauth2/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d "grant_type=client_credentials&scope=notifications/publish"

-u "id:secret" is HTTP Basic auth (curl base64-encodes it for you).

Response includes access_token and expires_in. Cache the token until it expires; do not fetch one per notification.

Decode the access token at jwt.io and confirm:

  • client_id equals your Client ID
  • scope contains notifications/publish
  • iss equals your AUTH_ISSUER

# 4. Human/admin publishers (optional — Cognito groups)

Skip this entire section unless a person (not a backend) needs to publish notifications while signed in with a normal user token.

M2M app clients cannot use groups — their tokens have scopes only. For backends, §3 + PUBLISHER_SCOPES is enough.

If you do want human publishers:

  1. Cognito → user pool → GroupsCreate group.
  2. Group name: e.g. notification-publishers (remember the exact spelling).
  3. Add the admin users to that group.
  4. On the Worker set:
PUBLISHER_GROUPS=notification-publishers

(Keep PUBLISHER_SCOPES=notifications/publish as well if you still have M2M publishers.)

Their user tokens (which carry cognito:groups) can then publish; source falls back to their sub.

Regular end users need no group — they only read their own inbox (and may use /self).


# Non-Cognito IdPs

The defaults are Cognito-shaped but overridable: AUTH_SUB_CLAIM (default sub), AUTH_GROUPS_CLAIM (default cognito:groups), AUTH_JWKS_URL (default {issuer}/.well-known/jwks.json). Any OIDC issuer that signs RS256/ES256 JWTs works.