Supermail
Authorization

Delegated portal login

Let a trusted application provision Supermail users and hand them short-lived, one-time links into the portal.

Delegated login lets your application hand one of its authenticated users into the Supermail portal without asking them to enter a Supermail password first. Your server proves its own identity with the application's existing client_id and client_secret, identifies the user by a stable app-local subject, and receives a short-lived confirmation URL.

This is a portal login capability, not an OAuth Resource API scope. Its durable source of truth is a separate per-user delegated grant carrying delegated_login; that grant is projected into the user's list of login methods.

Only use delegated login when your server has already authenticated the user. Never expose the client secret or call AppDelegatedLogin/* from browser code.

Before you start

  1. Register a confidential OIDC application and store its one-time client_secret.
  2. Ask the deployment administrator to enable Delegated login for that app in Admin → Apps. App ownership alone cannot enable this trust.
  3. Keep a stable, opaque external_subject for each of your users. It must be 1–255 bytes and must not contain @.

All AppDelegatedLogin/* JSON-RPC calls use HTTP Basic authentication:

Authorization: Basic base64(client_id:client_secret)

The current client secret and a secret still inside its rotation grace window both work.

Pick the right onboarding path

User stateStart withWhat happens
No Supermail account yetAppDelegatedLogin/provisionAtomically creates the user and your delegated grant
Already has a Supermail accountAppDelegatedLogin/create_link_ticketThe signed-in user explicitly confirms linking your app
Already provisioned or linked to your appAppDelegatedLogin/create_ticketCreates a one-time portal login URL

Do not use an email address or username as external_subject, and do not try to match it to an existing Supermail user. Generate or reuse an opaque identifier from your own identity system instead.

1. Provision a new user

AppDelegatedLogin/provision creates a Supermail user and its delegated grant in one transaction. It is idempotent on (client_id, external_subject), so retrying the same request returns the same user and grant with created: false.

Request
curl --user "$CLIENT_ID:$CLIENT_SECRET" \
  --header "Content-Type: application/json" \
  --data '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "AppDelegatedLogin/provision",
    "params": {
      "external_subject": "usr_7f3d9b1c",
      "username": "ada"
    }
  }' \
  https://supermail.example.com/rpc
Response (result)
{
  "user": {
    "id": "user_01H...",
    "username": "ada",
    "allow_app_create": false,
    "created": "2026-07-19T08:14:03+00:00"
  },
  "grant": {
    "id": "dgrant_01H...",
    "app_id": "app_01H...",
    "user_id": "user_01H...",
    "external_subject": "usr_7f3d9b1c",
    "origin": "provisioned",
    "scopes": ["delegated_login"],
    "created": "2026-07-19T08:14:03+00:00"
  },
  "created": true
}

The username is used only when the user is first created. It must be an available Supermail username; an idempotent replay resolves by external_subject, not by username.

2. Create a login ticket

Once your app has a live delegated grant for the subject, request a login URL:

Request
curl --user "$CLIENT_ID:$CLIENT_SECRET" \
  --header "Content-Type: application/json" \
  --data '{
    "jsonrpc": "2.0",
    "id": 2,
    "method": "AppDelegatedLogin/create_ticket",
    "params": {
      "external_subject": "usr_7f3d9b1c",
      "return_to": "/accounts"
    }
  }' \
  https://supermail.example.com/rpc
Response (result)
{
  "login_url": "https://supermail.example.com/delegated-login/start?ticket=...",
  "expires": "2026-07-19T08:16:03+00:00"
}

Redirect the user's browser to login_url. Supermail shows the application name and requires an explicit confirmation before it signs the user in. A newly provisioned user is then sent through a one-time security setup to add a password before the rest of the portal is available. That password is their independent backup if your application later disappears.

return_to is optional and defaults to /accounts. It must be a local absolute portal path beginning with one /; external URLs, protocol-relative paths, and backslashes are rejected.

If the person already has a Supermail account, create a link ticket instead of provisioning a duplicate:

Request
curl --user "$CLIENT_ID:$CLIENT_SECRET" \
  --header "Content-Type: application/json" \
  --data '{
    "jsonrpc": "2.0",
    "id": 3,
    "method": "AppDelegatedLogin/create_link_ticket",
    "params": {
      "external_subject": "usr_7f3d9b1c",
      "return_to": "/profile"
    }
  }' \
  https://supermail.example.com/rpc

The response contains link_url and expires. Send the browser there. The user must sign into their existing Supermail account and explicitly confirm the link; only then does Supermail create the delegated grant. Future AppDelegatedLogin/create_ticket calls for that subject sign into the linked account.

The link call returns a conflict if that external subject already has a grant for your application.

Ticket security and lifetime

  • Login and link URLs expire after about two minutes and can be confirmed only once.
  • Opening the URL with GET does not consume it. Supermail moves the token into an HttpOnly, path-scoped cookie and removes it from the visible URL; the confirmation POST consumes it. This keeps mail and chat link scanners from burning a ticket.
  • Ticket values are random bearer secrets and are stored only as SHA-256 hashes. Treat a returned URL like a password: do not log it, persist it, or send it through analytics.
  • Ticket creation is limited to 60 tickets per rolling minute per app.
  • Disabling delegated login, revoking the user's delegated method, or deleting the app invalidates outstanding access.

Grants, removal, and app deletion

The user sees the delegated grant as a login method named after your app. They may remove it from their profile once they have an independent password; removal revokes the grant and burns its outstanding tickets.

Deleting an app requires an explicit acknowledgement when delegated grants would be revoked. Supermail refuses deletion while any user still depends on that app as their only usable login path. Those users must first add a password or another live delegated path.

Delegated login establishes a portal session only. To call the Resource API, your app still uses the normal authorization-code + PKCE flow and obtains a user-approved OAuth grant.

On this page