This string has no description
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_URLandPDS_ENTRYWAY_DIDon 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:
- Entryway service DID. This is the audience of PDS-to-entryway service-auth JWTs and of refresh JWTs.
- PDS service DID. This is the audience of legacy access JWTs and OAuth access tokens for that PDS.
- Entryway JWT signing key. This secp256k1 key signs legacy sessions and OAuth tokens. The PDS is configured directly with its public key.
- User repository signing key. The PDS generates and retains this key. It appears as the user DID document’s
#atprotoverification 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_pdsservice of typeAtprotoPersonalDataServerwhose endpoint exactly equals the assigned PDS public URL; - an
atprotoverification 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:
issis the user DID;audis the configured entryway service DID;lxmis 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-resourceon the PDS names the entryway URL inauthorization_serversand 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.getServiceAuthremains on the PDS because it is backed by the user repository signing key.com.atproto.identity.getRecommendedDidCredentialsandcom.atproto.identity.submitPlcOperationremain 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.resolveHandleis 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-didfor 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.describeServerremains 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:
- authenticate the account, using either an entryway access token or the PDS service-auth JWT;
- validate and reserve the new handle;
- update the user’s PLC document with the entryway rotation key;
- wait until the DID document and handle resolve bidirectionally; and
- call
com.atproto.admin.updateAccountHandleon 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_pdsendpoint. - 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, andlxm; 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-Foras 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:
- The PDS starts with all four entryway settings and publishes protected resource metadata naming the entryway.
- Signup reserves a PDS signing key, creates a valid PLC DID, creates an empty repository, and returns entryway-issued tokens.
- The DID document’s PDS endpoint, signing key, handle, and rotation key all match the paired services.
- A legacy access JWT works on its assigned PDS and fails for another PDS audience.
- Refresh rotates at the entryway when submitted either directly or through the PDS; logout revokes the refresh token.
- OAuth discovery, authorization, DPoP access to the PDS, and revocation work end to end.
getSession, email flows, app passwords, invitations, and PLC signing work when called through the PDS.- A service-handle update changes the PLC document, resolves both ways, updates the PDS account copy, and emits the identity event.
- Activation, deactivation, takedown, and deletion update both services and recover from a deliberately interrupted callback.
- Service handles resolve at the entryway while the PDS well-known route returns 404 in entryway mode.
- Service-auth calls with the wrong audience, method, issuer, signature, or expiry fail; admin callbacks with a bad credential fail.
- 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:
- Entryway configuration
- Client construction, JWT verification setup, and service-auth generation
- Legacy, OAuth, admin, and service-auth verification
- OAuth scope-reference resolution
- Account server routes
- Identity routes
- Admin callbacks
- OAuth protected-resource metadata
- Public entryway-mode integration test and mock