This string has no description

28 kB · 392 lines · Created 1mo ago
Markdown

How an entryway works with a PDS #

Applies to @atproto/pds 0.5.36 at 415d0c5
Last reviewed: 2026-10-01

1. Big picture #

This document explains how to build an account entryway that works with the public @atproto/pds package in entryway mode. It starts with the roles of the two services, then covers the configuration, authentication, XRPC calls, and multi-step workflows needed for them to interoperate.

The core relationship is between an OAuth authorization server and resource server:

  • The entryway is the authorization server. It authenticates people, handles grants, and issues tokens.
  • The PDS is the resource server. It verifies those tokens and serves the user’s repository, records, and blobs.

The same split extends beyond OAuth. The entryway handles the account side: it stores credentials, assigns accounts to PDSs, manages service handles and PLC identity changes, and runs account lifecycle flows. The PDS handles the data side: it stores repositories and blobs, holds repository signing keys, and emits repository and account events.

The services call each other when a workflow crosses that boundary. The PDS delegates account operations to the entryway. The entryway calls back to the assigned PDS when a handle, status, or account deletion changes the PDS’s local state.

“Entryway” is a term used by the reference PDS implementation. It is not a separate role defined by the AT Protocol specification. The interface described here depends on the stated PDS version. Pin and test a compatible @atproto/pds version.

2. Who owns what #

Concern Owner
Email, account password, app passwords, invitations, email tokens Entryway
Legacy access and refresh sessions Entryway
OAuth authorization server, grants, and tokens Entryway
Account-to-PDS assignment Entryway
Service-handle allocation and resolution Entryway
PLC rotation key and identity-update authorization Entryway
Repository signing private key Assigned PDS
Repository, records, blobs, and repository writes Assigned PDS
Repository event sequencing and account status events Assigned PDS
Account DID, handle, and lifecycle status Entryway stores the primary state; PDS keeps the local copy needed to serve the account

Clients send repository and data requests directly to the PDS named by the user’s DID document. OAuth authorization and login happen at the entryway. OAuth-protected resource requests go to the PDS. Legacy clients may send account-management requests to either service; when those requests reach the PDS, it delegates the methods listed later in this document.

flowchart LR
    C[AT Protocol client]
    E[Entryway<br/>authorization server<br/>accounts and identity]
    P[PDS<br/>resource server<br/>repository and blobs]
    D[PLC directory / DID resolution]

    C -->|signup, login, OAuth grants| E
    C -->|token-authenticated data XRPC| P
    P -->|delegated account XRPC| E
    E -->|provisioning and admin callbacks| P
    E -->|PLC operations| D
    P -->|DID validation and submission| D

3. Configure the two services #

3.1 PDS settings #

The PDS enables entryway mode when PDS_ENTRYWAY_URL is set. In that mode all four of the following public settings are required:

PDS environment variable Required value
PDS_ENTRYWAY_URL The entryway’s externally meaningful base URL and OAuth issuer URL
PDS_ENTRYWAY_DID The service DID used as the audience of PDS-to-entryway service-auth JWTs and entryway refresh JWTs
PDS_ENTRYWAY_JWT_VERIFY_KEY_K256_PUBLIC_KEY_HEX Compressed or uncompressed secp256k1 public key, as hex, for entryway-issued legacy and OAuth JWTs
PDS_ENTRYWAY_PLC_ROTATION_KEY The entryway PLC rotation public key as a did:key value

These existing PDS settings also need to agree with the entryway deployment:

PDS setting Pairing requirement
PDS_SERVICE_DID Unique DID of this PDS; legacy access tokens use it as aud
PDS_HOSTNAME (and PDS_PORT for localhost) Determines the exact origin written to users’ DID documents as the atproto_pds service endpoint
PDS_SERVICE_HANDLE_DOMAINS Same service domains managed and resolved by the entryway
PDS_DID_PLC_URL Same PLC directory against which the entryway constructs and updates identities
PDS_ADMIN_PASSWORD Credential used by the entryway for the admin callbacks in section 7

Use HTTPS for public URLs outside local development. URL equality checks are exact: a different scheme, hostname, port, or path can break account creation or OAuth metadata.

The public PDS source for these settings is src/config/env.ts and src/config/config.ts.

3.2 Entryway settings #

