Authentication & authorization¶
Jmaple separates the two concerns cleanly:
- Authentication — "who is calling?". Pluggable, multiple providers can run in a chain.
- Authorization — "what can they reach?". One unified accounts/grants
layer in the database; admin is just a grant on the special
__system__account.
Providers¶
Three are bundled:
| Provider | When to use |
|---|---|
bearer |
Opaque DB-backed tokens minted via jmaple token issue. Easiest for service-to-service and bootstrap. |
jwt |
Validate JWTs signed by an external IdP. Works with static keys, public keys, or JWKS endpoints. |
oidc |
Discovery-based OpenID Connect. Validates access tokens and optionally fetches userinfo. |
Chain them in .env:
The chain walks providers in order; the first one to return a Principal
wins. A Principal is the verified identity — issuer + subject + provider
name + verified claims.
Identity resolution¶
The IdentityResolver sits between the auth chain and authorization. Given a
Principal, it:
- Looks up
external_identities WHERE issuer = ? AND subject = ?. - If found, returns the linked jmaple
User. - If not found, consults the adapter's auto-provisioning policy:
off— refuse (returnsNone, surfaces as403)by-subject— create a new user with a personal accountby-verified-email— link to an existing user with the same email ifemail_verifiedis true; otherwise create new
One jmaple User can hold many ExternalIdentity rows. A user logged in via
workforce SSO can later authenticate via personal Google as long as the
verified emails match — they end up as the same User with two identities.
Bootstrap admins¶
Set JMAPLE_AUTH__ADMIN_SUBJECTS to a list of <issuer>|<subject> strings.
Anyone whose composite id matches gets auto-provisioned with the admin role
on the system account, even when auto_provision is otherwise off.
This is the only way to bootstrap the first admin from an external IdP without writing SQL.
Accounts and grants¶
A JMAP Account is "a collection of data" (RFC 8620 §2). Every capability's
data is scoped to an account. A User reaches an account through a Grant
that gives them a role (owner, read-write, read-only, or admin for the
system account).
The session resource's accounts map shows every account a principal can
reach. The primaryAccounts map says which account is "the main one" for
each capability — used by clients that want a single default.
Bearer-token rotation¶
Opaque tokens carry a non-secret prefix (first 12 chars of the secret) stored
cleartext in the tokens table. The provider filters by prefix before doing
the expensive Argon2 verify, making lookup O(few) instead of O(all tokens).
Legacy tokens issued before this feature have token_prefix = NULL and fall
back to the full-scan path. Run jmaple token rotate <id> to re-issue any
legacy token with a populated prefix.