{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":[]},"type":"markdown"},"seo":{"title":"PERS Authentication Guide"},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"pers-authentication-guide","__idx":0},"children":["PERS Authentication Guide"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["This guide covers authentication and authorization patterns in the PERS API."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"architecture-overview","__idx":1},"children":["Architecture Overview"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["PERS uses a tiered security architecture designed for flexibility and security:"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Tenant Context (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["X-Project-Key"]},")"]},": Identifies the project/tenant. Required for almost all requests (Public & Authenticated)."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Authentication (JWT)"]},": Identifies the principal (User, Business, or Admin).",{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Users"]},": Authenticated via JWTs obtained through Token Exchange."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Businesses"]},": Authenticated via JWTs."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Admins"]},": Authenticated via Tenant-scoped JWTs."]}]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Token Binding (DPoP)"]},": Binds access tokens to a client-held private key (Optional but Highly Recommended)."]}]},{"$$mdtype":"Tag","name":"blockquote","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Note"]},": We have deprecated legacy ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["x-api-key"]}," usage for business logic authentication in favor of strict JWT-based flows. ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["X-Project-Key"]}," is solely for tenant context resolution."]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"1-project-identification-public-access","__idx":2},"children":["1. Project Identification (Public Access)"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["All API requests must identify the target tenant/project context using the Project Key."]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Header"]},": ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["X-Project-Key"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Purpose"]},": Resolves the tenant configuration, feature flags, and ensures data isolation."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Scope"]},": By itself, grants access to ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Public"]}," endpoints (e.g., browsing catalogs, basic availability checks)."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Format"]},": 64-character hex string. For testnet campaigns, prefix with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["pk_test_"]},"."]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"http","header":{"controls":{"copy":{}}},"source":"GET /campaigns\nX-Project-Key: a1b2c3d4e5f6789...\n","lang":"http"},"children":[]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"2-authentication-jwt","__idx":3},"children":["2. Authentication (JWT)"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Authenticated operations require a valid JSON Web Token (JWT) in the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Authorization"]}," header."]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Header"]},": ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Authorization: Bearer <token>"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Format"]},": Standard JWT (JSON Web Token)."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"unified-token-endpoint","__idx":4},"children":["Unified Token Endpoint"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Obtain PERS-native JWTs by exchanging external credentials at the unified auth endpoint."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Endpoint"]},": ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["POST /auth/token"]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"request-body--sessionauthrequestdto-","__idx":5},"children":["Request Body (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SessionAuthRequestDTO"]},")"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"align":"left","data-label":"Field"},"children":["Field"]},{"$$mdtype":"Tag","name":"th","attributes":{"align":"left","data-label":"Type"},"children":["Type"]},{"$$mdtype":"Tag","name":"th","attributes":{"align":"left","data-label":"Required"},"children":["Required"]},{"$$mdtype":"Tag","name":"th","attributes":{"align":"left","data-label":"Description"},"children":["Description"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["authToken"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["string"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Yes"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["The proof from the external provider (e.g., Firebase ID Token, DFNS Auth Token, etc.)."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["authType"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["string"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["No"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["The type of principal. Defaults to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["'user'"]},". Values: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["'user'"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["'tenant'"]}," (admin), ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["'business'"]},"."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["context"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["object"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["No"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Context selection for multi-membership users."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["context.businessId"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["string"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["No"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Explicit business ID when user has multiple business memberships."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["context.tenantId"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["string"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["No"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Explicit tenant ID for multi-tenant admin auth."]}]}]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"example-user-login-token-exchange","__idx":6},"children":["Example: User Login (Token Exchange)"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"http","header":{"controls":{"copy":{}}},"source":"POST /auth/token\nHost: api.pers.ninja\nX-Project-Key: a1b2c3d4e5f6789...\nContent-Type: application/json\n\n{\n  \"authToken\": \"external_provider_jwt_token\",\n  \"authType\": \"user\"\n}\n","lang":"http"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Response"]},":"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"accessToken\": \"pers_access_jwt...\",\n  \"refreshToken\": \"pers_refresh_jwt...\",\n  \"user\": { ... }\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"example-business-login-with-context-selection","__idx":7},"children":["Example: Business Login (With Context Selection)"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["When authenticating as a business member, include the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["context"]}," object:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"http","header":{"controls":{"copy":{}}},"source":"POST /auth/token\nHost: api.pers.ninja\nX-Project-Key: pk_test_abc123...\nContent-Type: application/json\n\n{\n  \"authToken\": \"passkey_jwt_token\",\n  \"authType\": \"business\",\n  \"context\": {\n    \"businessId\": \"550e8400-e29b-41d4-a716-446655440000\"\n  }\n}\n","lang":"http"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Response"]},":"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"accessToken\": \"pers_business_jwt...\",\n  \"refreshToken\": \"pers_refresh_jwt...\",\n  \"user\": { ... },\n  \"business\": { \"id\": \"...\", \"displayName\": \"Acme Corp\", ... }\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"blockquote","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Note"]},": If user has multiple business memberships and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["context.businessId"]}," is not provided, the API returns a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["MULTIPLE_CONTEXT_SELECTION_REQUIRED"]}," error with available options."]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"3-dpop-demonstrating-proof-of-possession","__idx":8},"children":["3. DPoP (Demonstrating Proof-of-Possession)"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Status"]},": Optional but ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Highly Recommended"]}," for enhanced security."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["DPoP binds an access token to a private key held by the client, preventing token replay attacks if the token is leaked. The PERS API supports DPoP validation on authentication and protected resource requests."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"how-verification-works","__idx":9},"children":["How Verification Works"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Client"]}," generates a temporary key pair (e.g., ES256)."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Client"]}," creates a DPoP proof (a JWT signed with the private key) containing the HTTP method and URI."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Client"]}," sends the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["dpop"]}," header with the proof."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Server"]}," binds the issued access token to the public key in the DPoP proof."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Subsequent Requests"]},": Client must send a new DPoP proof with each request using the bonded token."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"implementation-guide","__idx":10},"children":["Implementation Guide"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"step-1-generate-dpop-proof","__idx":11},"children":["Step 1: Generate DPoP Proof"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Use a library or generate a standard DPoP JWT header:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Typ"]},": ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["dpop+jwt"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Alg"]},": ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ES256"]}," (recommended)"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Jwk"]},": Public key"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Claims"]},":"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["htm"]},": HTTP Method (e.g., ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["POST"]},")"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["htu"]},": HTTP URI (e.g., ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["https://api.pers.ninja/v2/auth/token"]},")"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["iat"]},": Issued At (timestamp)"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["jti"]},": Unique ID"]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"step-2-include-header","__idx":12},"children":["Step 2: Include Header"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"http","header":{"controls":{"copy":{}}},"source":"POST /auth/token\nX-Project-Key: ...\ndpop: <dpop-proof-jwt>\n...\n","lang":"http"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The server will embed the DPoP key thumbprint into the returned ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["accessToken"]},"."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"authentication-methods-summary","__idx":13},"children":["Authentication Methods Summary"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"align":"left","data-label":"Auth Type"},"children":["Auth Type"]},{"$$mdtype":"Tag","name":"th","attributes":{"align":"left","data-label":"Headers Required"},"children":["Headers Required"]},{"$$mdtype":"Tag","name":"th","attributes":{"align":"left","data-label":"Use Case"},"children":["Use Case"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Public"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["X-Project-Key"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Viewing public campaigns, checking user existence, public catalogs."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["User"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["X-Project-Key"]},{"$$mdtype":"Tag","name":"br","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Authorization: Bearer <user_jwt>"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["User-specific operations, claim rewards, view simplified profile."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Business"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["X-Project-Key"]},{"$$mdtype":"Tag","name":"br","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Authorization: Bearer <biz_jwt>"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Managing business assets, campaigns (if delegated)."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Admin"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Authorization: Bearer <admin_jwt>"]},{"$$mdtype":"Tag","name":"br","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"em","attributes":{},"children":["(Tenant context often embedded)"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Full tenant administration, configuration."]}]}]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"note-on-legacy-api-keys","__idx":14},"children":["Note on Legacy API Keys"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Older integrations may have used ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["x-api-key"]}," for authentication. This pattern is deprecated. Please migrate to the ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["JWT"]}," pattern using the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["/auth/token"]}," exchange flow for all secure operations."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"role-based-access-control-rbac","__idx":15},"children":["Role-Based Access Control (RBAC)"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Some endpoints require a minimum membership role within a business context. The role hierarchy (highest to lowest):"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"align":"left","data-label":"Role"},"children":["Role"]},{"$$mdtype":"Tag","name":"th","attributes":{"align":"left","data-label":"Description"},"children":["Description"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["OWNER"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Full control - can delete entity, transfer ownership"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["ADMIN"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Administrative access - manage members & settings"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["EDITOR"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Create/modify content, cannot manage members"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["VIEWER"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Read-only access"]}]}]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"how-role-checks-work","__idx":16},"children":["How Role Checks Work"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Role is baked into JWT"]},": When authenticating as a business member, your role is included in the JWT token at login time."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Guard validates role"]},": The ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GlobalAuthGuard"]}," checks ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["minBusinessRole"]}," requirements without additional service calls."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["TENANT admins bypass"]},": Tenant administrators have full access regardless of ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["minBusinessRole"]}," settings."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"api-documentation-pattern","__idx":17},"children":["API Documentation Pattern"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Endpoints with role requirements document them in the description:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"header":{"controls":{"copy":{}}},"source":"Requires BUSINESS auth (min role: ADMIN) or TENANT admin.\n"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"example-membership-endpoints","__idx":18},"children":["Example: Membership Endpoints"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"align":"left","data-label":"Endpoint"},"children":["Endpoint"]},{"$$mdtype":"Tag","name":"th","attributes":{"align":"left","data-label":"Min Role"},"children":["Min Role"]},{"$$mdtype":"Tag","name":"th","attributes":{"align":"left","data-label":"Description"},"children":["Description"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GET /businesses/:id/members"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["VIEWER"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["List members (any member can view)"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["POST /businesses/:id/members"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["ADMIN"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Add member (admin only)"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["PUT /businesses/:id/members/:userId"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["ADMIN"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Update role (admin only)"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["DELETE /businesses/:id/members/:userId"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["ADMIN"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Remove member (admin only)"]}]}]}]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"mixed-authentication-endpoints","__idx":19},"children":["Mixed Authentication Endpoints"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Many PERS endpoints support multiple authentication methods, providing flexibility for different integration scenarios."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"example-file-upload-endpoint","__idx":20},"children":["Example: File Upload Endpoint"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["/files/entity-storage-url"]}," endpoint accepts multiple authentication types:"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"business-integration","__idx":21},"children":["Business Integration"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"// JWT authentication for business file uploads\nconst response = await fetch('https://api.pers.ninja/v2/files/entity-storage-url', {\n  method: 'POST',\n  headers: {\n    'Authorization': 'Bearer <business_jwt_token>',\n    'X-Project-Key': 'a1b2c3d4e5f6789...',\n    'Content-Type': 'application/json'\n  },\n  body: JSON.stringify({\n    entityType: 'CAMPAIGN',\n    operation: 'upload'\n  })\n});\n","lang":"javascript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"user-integration","__idx":22},"children":["User Integration"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Authenticate first, then use the access token."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"// 1. Exchange Token\nconst authResponse = await fetch('/auth/token', { ... }); \n// 2. Use Access Token\nconst response = await fetch('/files/entity-storage-url', {\n  headers: {\n    'Authorization': `Bearer ${accessToken}`,\n    'X-Project-Key': '...',\n  },\n  ...\n});\n","lang":"javascript"},"children":[]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"client-libraries--sdks","__idx":23},"children":["Client Libraries & SDKs"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"explorinspers-shared","__idx":24},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["@explorins/pers-shared"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The official TypeScript library providing type-safe contracts, DTOs, and error handling for PERS API integration."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"installation","__idx":25},"children":["Installation"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"npm install @explorins/pers-shared\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"usage-with-new-auth-patterns","__idx":26},"children":["Usage with New Auth Patterns"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The library includes DTOs for the unified authentication flow:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"import { \n  SessionAuthRequestDTO, \n  SessionAuthResponseDTO,\n  AccountOwnerType \n} from '@explorins/pers-shared';\n\n// 1. Prepare Authentication Request\nconst loginRequest: SessionAuthRequestDTO = {\n  authToken: \"firebase_id_token_or_other_proof\",\n  authType: AccountOwnerType.USER // or BUSINESS / TENANT\n};\n\n// 2. Call Unified Endpoint\n// (Implementation depends on your HTTP client)\nconst response = await httpClient.post<SessionAuthResponseDTO>(\n  '/auth/token', \n  loginRequest,\n  {\n    headers: { 'X-Project-Key': 'a1b2c3d4e5f6789...' }\n  }\n);\n\n// 3. Use Tokens\nconst { accessToken, refreshToken, user } = response.data;\n","lang":"typescript"},"children":[]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"counterfactual-wallets--user-signing","__idx":27},"children":["Counterfactual Wallets & User Signing"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["PERS implements a counterfactual wallet system that provides users with blockchain addresses before their first transaction."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/7.counterfactual-wallets-guide"},"children":["Counterfactual Wallets & Signing Accounts Guide →"]}]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"error-handling","__idx":28},"children":["Error Handling"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Authentication and authorization failures follow the same structured error format as the rest of the API (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SECURITY"]}," category, HTTP 401/403). See the ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/8.error-handling"},"children":["Error Handling Guide"]}," for the full error category reference, all error codes, and response examples."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"security-best-practices","__idx":29},"children":["Security Best Practices"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"jwt-token-security","__idx":30},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["JWT Token Security"]}]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["DPoP"]},": Use DPoP to bind tokens to your client instance."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Storage"]},": Store JWT tokens securely."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["HTTPS"]},": Always use HTTPS."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"project-key-security","__idx":31},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Project Key Security"]}]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Context Only"]},": Remember ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["X-Project-Key"]}," is for context, not user identity."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Rotation"]},": Rotate keys if they are exposed in unauthorized contexts."]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"getting-started","__idx":32},"children":["Getting Started"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Obtain Project Key"]},": Use your tenant's ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["X-Project-Key"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Authenticate"]},": Use ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["POST /auth/token"]}," to exchange your provider's token for a PERS JWT."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Secure"]},": Add ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["dpop"]}," headers for maximum security."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Call API"]},": Use the JWT in the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Authorization"]}," header for protected endpoints."]}]}]},"headings":[{"value":"PERS Authentication Guide","id":"pers-authentication-guide","depth":1},{"value":"Architecture Overview","id":"architecture-overview","depth":2},{"value":"1. Project Identification (Public Access)","id":"1-project-identification-public-access","depth":2},{"value":"2. Authentication (JWT)","id":"2-authentication-jwt","depth":2},{"value":"Unified Token Endpoint","id":"unified-token-endpoint","depth":3},{"value":"Request Body ( SessionAuthRequestDTO )","id":"request-body--sessionauthrequestdto-","depth":4},{"value":"Example: User Login (Token Exchange)","id":"example-user-login-token-exchange","depth":4},{"value":"Example: Business Login (With Context Selection)","id":"example-business-login-with-context-selection","depth":4},{"value":"3. DPoP (Demonstrating Proof-of-Possession)","id":"3-dpop-demonstrating-proof-of-possession","depth":2},{"value":"How Verification Works","id":"how-verification-works","depth":3},{"value":"Implementation Guide","id":"implementation-guide","depth":3},{"value":"Step 1: Generate DPoP Proof","id":"step-1-generate-dpop-proof","depth":4},{"value":"Step 2: Include Header","id":"step-2-include-header","depth":4},{"value":"Authentication Methods Summary","id":"authentication-methods-summary","depth":2},{"value":"Note on Legacy API Keys","id":"note-on-legacy-api-keys","depth":3},{"value":"Role-Based Access Control (RBAC)","id":"role-based-access-control-rbac","depth":2},{"value":"How Role Checks Work","id":"how-role-checks-work","depth":3},{"value":"API Documentation Pattern","id":"api-documentation-pattern","depth":3},{"value":"Example: Membership Endpoints","id":"example-membership-endpoints","depth":3},{"value":"Mixed Authentication Endpoints","id":"mixed-authentication-endpoints","depth":2},{"value":"Example: File Upload Endpoint","id":"example-file-upload-endpoint","depth":3},{"value":"Business Integration","id":"business-integration","depth":4},{"value":"User Integration","id":"user-integration","depth":4},{"value":"Client Libraries & SDKs","id":"client-libraries--sdks","depth":2},{"value":"@explorins/pers-shared","id":"explorinspers-shared","depth":3},{"value":"Installation","id":"installation","depth":4},{"value":"Usage with New Auth Patterns","id":"usage-with-new-auth-patterns","depth":4},{"value":"Counterfactual Wallets & User Signing","id":"counterfactual-wallets--user-signing","depth":2},{"value":"Error Handling","id":"error-handling","depth":2},{"value":"Security Best Practices","id":"security-best-practices","depth":2},{"value":"JWT Token Security","id":"jwt-token-security","depth":3},{"value":"Project Key Security","id":"project-key-security","depth":3},{"value":"Getting Started","id":"getting-started","depth":2}],"frontmatter":{"seo":{"title":"PERS Authentication Guide"}},"lastModified":"2026-08-21T20:04:16.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/5.authentication-guide","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}