The entryway needs the following configuration, regardless of how its settings are named or stored:

  • Its public base URL and service DID. These need to match PDS_ENTRYWAY_URL and PDS_ENTRYWAY_DID on every attached PDS.
  • The secp256k1 private key corresponding to PDS_ENTRYWAY_JWT_VERIFY_KEY_K256_PUBLIC_KEY_HEX.
  • The PLC rotation private key corresponding to PDS_ENTRYWAY_PLC_ROTATION_KEY.
  • A registry of attached PDSs containing each PDS’s public URL, service DID, and callback credential.
  • The service-handle domains and PLC directory shared with the PDS fleet.
  • Durable stores for accounts, credentials, refresh-token state, OAuth state, invitations, and one-time email tokens.

When more than one PDS is attached, the entryway records the assigned PDS for each account. Tokens, callbacks, DID documents, and later operations all use that assignment until the account is fully migrated.

4. Identity and key model #

Four identities or keys have distinct roles:

  1. Entryway service DID. This is the audience of PDS-to-entryway service-auth JWTs and of refresh JWTs.
  2. PDS service DID. This is the audience of legacy access JWTs and OAuth access tokens for that PDS.
  3. Entryway JWT signing key. This secp256k1 key signs legacy sessions and OAuth tokens. The PDS is configured directly with its public key.
  4. User repository signing key. The PDS generates and retains this key. It appears as the user DID document’s #atproto verification method and signs user-scoped service-auth requests sent from the PDS to the entryway.

For a did:plc account, the DID document data contains:

  • an atproto_pds service of type AtprotoPersonalDataServer whose endpoint exactly equals the assigned PDS public URL;
  • an atproto verification method matching the signing key reserved on that PDS;
  • the configured entryway PLC rotation key among its rotation keys; and
  • alsoKnownAs: ["at://<handle>"] for the current handle.

An operator-controlled recovery key can precede the entryway rotation key. Store the entryway PLC rotation private key durably and protect it with a recovery plan. Losing or unintentionally replacing it can make identity administration impossible.

5. How authentication works #

Four kinds of authentication connect the two services:

  • The entryway issues legacy access and refresh JWTs. The PDS verifies access JWTs as the resource server and forwards refresh JWTs to the entryway.
  • The entryway issues OAuth tokens. The PDS verifies them as an OAuth protected resource.
  • The PDS uses user-scoped service-auth JWTs when it delegates an authenticated account operation to the entryway.
  • The entryway uses Basic admin auth for callbacks that change PDS state.

5.1 Legacy access JWT #

The entryway issues a legacy access JWT with these properties:

Field Value
JWS algorithm ES256K
Protected typ at+jwt
sub User DID
aud Assigned PDS service DID
scope One of the PDS legacy scopes, normally com.atproto.access or an app-password scope
Time claims Valid iat and exp

The PDS accepts the legacy scopes declared by its public auth-scope.ts. An access token uses the service DID of the account’s assigned PDS as its audience. When that token is presented directly to the entryway, the entryway accepts the assigned PDS DID as the audience; it does not require the entryway DID as the access-token audience.

5.2 Legacy refresh JWT #

The entryway issues a refresh JWT with these properties:

Field Value
JWS algorithm ES256K
Protected typ refresh+jwt
sub User DID
aud Entryway service DID, not the PDS DID
scope com.atproto.refresh
jti Unique refresh-token identifier
Time claims Valid iat and exp

The entryway keeps enough refresh-token state to rotate and revoke the token identified by jti. When a refresh is sent to the PDS, the PDS first verifies the JWT signature, claims, and local account status. It then forwards the same credential to the entryway for rotation.

5.3 PDS-to-entryway service auth #

For account-authorized delegated methods, the PDS replaces the client credential with a short-lived AT Protocol service-auth JWT:

  • iss is the user DID;
  • aud is the configured entryway service DID;
  • lxm is the exact XRPC NSID being called;
  • the JWT is signed with the user repository signing key held by the PDS; and
  • the request uses Authorization: Bearer <jwt>.

The entryway resolves the issuer DID, obtains its #atproto verification method, verifies the signature, checks that aud is its own DID, and checks that lxm matches the requested NSID. It uses the verified iss as the account identity. A DID supplied only in the request body is not trusted.

5.4 Entryway-to-PDS admin auth #

The callbacks in section 7 use HTTP Basic authentication:

Authorization: Basic base64("admin:<PDS_ADMIN_PASSWORD>")

Each callback goes to the PDS assigned to the target account. Use a different high-entropy credential per PDS, restrict callback network access where practical, and use TLS outside local development.

5.5 OAuth and DPoP #

In entryway mode, the PDS is an OAuth protected resource and the entryway is the authorization server:

  • GET /.well-known/oauth-protected-resource on the PDS names the entryway URL in authorization_servers and the PDS URL as the resource.
  • The entryway publishes the normal authorization-server metadata and OAuth endpoints at its issuer.
  • OAuth access tokens are signed by the configured entryway secp256k1 key, identify the user with sub, and use the PDS service DID as audience.
  • A client’s DPoP proof is bound to the public PDS request URL. The PDS verifies it locally; DPoP credentials are not transparently forwarded.

The PDS also supports OAuth tokens whose scope is stored by reference. If an authorization server puts ref:<CID> in the token instead of the full scope string, the PDS calls com.atproto.temp.dereferenceScope on the entryway and caches the returned scope. An entryway that issues scope references needs to implement this query and return {"scope":"<full scope string>"}. An entryway that always puts the full scope string in the token does not need the query. The method currently lives in the temporary namespace, so treat it as a version-specific compatibility interface rather than a stable AT Protocol API.

6. PDS-to-entryway delegation #

The PDS uses two forwarding modes:

  • Service auth: the PDS validates the client, replaces the client credential with a user-scoped service-auth JWT, and calls the entryway.
  • Credential passthrough: the PDS sends the original authorization header to the entryway so it can validate or update that credential itself.

6.1 Service-auth delegation #

The PDS validates the client locally, then calls the entryway with the user-scoped service-auth JWT from section 5.3. Request input and query parameters retain their Lexicon-defined shape unless noted.

XRPC method Entryway responsibility
com.atproto.admin.sendEmail Resolve the recipient’s email and send the moderation message
com.atproto.identity.requestPlcOperationSignature Issue and deliver the identity-operation confirmation token
com.atproto.identity.signPlcOperation Validate confirmation and return a PLC operation signed by the entryway rotation key
com.atproto.identity.updateHandle Update the stored handle and PLC document, then call back to the PDS; the PDS currently includes an extra did body property outside the Lexicon
com.atproto.server.deactivateAccount Deactivate the account, then update the PDS account status through the callback described in section 7.3
com.atproto.server.confirmEmail Confirm the account email
com.atproto.server.createAppPassword Create and return an app password
com.atproto.server.getAccountInviteCodes Return and optionally allocate account invite codes
com.atproto.server.getSession Return current account, email, and status data; do not include session JWTs
com.atproto.server.listAppPasswords List app-password descriptors
com.atproto.server.requestAccountDelete Create and send an account-deletion token
com.atproto.server.requestEmailConfirmation Create and send an email-confirmation token
com.atproto.server.requestEmailUpdate Start email update and return tokenRequired
com.atproto.server.revokeAppPassword Revoke the named app password and related sessions
com.atproto.server.updateEmail Validate required factors and update the account email

The PDS filters email fields from getSession when the original OAuth grant does not authorize reading them, even though the entryway returns the full account response.

6.2 Credential passthrough #

For the following methods, the PDS forwards the original Authorization header when one exists. The entryway performs the credential or token action.

XRPC method Notes
com.atproto.server.createSession No incoming auth; entryway validates identifier, password, and optional auth factor and returns its JWTs
com.atproto.server.refreshSession Original refresh JWT is forwarded after PDS validation
com.atproto.server.deleteSession Original refresh JWT is forwarded, including when expired
com.atproto.server.activateAccount Legacy access JWT is forwarded; entryway performs the callback in section 7.3
com.atproto.server.deleteAccount No auth header is required by the Lexicon flow; entryway validates DID, password, and deletion token, then performs the callback in section 7.4
com.atproto.server.requestPasswordReset Entryway handles requests for accounts whose email is not stored on the PDS
com.atproto.server.resetPassword Entryway validates the reset token and stores the new password

DPoP credentials cannot use transparent passthrough. The PDS rejects attempts to proxy an Authorization: DPoP header or a request carrying a DPoP header.

The admin methods com.atproto.admin.updateAccountEmail and com.atproto.admin.updateAccountPassword also pass the original Basic admin credential through to the entryway after the PDS validates it. If these methods are exposed, the two services need compatible administrative authentication.

6.3 Forwarded client address #

Both forwarding modes add X-Forwarded-For containing the client address as observed by the PDS. Trust this value for abuse controls and logging only when the immediate caller is a known PDS. Discard forwarding chains from untrusted callers. Configure the PDS to trust only its real front proxies so that the address it observes is reliable.

6.4 Methods that stay on the PDS or are disabled #

  • Repository, blob, sync, and ordinary data methods remain on the PDS.
  • com.atproto.server.getServiceAuth remains on the PDS because it is backed by the user repository signing key.
  • com.atproto.identity.getRecommendedDidCredentials and com.atproto.identity.submitPlcOperation remain on the PDS. In entryway mode the recommended credentials contain the entryway rotation key. Submitted PLC operations need to retain that key and the assigned PDS endpoint.
  • com.atproto.identity.resolveHandle is not directly forwarded. In entryway mode the PDS skips its local account table and uses an AppView or normal handle resolution.
  • The PDS does not answer /.well-known/atproto-did for service handles in entryway mode. The entryway serves those handles through HTTPS well-known resolution, DNS records, or both.
  • Administrative invite creation, listing, enabling, and disabling on the PDS are rejected because invites belong to the entryway.
  • com.atproto.server.describeServer remains local to each service. Configure service domains, invite requirements, and policy links consistently so clients do not receive contradictory descriptions.

7. Entryway-to-PDS workflows #

7.1 Account creation #

Clients create accounts at the entryway. Direct PDS account creation in entryway mode requires a fully formed entryway-authorized identity operation; it is not the public signup API.

sequenceDiagram
    participant C as Client
    participant E as Entryway
    participant P as Assigned PDS
    participant PLC as PLC directory

    C->>E: com.atproto.server.createAccount
    E->>P: com.atproto.server.reserveSigningKey
    P-->>E: signingKey (did:key)
    E->>E: choose handle and PDS, create signed PLC genesis op
    E->>P: com.atproto.server.createAccount {did, handle, plcOp}
    P->>P: validate op, create empty repo and local account
    P->>PLC: submit genesis operation
    P-->>E: account created
    E->>E: issue entryway access and refresh JWTs
    E-->>C: DID, handle, DID document, session JWTs

com.atproto.server.reserveSigningKey is unauthenticated in the referenced PDS version. It accepts an optional DID and returns a signingKey DID. Consider protecting it with network policy and rate limits appropriate to the deployment without changing its application-level response.

The entryway then constructs a valid PLC genesis operation containing the reserved signing key, assigned PDS endpoint, handle, and entryway rotation key. It calls the assigned PDS’s com.atproto.server.createAccount with at least:

{
  "did": "did:plc:...",
  "handle": "alice.example.com",
  "plcOp": {}
}

The actual plcOp is the signed PLC operation object. The PDS checks its signature against the configured entryway rotation key, checks all DID document contents, finds the reserved private key, creates the repository, submits the PLC operation, and stores its local account copy. It does not store the signup email, account password, or invite on this path.

The entryway returns its own session JWTs to the client. If the PDS call fails, the entryway removes or reconciles its provisional account data. A timed-out call may have succeeded, so check both the DID directory and the PDS before retrying account creation.

7.2 Handle update #

A handle update follows this sequence:

  1. authenticate the account, using either an entryway access token or the PDS service-auth JWT;
  2. validate and reserve the new handle;
  3. update the user’s PLC document with the entryway rotation key;
  4. wait until the DID document and handle resolve bidirectionally; and
  5. call com.atproto.admin.updateAccountHandle on the assigned PDS using the PDS admin credential.

The callback body is {"did":"did:...","handle":"new.example.com"}. The PDS re-resolves the DID, rejects a mismatch, updates its local handle, and sequences the identity event. The entryway updates the DID document; the PDS emits the repository event.

7.3 Activation and deactivation #

After accepting com.atproto.server.activateAccount or com.atproto.server.deactivateAccount, the entryway calls the assigned PDS’s com.atproto.admin.updateSubjectStatus with a repository subject:

{
  "subject": {
    "$type": "com.atproto.admin.defs#repoRef",
    "did": "did:plc:..."
  },
  "deactivated": { "applied": true }
}

Use applied: false for activation. The entryway also updates its primary account status and applies its refresh-token revocation policy. Make these boolean state transitions safe to repeat.

Moderation systems may similarly enter through the entryway’s com.atproto.admin.updateSubjectStatus. The entryway sends the change to the assigned PDS and mirrors account-level takedown or deactivation state in its own authorization decisions.

7.4 Account deletion #

After validating the password and deletion token, the entryway calls com.atproto.admin.deleteAccount on the assigned PDS with {"did":"did:..."}. The PDS removes the local account, sequences deletion, and destroys repository storage. The entryway then removes its account and credential state.

Deletion spans two services and has no distributed transaction. Record durable progress, retry interrupted work, treat an already absent account as completed during reconciliation, and retain enough audit data to investigate partial failures.

8. Routing and networking #

  • The entryway and every PDS need stable, mutually reachable HTTPS origins.
  • A user DID document always names the assigned PDS, not the entryway, as its atproto_pds endpoint.
  • Service-handle wildcard DNS or HTTPS routing points at the entryway because the PDS intentionally disables local well-known responses in entryway mode.
  • The entryway URL is also an OAuth issuer. Reverse proxies preserve its externally visible scheme and host when generating metadata and redirects.
  • Accept PDS admin callbacks only over trusted network paths and with valid per-PDS Basic credentials.
  • Time synchronization is required on all hosts because access, refresh, service-auth, OAuth, and DPoP verification all depend on short-lived claims.

The PDS forwards XRPC request bodies and query parameters but does not promise to forward arbitrary client headers. Do not depend on headers other than the delegated authorization and client address described here.

9. Errors, retries, and consistency #

Return standard XRPC status codes and error bodies from the entryway. The PDS passes delegated success bodies and most upstream XRPC failures back to the client. Preserve named errors used by clients, such as InvalidToken, ExpiredToken, AccountTakedown, and AuthFactorTokenRequired, where the corresponding public method defines them.

Cross-service operations are synchronous but not transactional. Make these operations safe to retry:

  • token revocation by token ID;
  • activation and deactivation to a requested boolean state;
  • handle callbacks after the PLC document already contains the new handle;
  • password and email token consumption;
  • account deletion and cleanup; and
  • account creation cleanup after an ambiguous timeout.

The PDS retains a local status copy and checks it before many data operations. Complete the PDS callback before reporting a handle or lifecycle change as finished. Monitor for mismatches among the entryway account row, PDS account copy, and DID document, and provide a repair path.

10. Security notes #

  • Never send account passwords, admin credentials, refresh tokens, or email tokens over plaintext transport outside isolated local development.
  • Keep the entryway JWT private key separate from the PLC rotation key. They have different trust and recovery roles.
  • Verify service-auth signature, iss, aud, expiration, and lxm; checking only that a bearer token exists is insufficient.
  • Derive the account for a delegated request from the verified service-auth issuer, not from an extension field in the request body.
  • Issue access tokens only for the account’s assigned PDS audience and refresh tokens only for the entryway audience.
  • Treat X-Forwarded-For as trusted only from registered PDS peers.
  • Rate-limit public credential, reset, signup, and signing-key-reservation routes. Avoid account-existence leaks in password-reset behavior.
  • Protect PDS admin endpoints with high-entropy credentials and network policy.
  • Back up the account-to-PDS mapping, JWT signing material, PLC rotation material, refresh-token state, and account database.

The referenced PDS accepts one configured entryway JWT public key. JWT key rotation therefore requires a coordinated rollout; there is no configured multi-key overlap for legacy token verification in this profile. Plan for old sessions during rotation.

11. Compatibility checklist #

Before serving users, test at least the following against the exact deployed PDS version:

  1. The PDS starts with all four entryway settings and publishes protected resource metadata naming the entryway.
  2. Signup reserves a PDS signing key, creates a valid PLC DID, creates an empty repository, and returns entryway-issued tokens.
  3. The DID document’s PDS endpoint, signing key, handle, and rotation key all match the paired services.
  4. A legacy access JWT works on its assigned PDS and fails for another PDS audience.
  5. Refresh rotates at the entryway when submitted either directly or through the PDS; logout revokes the refresh token.
  6. OAuth discovery, authorization, DPoP access to the PDS, and revocation work end to end.
  7. getSession, email flows, app passwords, invitations, and PLC signing work when called through the PDS.
  8. A service-handle update changes the PLC document, resolves both ways, updates the PDS account copy, and emits the identity event.
  9. Activation, deactivation, takedown, and deletion update both services and recover from a deliberately interrupted callback.
  10. Service handles resolve at the entryway while the PDS well-known route returns 404 in entryway mode.
  11. Service-auth calls with the wrong audience, method, issuer, signature, or expiry fail; admin callbacks with a bad credential fail.
  12. Forwarded client addresses are accepted only from a trusted PDS peer.

12. Public implementation references #

The behavior in this profile can be audited entirely in the public PDS package:

Sign up or Sign in to add to the discussion.