# Authdog > Authdog is Agentic Identity, Access Control, and Observability — scoped credentials for workloads, authorization models per environment, and Lidar as the identity SIEM. Expanded Markdown for every document linked from https://www.authdog.com/llms.txt, in the same order. Prefer https://www.authdog.com/llms.txt when you only need the index. ## Concepts ### Authentication Source: https://www.authdog.com/docs/concepts/authentication.md Authentication is how your app learns who is making a request. Authdog runs sign-in, checks the result with the identity provider you configured, and gives your app a session to validate from then on. Your app never sees the password. It sends the person to Authdog, Authdog finishes the method you enabled, and your backend checks the session on later requests. To add this to an app, follow [Sign up and sign in](/docs/authentication), or start from a [quickstart](/docs/quickstarts). ## How it works
Your user Selects a sign-in method
Authdog
Creates a trusted session
1 Verify identity 2 Create or link user 3 Issue JWT and cookie
Your app Validates the session

No passwords in your app

That identity is what [authorization](/docs/concepts/authorization), [organizations](/docs/concepts/organizations), and [provisioning](/docs/concepts/provisioning) build on. ## Sign-in methods Turn on the methods you want in each environment. Only enabled methods show up on that environment's sign-in screen, so development, staging, and production can differ. - **Password.** Email and password. - **Social login.** OAuth with providers such as Google, GitHub, Microsoft, and Apple. See [Integrations](/docs/integrations). - **Magic link.** A one-time link or code sent by email. - **Enterprise SSO.** SAML 2.0 or OIDC connections to a customer's identity provider. - **Multi-factor authentication.** A second factor, such as TOTP, after the first sign-in succeeds. ## Enterprise SSO and domain routing Add each customer identity provider as a connection on the environment. The connection holds the SAML metadata or the OIDC discovery settings. Authdog can pick the connection from the user's email domain. Discovery returns the matching connection and a sign-in URL, so you can send them to their company sign-in page without building customer-specific routing in your app. **Request** ```bash curl --get "https://identity.authdog.com/api/v1/sso/discover" \ --data-urlencode "environmentId=" \ --data-urlencode "email=jane@acme.com" ``` **Response** ```json { "connectionId": "...", "providerId": "okta", "signinUrl": "https://identity.authdog.com/..." } ``` ## After sign-in Once Authdog accepts the sign-in, it: 1. Creates the user, or links the identity to an existing user with the same verified email. 2. Issues a signed JWT in the `authdog-session` cookie. API clients can send that same token as a bearer token. 3. Sends events such as `user.signed_in` and `user.created` to the [webhooks and integrations](/docs/integrations) you've configured. Your backend validates the session on each request. It doesn't check the original credential again. See [Sessions and tokens](/docs/concepts/sessions-tokens). ## Machine-to-machine authentication Service-to-service calls don't have a person at a browser. An [M2M application](/docs/integrations) exchanges a client ID and secret for an access token with the OAuth 2.0 client credentials grant. There's no interactive sign-in, and no session cookie. ## Configuration - Sign-in methods, social providers, and MFA live on the environment. - Add enterprise SSO connections on the environment, or let customers set them up in the admin portal. - Use the hosted [Account portal](/docs/account-portal), or build the UI with [custom flows](/docs/custom-flows) and [components](/docs/components). ## Related | Read | To learn how to | | --- | --- | | [Sessions and tokens](/docs/concepts/sessions-tokens) | Validate the session your app receives | | [Sign up and sign in](/docs/authentication) | Turn the methods on and wire the UI | | [Authorization](/docs/concepts/authorization) | Decide what a signed-in user can do | | [Provisioning](/docs/concepts/provisioning) | Create enterprise users before they sign in | ### Connectors Source: https://www.authdog.com/docs/connectors.md Connectors are the external identity providers your users sign in with, Apple, Google, Meta, Microsoft, GitHub, and sixty more. Authdog handles the OAuth 2.0 exchange, normalizes the profile it gets back, and links it to an Authdog user, so your application only ever deals with an Authdog session. Every connector is configured **per environment**. Development, staging, and production each hold their own client credentials and their own callback URL, and enabling a provider in one environment never affects another. ## How a connector works 1. A user picks the provider on your sign-in page (hosted or your own UI). 2. Authdog redirects to the provider's authorization endpoint with the client ID and scopes for that connection. 3. The user authenticates and consents at the provider. 4. The provider redirects back to Authdog's callback URL with an authorization code. 5. Authdog exchanges the code for tokens, reads the profile, and creates or links the Authdog user. 6. Your application receives an Authdog session, exactly as it would after a password or passkey sign-in. Your code never handles the provider's tokens. It reads the Authdog session, and provider identity arrives as a linked identity on the user. ## Set up any connector The steps are the same for every provider. Only the developer console you visit and the fields you copy differ. 1. Create an OAuth application in the provider's developer console. 2. Copy the **redirect URI** Authdog shows you into that application's list of allowed redirect URIs. 3. Paste the provider's **client ID** and **client secret** into Authdog. 4. Activate the connection and test a sign-in. In the [Authdog console](https://console.authdog.com): 1. Select the project and environment. 2. Open **Authentication > Providers**. 3. Filter to **OAuth 2.0** and find the provider in the catalog. 4. Click **Enable**. 5. Copy the **Redirect URI** shown in the form. 6. Enter the provider's credentials. 7. Save, then toggle the connection **active**. ## The redirect URI Authdog's callback endpoint is bound to the individual connection, not to the provider: ```url https://identity.authdog.com/api/v1/callback/ ``` The connection form displays the exact string with a copy button, use that value rather than assembling it by hand. Three things change it: | Situation | Callback host | | --- | --- | | Default | `https://identity.authdog.com` | | Environment with a live [custom domain](/docs/custom-domains) | `https://auth.yourdomain.com` | | Local development | `http://localhost:3500` | Because `connectionId` is unique per connection, the same provider configured in two environments produces two different redirect URIs. Register both in the provider's application, or create a separate provider application per environment, which is the better practice, since it also keeps credentials separated. > **Caution** > > Most providers match redirect URIs exactly: scheme, host, path, and trailing > slash. A mismatch surfaces as the provider's own error page, `redirect_uri_mismatch`, > `invalid_redirect_uri`, before the user ever returns to Authdog. ## Starting a sign-in With the hosted sign-in page, every active connection renders as a button automatically. Nothing to wire up. From your own UI, link to the connection's sign-in endpoint: ```url https://identity.authdog.com/api/v1/signin/ ``` Pass `login_hint` to pre-fill the account, and the endpoint honors the sign-up and return-URL context Authdog carries through the flow. ## Provider guides Every connector in the console catalog has its own guide, linked from the cards at the foot of this page and from the sidebar. They share the redirect-URI step above; each guide covers where the credentials come from and what the provider actually returns. The provider ID is what the connection row stores, and what the console shows on the connection: | Connector | Provider ID | Connector | Provider ID | | --- | --- | --- | --- | | [Apple](/docs/connectors/apple) | `apple-oauth20` | [Figma](/docs/connectors/figma) | `figma-oauth20` | | [Google](/docs/connectors/google) | `google-oauth20` | [Fitbit](/docs/connectors/fitbit) | `fitbit-oauth20` | | [Meta (Facebook)](/docs/connectors/meta) | `facebook-oauth20` | [FreshBooks](/docs/connectors/freshbooks) | `freshbooks-oauth20` | | [Microsoft](/docs/connectors/microsoft) | `microsoft-oauth20` | [Hugging Face](/docs/connectors/huggingface) | `huggingface-oauth20` | | [GitHub](/docs/connectors/github) | `github-oauth20` | [IBMid](/docs/connectors/ibmid) | `ibmid-oauth20` | | [GitLab](/docs/connectors/gitlab) | `gitlab-oauth20` | [ID.me](/docs/connectors/idme) | `idme-oauth20` | | [LinkedIn](/docs/connectors/linkedin) | `linkedin-oauth20` | [ID.me Community](/docs/connectors/idme-community) | `idme-community-oauth20` | | [Discord](/docs/connectors/discord) | `discord-oauth20` | [IDPartner](/docs/connectors/idpartner) | `idpartner-oauth20` | | [Slack](/docs/connectors/slack) | `slack-oauth20` | [Indeed](/docs/connectors/indeed) | `indeed-oauth20` | | [X (Twitter)](/docs/connectors/twitter) | `twitter-oauth20` | [Intuit](/docs/connectors/intuit) | `intuit-oauth20` | | [TikTok](/docs/connectors/tiktok) | `tiktok-oauth20` | [Kakao](/docs/connectors/kakao) | `kakao-oauth20` | | [Spotify](/docs/connectors/spotify) | `spotify-oauth20` | [Keycloak](/docs/connectors/keycloak) | `keycloak-oauth20` | | [Twitch](/docs/connectors/twitch) | `twitch-oauth20` | [Klarna](/docs/connectors/klarna) | `klarna-oauth20` | | [Amazon](/docs/connectors/amazon) | `amazon-oauth20` | [LINE](/docs/connectors/line) | `line-oauth20` | | [Asignio](/docs/connectors/asignio) | `asignio-oauth20` | [NAVER](/docs/connectors/naver) | `naver-oauth20` | | [AWS Cognito](/docs/connectors/aws-cognito) | `aws-cognito-oauth20` | [PayPal](/docs/connectors/paypal) | `paypal-oauth20` | | [Badge](/docs/connectors/badge) | `badge-oauth20` | [PayPal Sandbox](/docs/connectors/paypal-sandbox) | `paypal_sb-oauth20` | | [Beyond Identity](/docs/connectors/beyondidentity) | `beyondidentity-oauth20` | [Descope](/docs/connectors/descope) | `descope-oauth20` | | [Duo](/docs/connectors/duo) | `duo-oauth20` | | | | [Baidu](/docs/connectors/baidu) | `baidu-oauth20` | [Planning Center](/docs/connectors/planning-center) | `planning-center-oauth20` | | [Basecamp](/docs/connectors/basecamp) | `basecamp-oauth20` | [Renren](/docs/connectors/renren) | `renren-oauth20` | | [BitBadges](/docs/connectors/bitbadges) | `bitbadges-oauth20` | [Salesforce](/docs/connectors/salesforce) | `salesforce-oauth20` | | [Bitbucket](/docs/connectors/bitbucket) | `bitbucket-oauth20` | [Salesforce Sandbox](/docs/connectors/salesforce-sandbox) | `salesforce_sb-oauth20` | | [1Kosmos BlockID](/docs/connectors/blockid) | `blockid-oauth20` | [Salesforce Community](/docs/connectors/salesforce-community) | `salesforce_community-oauth20` | | [Box](/docs/connectors/box) | `box-oauth20` | [Shopify](/docs/connectors/shopify) | `shopify-oauth20` | | [Coinbase](/docs/connectors/coinbase) | `coinbase-oauth20` | [Vercel](/docs/connectors/vercel) | `vercel-oauth20` | | [d ACCOUNT](/docs/connectors/daccount) | `daccount-oauth20` | [Vimeo](/docs/connectors/vimeo) | `vimeo-oauth20` | | [DigitalOcean](/docs/connectors/digitalocean) | `digitalocean-oauth20` | [VKontakte](/docs/connectors/vk) | `vk-oauth20` | | [Dock Web3 ID](/docs/connectors/dock-web3id) | `dock-web3id-oauth20` | [Weibo](/docs/connectors/weibo) | `weibo-oauth20` | | [DocuSign](/docs/connectors/docusign) | `docusign-oauth20` | [WordPress.com](/docs/connectors/wordpress) | `wordpress-oauth20` | | [Dribbble](/docs/connectors/dribbble) | `dribbble-oauth20` | [Xero](/docs/connectors/xero) | `xero-oauth20` | | [Dropbox](/docs/connectors/dropbox) | `dropbox-oauth20` | [Yahoo](/docs/connectors/yahoo) | `yahoo-oauth20` | | [Dwolla](/docs/connectors/dwolla) | `dwolla-oauth20` | [Yammer](/docs/connectors/yammer) | `yammer-oauth20` | | [Exact Online](/docs/connectors/exact-online) | `exact-oauth20` | [Yandex](/docs/connectors/yandex) | `yandex-oauth20` | ## Connectors that need a Domain URI Some providers run on a host you or your customer control, not a fixed one. The connection form asks for a **Domain URI** alongside the credentials: a Cognito user pool domain, a Shopify store, a Keycloak realm, a Salesforce Experience Cloud site, or a Klarna regional login host. Authdog substitutes that value into the provider's endpoints for **AWS Cognito**, **Salesforce Community**, **Shopify**, **Klarna**, **Keycloak**, **Duo**, **Beyond Identity**, and **Descope**. Other catalog entries that accept the field (Exact Online, 1Kosmos BlockID, Asignio, Badge, BitBadges, Dock Web3 ID, and IDPartner) are not substituted on both legs of the flow. Test them end to end in a development environment before rolling them out. > **Note** > > Connectors cover consumer and developer identity. For workforce identity , > Okta, Entra ID, JumpCloud, Ping, BeyondTrust, generic SAML and OIDC, use > [Enterprise SSO](/docs/sso), which adds email-domain routing so a user's > address selects their employer's connection. ## Account linking When a provider returns an email address that already belongs to an Authdog user, the provider identity links to that account rather than creating a second one. Providers that do not release an email (or where the user hides it, as with Apple's private relay) produce an account keyed on the provider's stable subject identifier. Decide early which providers you trust to assert an email, and keep that consistent across environments. See [Users](/docs/users) for how identities appear on the user record. ## Related | Read | To learn how to | | --- | --- | | [Authentication (console)](/docs/console/authentication) | Configure every sign-in method for an environment | | [Enterprise SSO](/docs/sso) | Connect workforce identity providers with SAML and OIDC | | [Custom domains](/docs/custom-domains) | Serve authentication (and callbacks) from your own host | | [Branding](/docs/console/branding) | Style the hosted page these buttons appear on | | [Users](/docs/users) | Inspect linked identities on a user record | ### Authorization Source: https://www.authdog.com/docs/concepts/authorization.md Authorization is the question that comes right after sign-in: you know who the caller is, so what are they allowed to do? Authdog answers that on the server, once per environment, with one of three models. Pick the model that matches how your product thinks about access. To set up roles in practice, see [Roles and permissions](/docs/permissions). ## Authentication vs authorization Keep the two steps separate. Mixing them is how apps end up trusting "they're signed in" as if that meant "they can do this." - **Authentication** establishes identity. The [session](/docs/concepts/sessions-tokens) proves who the caller is. - **Authorization** makes a decision. Given that identity (and sometimes the resource and the context), is this action allowed? A request is authenticated once. It can be authorized many times, once for each protected action. ## Pick one model per environment Each environment runs exactly **one** authorization model. One engine decides every request, so the answer is unambiguous. You can still use a lightweight model in one environment and a richer one in another, without rewriting your app. | Model | Decides based on | Best when | | --- | --- | --- | | **RBAC** | the roles a user holds | access maps cleanly to job functions | | **ABAC** | attributes of the user, the resource, and the context | rules depend on data (department, ownership, time) | | **FGA** | relationships between users and objects | access is per object and hierarchical (folders, docs, teams) | ### RBAC: role-based access control This is the default, and the right place to start. You define **roles**, grant each role a set of **permissions**, and assign roles to users. A user's effective permissions are the union of every role they hold. You can assign a role directly, or grant it from an identity-provider or HRIS group through [provisioning](/docs/concepts/provisioning). [Organization](/docs/concepts/organizations) membership is separate: it governs who administers the account and its tenants, not end-user RBAC inside an environment. ```rbac role: editor permissions: [post.read, post.write] role: admin permissions: [post.*, user.manage] user jane -> [editor] => can post.read, post.write ``` RBAC is seat-based and easy to reason about. Stay here unless you already know you need more. ### ABAC: attribute-based access control Use this when access depends on data, not a fixed job title. A rule like "a manager can approve expenses under $5,000 in their own department" turns into a pile of roles if you try to express it with RBAC. ABAC evaluates a **policy** against attributes of the subject, the resource, and the request. Authdog policies are written in **Rego** (the Open Policy Agent language) and evaluated **at the edge**, in process. There's no external policy server and no extra network hop. You author policies per environment, and they're versioned. ```rego package authdog.authz default allow = false allow { input.user.department == input.resource.department input.action == "approve" input.resource.amount < 5000 } ``` ### FGA: fine-grained (relationship) authorization Use this when permission follows a relationship, object by object. "Anyone who can view a folder can view every document inside it" is that kind of rule. Authdog gives you a **Zanzibar-style** model: you write **relationship tuples** (`document:readme#viewer@user:jane`) and a model that describes how relations inherit, then you ask `check(user, relation, object)`. ```fga tuple: folder:eng#viewer@user:jane tuple: document:spec#parent@folder:eng check(user:jane, viewer, document:spec) -> allowed (inherited via parent) ``` You describe the model in an OpenFGA-style language, for example `define viewer: [user] or viewer from parent`. The console shows that model as a graph. See [FGA](/docs/fga) and the [console module](/docs/console/fga). FGA fits collaborative, document-centric products, where access is defined object by object. ## How enforcement works Whichever model the environment uses, the decision happens **on the server**, before your resolver or handler runs. The client can't skip it. The engine reads the caller's identity from the validated [session](/docs/concepts/sessions-tokens), loads that environment's roles, policies, or tuples, and returns allow or deny. Any error fails closed. Tenant owners always keep management access. ## Choosing a model - Start with **RBAC**. Most apps never outgrow it. - Move to **ABAC** when the decision depends on the resource or on context. - Move to **FGA** when access is defined per object and inherited through a hierarchy. You can run a different model in each environment, so it's safe to try a richer model in staging first. ## Related | Read | To learn how to | | --- | --- | | [Roles and permissions](/docs/permissions) | Set up RBAC in practice | | [AuthZEN API](/docs/authzen) | Ask for a decision over a vendor-neutral contract | | [Organizations](/docs/concepts/organizations) | Tell account membership apart from environment users | | [Provisioning](/docs/concepts/provisioning) | Grant roles from identity-provider or HRIS groups | | [Sessions and tokens](/docs/concepts/sessions-tokens) | See the identity every decision starts from | ### Sessions & tokens Source: https://www.authdog.com/docs/concepts/sessions-tokens.md When a user signs in to your application, Authdog issues a short-lived session token you can use to authenticate requests to your backend. The token is a signed JSON Web Token (JWT). It says who the user is, and your app checks it on later requests instead of handling their password. Read how sign-in produces the token in [Authentication](/docs/concepts/authentication). To validate it on a server, see [Backend requests](/docs/backend). ## What a session is The session is the JWT. Authdog signs it when authentication succeeds. - **Signed, not encrypted.** Anyone who has the token can read the claims. Only Authdog can produce a valid signature, so a forged token fails validation. - **Scoped to one environment.** Each environment signs with its own private key. A token from `dev` will not validate in `production`. - **Short-lived.** It expires, and Authdog refreshes it. A leaked token is only useful for a short window. Your app doesn't store the password, and it doesn't re-check the original credential. It verifies this token. ## How the session reaches your app Authdog can hand you the same token two ways. Accept either one on your backend: - **Cookie.** After sign-in, Authdog sets `authdog-session` on the browser. This is the usual path for a first-party web app, because the browser attaches the cookie for you. - **Bearer token.** Send `Authorization: Bearer ` when the client can't use a cookie: a mobile app, a single-page app calling your API, or another service. ```http GET /api/orders HTTP/1.1 Cookie: authdog-session=eyJhbGciOi... # or Authorization: Bearer eyJhbGciOi... ``` ## Keys: public vs private Every environment has its own key material. The two kinds do different jobs: - **Signing keys** stay inside Authdog and sign sessions. The private half never leaves the platform. Verifiers fetch the public half from the environment's JWKS endpoint. - The **public key** (`pk_...`) is a publishable identifier. It's safe to put in your app. SDKs use it to tell which environment a token belongs to, and where to validate it. Because the keys are per environment, a token issued in `dev` can never validate against `production`. See [Deployments](/docs/deployments) when you're promoting configuration across stages. ## How your app validates a session If you're using an Authdog SDK, this is handled for you. The steps underneath are short, and they're worth knowing if you ever validate a token yourself: 1. Read the token from the cookie or the `Authorization` header. 2. Resolve the environment from the `pk_...` public key. 3. Confirm the token with the identity provider's `userinfo` endpoint. That call checks the signature and expiry, honors revocation, and returns a fresh profile. 4. You get a typed context: who the user is, or that nobody is signed in. ```python # Illustrative: an SDK wraps all of this ctx = authdog.session(request) # reads cookie/Bearer, verifies, calls userinfo if not ctx.is_authenticated: return 401 user_id = ctx.user.id ``` A bad token doesn't throw. A missing or invalid session comes back as "not authenticated," so you can gate on a boolean instead of catching exceptions. ## Sessions and authorization The validated session is the identity every [authorization](/docs/concepts/authorization) decision starts from. Claims on the token (user id, roles, permissions, and entitlements if you use the Stripe add-on) are enough for a fast check without another lookup. Richer checks (RBAC, ABAC, or FGA) still run on the server against that same identity. [Organizations](/docs/concepts/organizations) sit above tenants in the management hierarchy. They aren't an end-user claim inside the environment session. ## Lifecycle and revocation - **Expiry and refresh.** Sessions are short-lived and refreshed for you, so a leaked token has a bounded window. - **Sign-out.** Clears the `authdog-session` cookie and ends the session. - **Revocation.** Suspending or deleting a [user](/docs/users), or rotating an environment's keys, invalidates outstanding sessions on the next `userinfo` check. ## Service-to-service tokens A machine doesn't get a session cookie. An [M2M application](/docs/integrations) uses the OAuth 2.0 client credentials grant: it exchanges a client id and secret for a short-lived access token. Validate that token the same way you validate any other bearer token. ## Related | Read | To learn how to | | --- | --- | | [Authentication](/docs/concepts/authentication) | Issue a session from sign-in | | [JWT claims](/docs/jwt-claims) | Add your own claims to the token | | [Backend requests](/docs/backend) | Validate sessions on your server | | [Authorization](/docs/concepts/authorization) | Decide what that identity can do | | [Security](/docs/security) | See the defaults behind the token model | ### Multi-tenancy Source: https://www.authdog.com/docs/concepts/multi-tenancy.md Multi-tenancy is how Authdog keeps data, configuration, and identities apart as your account grows. Once the layers are clear, the console and the CLI both make more sense: you always know which workspace you're changing. ## The platform hierarchy ```hierarchy Organization └── Tenant (workspace) └── Project (an application / product) └── Environment (dev · staging · production) ├── signing keys + public key (pk_...) ├── sign-in methods & connections ├── users └── authorization model (RBAC | ABAC | FGA) ``` - **[Organization](/docs/concepts/organizations).** The account boundary: billing, admin memberships, and the tenants you link under it. - **Tenant.** The workspace for projects, team access, and domains. Day-to-day console work happens inside a selected tenant. - **Project.** One application or product. It groups the environments that application ships through. - **Environment.** The unit that actually serves auth. It has its own keys, connections, [user store](/docs/users), and [authorization](/docs/concepts/authorization) model. ## Organization An organization groups tenants under shared billing and administration. Not every account has one. When it does, tenant pickers and CLI `/browse` only show tenants linked to the organization you've selected. See [Organizations](/docs/concepts/organizations). ## Tenant This is the primary workspace: projects, members, verified domains, and settings for your company or for a client. Use a separate tenant when a product, a client, or a business unit shouldn't share that configuration. ## Project A project is one application or product. It keeps that application's environments away from your other products. MCP servers follow the same hierarchy, but they expose server-side tool endpoints instead of a client-facing sign-in flow. ## Environment This is the unit that serves auth. **Each environment is fully self-contained.** It has its own signing keys and [public key](/docs/concepts/sessions-tokens), its own sign-in methods and SSO connections, its own [user store](/docs/users), and its own [authorization model](/docs/concepts/authorization). Environments map naturally onto deployment stages. See [Deployments](/docs/deployments). ## Why environment isolation matters Keys belong to one environment, so a [session](/docs/concepts/sessions-tokens) issued in `dev` can never validate in `production`, and the reverse is true too. That gives you: - **Safe testing.** Experiment in dev without touching production users or data. See [Testing](/docs/testing). - **Clean promotion.** Configure a connection or a policy in staging, verify it, then apply the same shape to production. - **A smaller blast radius.** Rotating or leaking a dev key has no effect on production. Your app selects the environment by using that environment's public key. There is no cross-environment leakage. ## Choosing your structure - One product, a few stages: one tenant, one project, and an environment per stage. - Several products: a project per product (same tenant or separate tenants), each with its own environments. - Several business units or clients that need their own teams and billing: separate tenants, optionally under one [organization](/docs/concepts/organizations). - Don't treat an organization as a folder inside an environment. Organizations sit above tenants. End users live in environments. ## Related | Read | To learn how to | | --- | --- | | [Organizations](/docs/concepts/organizations) | Set the account boundary, memberships, and billing | | [Tenants](/docs/console/tenants) | Create and manage workspaces | | [Projects](/docs/console/projects) | Browse and create projects inside a tenant | | [Environments](/docs/console/environments) | Manage deployment stages | | [Deployments](/docs/deployments) | Treat environments as stages | | [Sessions and tokens](/docs/concepts/sessions-tokens) | See why a dev token can't validate in production | ### Organizations Source: https://www.authdog.com/docs/concepts/organizations.md An **organization** is the top of your Authdog account. It groups one or more [tenants](/docs/console/tenants) under shared billing, administration, and membership. This is your account boundary. It isn't a directory of the customers who sign in to your app. Those people live in an environment's user store. ## Platform hierarchy ```hierarchy Organization └── Tenant (workspace) └── Project (application / product) └── Environment (dev · staging · production) ``` - **Organization.** Billing, logo, owner, and the people who administer the account. It owns tenants through organization–tenant links. - **Tenant.** The day-to-day workspace: projects, team access, domains, and settings. See [Multi-tenancy](/docs/concepts/multi-tenancy). - **Project and environment.** Where applications, keys, connections, and the end-user store live. The CLI and the console follow the same path. Pick an organization when the account has one, then a tenant, a project, and an environment. Accounts with no organization still work. Navigation starts at the tenant list. ## Memberships and invitations People join an organization through a **membership**. Members are account administrators (for example `admin` or `member`). They are not end users in an environment's user store. Removing a membership revokes console and API access for that organization. It doesn't delete the underlying user account. Invite teammates by email, with a role. You can also attach tenant access to the invite, so the new member can enter specific tenants when they accept. ## What an organization owns | Concern | Scoped to the organization | | --- | --- | | Billing and subscription | Plan and seat totals roll up across tenants | | Tenants | Linked through organization–tenant associations | | Service accounts and org keys | Automation credentials for management APIs | | Member roles | Who can administer linked tenants and projects | End-user identities, signing keys, and sign-in methods stay on the **environment**. Switching organization in the CLI or the console clears the lower scope (tenant, project, environment), so you don't act on the wrong subtree. ## Organizations vs tenants Use an **organization** when you want one billing and admin umbrella over several workspaces. Use separate **tenants** when products, clients, or business units shouldn't share projects, teams, or settings. Don't create one organization per end-customer company, unless that company is its own Authdog account, with its own tenants and its own billing. An environment can store **customer records** (an external key and a display name) for the app's own customers. Those records aren't organizations, and they don't add a second SSO connection. Enterprise SSO stays one connection for the environment. ## Related | Read | To learn how to | | --- | --- | | [Multi-tenancy](/docs/concepts/multi-tenancy) | Keep tenants, projects, and environments apart | | [Tenants](/docs/console/tenants) | Create and select workspaces in the console | | [Projects](/docs/console/projects) | Add applications inside a tenant | | [Environments](/docs/console/environments) | Manage stages and credentials | ### Provisioning Source: https://www.authdog.com/docs/concepts/provisioning.md Provisioning is how [users](/docs/users) and their access get created, updated, and removed on their own, from a customer's identity provider or HR system, instead of from a support ticket. When someone joins or leaves that company, their access to your app should follow. That's what this is for. ## The problem it solves Without provisioning, every new hire signs up by hand, and every departure leaves an account behind. Provisioning closes that loop. The customer's directory is the **source of truth**: their identity provider or HRIS pushes changes to Authdog, and Authdog reflects them in your environment's [user store](/docs/users) and [roles](/docs/concepts/authorization). Authdog exposes two inbound channels. Both are token-authenticated REST endpoints, scoped to one environment. ## SCIM 2.0: identity provider sync **SCIM** (System for Cross-domain Identity Management) is the standard identity providers such as Okta, Entra ID, and JumpCloud already speak. Authdog exposes a SCIM 2.0 service: - **Endpoint:** `/v1/scim/v2` - **Auth:** a bearer token prefixed `adscim_`, issued per environment in the console - **Resources:** Users and Groups: create, update, deactivate, delete; group membership changes Configure the identity provider once, with the endpoint and the token. After that it pushes user and group changes on its own. Deactivating a user there suspends them in Authdog, which invalidates their [sessions](/docs/concepts/sessions-tokens). ## HRIS: HR-driven provisioning Some customers treat the **HR system** as the source of truth, not the identity provider. The HRIS connector covers that case. It mirrors the SCIM slice: - **Endpoint:** `/v1/hris/v1` - **Auth:** a bearer token prefixed `adhris_` - **Resources:** Employees (users, with employment attributes like job title, manager, department, employment type, cost center, start/end dates) and Departments (groups) Employment status can drive access, not only whether someone is in a directory group. ## Group-to-role mapping Creating the user is only half of it. They also need the right **access**. Authdog maps directory **groups** (SCIM groups or HRIS departments) to [roles](/docs/permissions) for you: ``` IdP group "Engineering" -> role "developer" IdP group "Billing Admins" -> role "billing-admin" HRIS department "Support" -> role "agent" ``` When group membership changes upstream, Authdog recomputes that user's roles additively. RBAC [authorization](/docs/concepts/authorization) stays in step with the customer's org chart. A mapping matches the group display name (case-insensitive) or the external id (exact). ## Deprovisioning This is the part that protects you. When someone is removed or deactivated upstream: - their Authdog user is **suspended or deleted**, - outstanding [sessions](/docs/concepts/sessions-tokens) stop validating on the next check, - and group-derived roles fall away. Access ends when employment ends. Nobody has to remember a cleanup ticket. ## How it fits together
Customer directory Source systems
IdP HRIS
Authdog environment Synchronizes identity and access
1 Create or update user 2 Sync group membership 3 Apply role mappings
Your app Authorizes with current roles
Access reflects latest directory state
Provisioning tokens are scoped to one **environment**. For enterprise customers, provisioning pairs with their [enterprise SSO](/docs/concepts/authentication) connection: the same customers who bring their own login also bring their own directory. Platform account structure ([organizations](/docs/concepts/organizations) and tenants) stays above the environment. Provisioning only changes that environment's user store. ## Related | Read | To learn how to | | --- | --- | | [Users](/docs/users) | See the records provisioning creates | | [Roles and permissions](/docs/permissions) | Land group mappings on roles | | [Authentication](/docs/concepts/authentication) | Pair provisioning with enterprise SSO | | [Integrations](/docs/integrations) | Browse the connector catalog | ### Lidar Source: https://www.authdog.com/docs/concepts/lidar.md **Lidar** is Authdog's identity SIEM. It watches authentication events for each environment, correlates related activity, and raises **Signals** you can search and triage in the console. It is the SIEM for identity: ingest, detect, investigate. If you already run Datadog, Splunk, or Sentinel, [notification channels](/docs/events-webhooks) can copy the same trail there. You don't need a second product just to investigate identity. The operator walkthrough is [Lidar in the console](/docs/console/lidar). ## How it works Lidar runs as parallel correlating slices over auth events: 1. **Monitors** define which detectors run and how they are tuned (query, severity, enabled). 2. Auth events are **classified** into classes such as `authn.failure` or `authn.success`. 3. Events are **grouped** by IP, subject (user), or environment, depending on the detector. 4. Matching detectors produce a **finding** when the analysis marks the group suspicious. 5. Findings appear in the console as **Signals**. ``` monitor.query → detector + thresholds (per environment) events → classify groupBy → correlate (subject | ip | environment) finding → threats row (deduped) console Signal → SecOps UI over that row ``` Findings are environment-scoped and stored in the platform threats store, so the same detection is readable from Lidar Signals and from the threats API. ## Monitors and detectors Every environment seeds a default monitor catalog. Operators can enable or disable monitors and edit the query string and severity. A monitor query always starts with `detection:` and may add threshold tokens: | Detector | Default query | Correlates by | Question it answers | | --- | --- | --- | --- | | Brute force | `detection:brute_force attempts:>100 window:5m` | IP | Credential attack from one source? | | Impossible travel | `detection:impossible_travel distanceKm:>5000` | Subject | Physically impossible movement? | | Bot | `detection:bot botScore:>0.9` | IP | Automated auth traffic? | | Breached password | `detection:breached_password` | Subject | Known-bad password attributes present? | | Rate limit | `detection:rate_limit observed:>limit` | IP | Auth volume spike? | | Unrecognized device | `detection:unrecognized_device action:challenged` | Subject | New device or client for this user? | | Stale account | `detection:stale_account dormantDays:>90` | Subject | Dormant account suddenly active? | | Repeat signup | `detection:repeat_signup priorAccounts:>2` | IP | Registration abuse? | | Credential stuffing | `detection:credential_stuffing attempts:>200 window:15m` | Environment | Env-wide list testing? | | MFA fatigue | `detection:mfa_fatigue challenges:>4 window:30m` | Subject | MFA push bombing? | | Privilege escalation | `detection:privilege_escalation` | Subject | Suspicious admin consolidation? | Some tokens change analysis windows or minimum event floors in code (`window:`, `attempts:`, `priorAccounts:`, `dormantDays:`). Others are advisory text injected into the detector prompt (`botScore:`, `distanceKm:`, `observed:`, `action:`). Treat high numeric thresholds as guidance for the model, not a hard in-batch count gate. ## Findings and deduplication When a detector returns a suspicious verdict, Authdog upserts a finding keyed by: ``` ai::: ``` Redelivery of the same analysis window refreshes the verdict instead of creating a duplicate alert. Operator triage fields (status, assignee, resolution) survive re-analysis. A single burst of auth traffic can produce multiple Signals when several detectors match different correlation keys. Triage joins on user, IP, and time, not a single combined row. ## Lidar vs audit logs vs SIEM | Surface | Role | | --- | --- | | [Audit logs](/docs/audit-logs) | Immutable record of what happened (sign-ins, role changes, admin actions) | | [Events & webhooks / SIEM](/docs/events-webhooks) | Forward that event stream to your log platform | | **Lidar** | Detect suspicious patterns in the stream and raise Signals | Audit data alone is not a threat decision. Lidar findings are derived detections; they can be wrong or incomplete if history or queue processing is degraded. Always correlate with neighboring audit events before remediating. ## Access and environment scope Lidar is included on Team, Pro, and Enterprise. Until the organization has access, the page shows a request landing rather than live protection. Signals, Scan Logs, Scan Runs, Monitors, and Posture require a selected environment and an approved Lidar grant. See [pricing](/pricing). ## Related | Read | To learn how to | | --- | --- | | [Recipes](/docs/recipes) | Combine Lidar monitors with Bot protection against a specific abuse | | [Lidar in the console](/docs/console/lidar) | Enable access, tune monitors, and triage Signals | | [Audit logs](/docs/audit-logs) | Inspect the underlying identity event trail | | [Events & webhooks](/docs/events-webhooks) | Stream events to an external SIEM | | [Security](/docs/security) | Platform security defaults and hardening | ## Console ### Console overview Source: https://www.authdog.com/docs/console/overview.md The [Authdog console](https://console.authdog.com) is where you configure identity for an application. **Organization > Tenant > Project > Environment** Almost all settings are **per environment**. Enabling a method in `dev` does not enable it in `prod`. Overview and **Dashboard** (`/dashboard/home`) show the Project ID, Environment ID, and publishable **public key** (`pk_...`) the SDK needs. Copy the public key from the Dashboard card or the environment picker. See [Environments](/docs/console/environments). ## Find your way The top bar holds the project/environment switcher, search (`user:`, `project:`), and account menu. The sidebar groups workspace, products (Authentication, Authorization, Audit, Lidar, Vault, MCP Auth, Agents, Forms), and developer settings. **MCP Auth** appears only for MCP projects. **Agents** appears for every project type. Authorization sub-items follow the environment model (RBAC, ABAC, or FGA). Selecting a project pins its first environment and opens the Dashboard. Tenant-level pages such as Organizations clear that selection. ## Related | Read | To learn how to | |------|-----------------| | [Projects](/docs/console/projects) | Create web, M2M, and MCP projects | | [Authorization](/docs/console/authorization) | Pick a model | | [Lidar](/docs/console/lidar) | Detect abuse | | [Agents](/docs/console/agents) | Register and revoke named agent identities | | [MCP servers](/docs/console/mcp) | Register an agent endpoint | | [Multi-tenancy](/docs/concepts/multi-tenancy) | Isolation model | ### Projects Source: https://www.authdog.com/docs/console/projects.md The **Projects hub** is the first page you see after selecting a tenant. It lists every project in the workspace (web applications, machine-to-machine clients, and MCP servers) and is where you create new ones. The resource hierarchy is **Organization > Tenant > Project > Environment**. Projects live inside a tenant and contain one or more environments (for example `dev` and `prod`). ## Project list The page header reads **"Projects"** with the subtitle _"Browse your projects and MCP servers in one place."_ A **"New Project"** button and a card/table view toggle sit in the top-right. A rail on the left filters by project type, each entry carrying a count badge: | Filter | Shows | |--------|-------| | **All** | Every project in the tenant. | | **Web** | Web applications (consumer or B2B sign-in). | | **M2M** | Machine-to-machine clients using the client credentials grant. | | **MCP** | Model Context Protocol servers. | Two layouts are available: - **Cards view**: one card per project with name, description, organization, environment count, and last-updated date. - **Table view**: columns for Name, Type, Description, Environments, Updated, and a per-row gear that opens project settings. Under each project name the list shows a subtitle identifying it further: the organization for web projects, `Machine-to-machine` for M2M, and the transport (e.g. `SSE transport`) for MCP servers. > **Tip** > > Use the type filters to narrow the list when a tenant holds many projects. The > count badges update as projects are added or removed. ## Create a project Click **"New Project"** (or the **"+"** button in the top navigation bar) to open the **Create application** dialog. Its subtitle explains the model: _"Applications let users and services from other systems securely access your product using their existing credentials."_ Pick an **Application type**. Selecting a card reveals what that type gives you, then click **"Next"** to fill in the details. ### Web application _"Share user identity with apps managed by you or an organization."_ - Enables user authorization across first-party and third-party applications - Secure, standardized identity federation using OAuth 2.0 and OIDC protocols - Third-party applications require explicit user consent This is the type to pick for a normal sign-in experience: a SaaS app, a customer portal, a mobile app. ### Machine-to-machine application _"Provide API credentials to your customers via client credentials flow."_ - Issues a client ID and secret for server-to-server authentication - Uses the OAuth 2.0 client credentials grant, no user interaction required - Ideal for backend services, CLIs, and automated jobs Creating an M2M project also creates an OIDC client configured for the `client_credentials` grant, so the project is usable as soon as the dialog closes. ### MCP server _"Expose tools and resources to AI agents securely."_ - Register a Model Context Protocol server for AI agents - Secure, authenticated tool and resource access - Manage scopes and access control after creation MCP projects get an extra **MCP Auth** module in the sidebar, see [MCP servers](/docs/console/mcp). ### Form fields | Field | Required | Details | |-------|----------|---------| | **Name** | Yes | Minimum 2 characters. Shown in the project list, top bar, and breadcrumbs. | | **Description** | No | Free-text summary of the project's purpose. | | **URL** | No | Must be a valid URL if provided. For MCP servers this is the transport endpoint. | ### Programmatically Use the `saveApplication` mutation to create a web or M2M project: ```graphql mutation SaveApplication($meta: SaveApplicationInput!) { saveApplication(meta: $meta) { project { id } environments { id name } meta { ok { message } error { message } } } } ``` ```json { "meta": { "tenantId": "", "name": "My Web App", "description": "Customer-facing authentication portal", "url": "https://app.example.com" } } ``` MCP servers use `saveMCPServer`: ```graphql mutation SaveMCPServer($meta: SaveMCPServerInput!) { saveMCPServer(meta: $meta) { server { id name transportType } meta { ok { message } error { message } } } } ``` ```json { "meta": { "tenantId": "", "name": "Auth Tools Server", "description": "MCP server exposing authentication tools", "serverUrl": "https://mcp.example.com/sse" } } ``` ## What happens after selecting a project Clicking a project selects it along with its pinned (or first) environment and navigates you to the **[Dashboard](/docs/console/dashboard)**. The sidebar expands from the workspace-only view into the full module list: Overview, Users, Products, and Developer. Project settings live in the gear menu at the top of the sidebar. The project type changes which modules appear, MCP projects gain **MCP Auth**, and the Authorization sub-items follow the environment's permission model. > **Warning** > > Switching projects clears your current environment selection. Save any pending > changes in a settings form before you switch. ## Project settings Rename a project, copy its ID, or delete it from **Settings > General**: see [Project settings](/docs/console/settings). The gear icon on a table row jumps straight there. ## Related | Read | To learn how to | |------|-----------------| | [Console overview](/docs/console/overview) | Understand the console layout and context switching | | [Tenants](/docs/console/tenants) | Create and manage the workspaces that contain projects | | [Environments](/docs/console/environments) | Create and switch deployment environments for a project | | [Project settings](/docs/console/settings) | Rename, inspect, or delete a project | | [Agents](/docs/console/agents) | Register named agent identities on any project type | | [MCP servers](/docs/console/mcp) | Register MCP servers and test agent auth | | [Machine identity](/docs/machine-identity) | Pick M2M, Agents, service account, or PAT | ### Authentication Source: https://www.authdog.com/docs/console/authentication.md The **Authentication** module is where you decide how people sign in to your application. Its subtitle says exactly that: _"Configure how users sign in to your application."_ Everything here is **per environment**. The rail on the left groups the tabs into three blocks, the configuration tabs, an **ACCESS** block, and a **RELATED** block linking out to Authorization, Branding, Domains, and Redirects. ## Analytics ``?tab=analytics``: sign-in volume, success and failure rates, and method mix for the selected time range. Use it to confirm a newly enabled method is being picked up before you make it the default. ## Methods ``?tab=methods``: the ways in. Each is a card with an **Enabled** / **Disabled** badge and a single button. | Method | What it is | |--------|-----------| | **Single Sign-On** | Sign in with SAML and OIDC identity providers like Entra ID, Okta, and Google Workspace. **"Manage"** opens connection setup. | | **Email + Password** | Classic credentials. The [password policy](#policies) is enforced while this is on. | | **Passkeys** | WebAuthn credentials, faster, easier, and more secure than passwords. | | **Magic Auth** | A unique six-digit code sent to the user's email address. | | **SMS** | A one-time code sent by SMS. Requires a connected SMS provider. | | **WhatsApp** | A one-time code sent on WhatsApp through the environment's Twilio connection. | > **Tip** > > Passkeys and Magic Auth both remove the password from the flow. Enabling > passkeys alongside Email + Password is the low-risk first step: existing users > keep their password, new devices get offered a passkey. ## Providers ``?tab=providers``: the connection catalog, titled **"Connections"**: _"Configure external identity providers and social logins."_ Counters at the top show how many are **Available** versus **Configured**. Filter by protocol with the **OAuth 2.0**, **Enterprise**, **SAML 2.0**, and **OpenID Connect** tabs, then page through the catalog (20 per page by default, adjustable to 10/20/50/100). Over sixty providers ship in the catalog, Apple, Amazon, AWS, Bitbucket, Box, Coinbase, Discord, DigitalOcean, DocuSign, Dropbox, and many more, including identity-verification providers such as 1Kosmos BlockID and Asignio. Click **"Enable"** on a provider to enter its client ID, client secret, and any provider-specific fields. Enterprise providers (Okta, Entra ID, JumpCloud, Ping, BeyondTrust) additionally accept metadata URLs and domain routing so a user's email domain picks the right connection. ## Features ``?tab=features``: capabilities layered on top of the methods. | Feature | What it controls | |---------|-----------------| | **Hosted UI** | Whether users sign in through Authdog's customizable hosted pages instead of your own screens. | | **Sign-up** | Whether users can create their own accounts. This does not let **Sign in** create the first account. That rule is [Require signup for first login](/docs/console/users#require-signup-for-first-login) on Users → Settings. | | **Invitations** | The expiry period for invitations to join an organization (default: 7 days). | | **Multi-factor auth** | Requires non-SSO users to set up MFA to sign in. | | **Localization** | Shows the hosted UI and auth emails in the user's browser language, with a configurable fallback (default: English (US)). | | **User impersonation** | Lets dashboard team members sign in as any user of your app. Gated, contact Authdog to enable. | | **JWT template** | Augments session tokens with custom metadata. See [JWT Claims](#jwt-claims). | | **Trusted domain reminder** | Shows _"Only sign in if you recognize this address"_ with the identity host URL on hosted sign-in and sign-up pages. | ## Add-ons ``?tab=addons``: third-party integrations that hook into the authentication flow. _"If you're interested in using an add-on not listed here, contact us."_ Bot-protection add-ons are documented in full under [Bot protection](/docs/bot-protection). | Add-on | What it does | |--------|-------------| | **Google Analytics** | Attributes sign-ups to traffic sources and surfaces drop-offs during authentication. | | **Segment** | Sends hosted authentication events to your Segment destinations. | | **Stripe** | Provisions access tokens with entitlements and syncs seat counts for per-seat billing. | | **Google reCAPTCHA** | Protects hosted auth pages against bots and abuse, sign-in, sign-up, waitlist, password reset, magic link, one-time codes, and MFA. | | **Cloudflare Turnstile** | Privacy-friendly alternative to reCAPTCHA. Managed or non-interactive widgets on sign-in, sign-up, waitlist, and password reset; invisible mode can cover every flow. | The Stripe connection configured here is the same one [Billing](/docs/console/billing) uses for plans and subscriptions. ## Clients ``?tab=clients``: the **OpenID clients** registered for this environment. The table lists Name, Client ID (truncated), Environment, MCP Server, Scopes, Created date, and row actions. Click **"Create client"** to register a new one and pick its grant types, redirect URIs, and scopes (`openid`, `profile`, `email`, and any custom scopes you define). M2M projects get a `client_credentials` client automatically at creation; MCP servers get one bound to the server. > **Caution** > > Client secrets are displayed once at creation. Store them in your secret > manager (or in [Vault](/docs/console/vault)) before closing the dialog. ## Sessions ``?tab=sessions``: **Session Management**: _"Configure signing keys and JWT settings for your application."_ See [Sessions](/docs/console/sessions) for the full walkthrough of key rotation. In short: three counters (Total Keys, Keys Used Today, Revoked Keys). A **Signin Keys** table lists each key's ID, type, use, algorithm, status, and dates with **"Use Key"** / **"Revoke"** actions. A **JWT Settings** card holds **Token Lifetime** and **Clock Skew**, and a live **Claims Preview** shows the token body. ## JWT Claims ``?tab=jwt-claims``: map custom claims into the access tokens issued at sign-in. You edit a JSON object where each key is the claim name written into the token and each value is its source. The editor validates as you type and reports, for example, _"1 claim valid and ready to save."_ Available sources: | Source | Yields | |--------|--------| | `roles` | The user's role slugs, direct plus inherited via groups. | | `groups` | The user's group slugs. | | `email` | Primary email address. | | `displayName` | Display name. | | `givenName` / `familyName` | First and last name. | | `username` | Username. | | `externalId` | Stable external user id. | | `metadata` | A value from user metadata, requires a `"key"`. | | `static` | A fixed literal, `{ "source": "static", "value": "..." }`. | Use a source as a bare string, or as an object when it takes options: ```json { "roles": "roles", "dept": { "source": "metadata", "key": "dept" }, "tier": { "source": "static", "value": "pro" } } ``` Click **"Save claims"** to apply. New claims appear in tokens issued from the next sign-in onward, existing tokens are not rewritten. ## Restrictions ``?tab=restrictions``: who is allowed to get in at all. **Sign-up mode** is a three-way choice: | Mode | Behaviour | |------|-----------| | **Public** | Anyone can sign up. | | **Restricted** | Sign-ups disabled. Access only via invite, manual creation, or enterprise SSO. | | **Waitlist** | Sign-ups disabled, but people can join a waitlist. | **Email restrictions** control which addresses qualify, via an **Allowlist** (only these domains may sign in) or a **Blocklist** (these domains may not). **Additional email restrictions** adds two rules: - **Block email subaddresses**: addresses containing `+`, `=`, or `#` are allowed, but only one distinct subaddress per base email. Once `email+one@domain.com` is used, `email+two@domain.com` is blocked for sign-up and for adding to an existing account. - **Block sign-ups that use disposable email addresses**: rejects known throwaway domains. > **Tip** > > An allowlist is the right default for enterprise deployments: it guarantees > only your customer's domains can reach the app, regardless of sign-up mode. ## Policies ``?tab=policies``: the enforcement rules, across seven sub-tabs: **Password**, **Countries**, **Rate limiting**, **Bot detection**, **Brute force**, **Breached passwords**, and **Device risk**. **Password policy** is enforced whenever Email + Password is enabled. Set a **Minimum length** (recommended: 10–14 characters) and require any of **Lowercase**, **Uppercase**, **Number**, and **Symbol**. A **"Test a password"** box evaluates a candidate against the current settings (including unsaved edits) and shows which requirement each character class satisfies. The remaining sub-tabs set thresholds for geographic restrictions, request rate limits, bot scoring, brute-force lockout, breached-credential rejection, and device-risk challenges. The detections they drive surface as signals in [Lidar](/docs/console/lidar). ## Related | Read | To learn how to | |------|-----------------| | [Users](/docs/console/users) | Manage the users these settings apply to | | [Sessions](/docs/console/sessions) | Rotate signing keys and tune token lifetimes | | [Authorization](/docs/console/authorization) | Decide what users may do once signed in | | [Branding](/docs/console/branding) | Style the hosted sign-in experience | | [Lidar](/docs/console/lidar) | Triage the detections these policies produce | | [SSO](/docs/sso) | Set up enterprise SAML and OIDC connections | ### Sessions Source: https://www.authdog.com/docs/console/sessions.md The **Sessions** page configures how JSON Web Tokens (JWTs) are issued for the current environment. Manage signing keys, adjust token lifetime and clock skew, and preview the claims structure, all from **Authentication > Sessions** (`/dashboard/authentication?tab=sessions`). ## Signing keys Signing keys are the JSON Web Keys (JWKs) used to sign access tokens for your environment. The page displays three stats cards summarizing key status: | Card | Icon | Description | |------|------|-------------| | **Total Keys** | Key (blue) | The total number of signing keys created in this environment. | | **Keys Used Today** | CheckCircle2 (green) | The number of keys currently active and used to sign new tokens. | | **Revoked Keys** | Ban (red) | The number of keys that are no longer active. | Below the stats cards, a table lists all signing keys with the following columns: | Column | Details | |--------|---------| | **Key ID** | The first segment of the key UUID, rendered in monospace text. | | **Type** | Key type (`kty`): e.g. `RSA` or `OKP`. | | **Use** | Key usage: always `sig` (signature). | | **Algorithm** | The signing algorithm (e.g. `RS256`, `Ed25519`), shown as an outlined badge. | | **Status** | A badge indicating the key state: **"In use"** (green), **"Inactive"** (neutral), or **"Revoked"** (red). | | **Dates** | Created, updated, and revoked timestamps. | | **Actions** | Inline action buttons (only shown for inactive keys). | ### Create a signing key Click **"New JSON Web Key"** in the card header to open the creation dialog. Select the desired algorithm from the dropdown (default: RS256) and click **"Save Key"**. The new key is created in an **Inactive** state. ### Activate a key Click **"Use Key"** on an inactive key row. Only active keys are used to sign new tokens. Keep multiple active keys to support key rotation. ### Revoke a key Click **"Revoke"** (destructive style) on an inactive key row. Revoked keys remain listed for auditing but are never used to sign new tokens. Tokens previously signed with a revoked key can still be verified using the public key from the JWKS endpoint until they expire. > **Caution** > > Revoking a key is permanent. Once revoked, a key cannot be reactivated. > Ensure you have at least one active key before revoking others. ### Rotate keys without downtime Rotation works because verification and signing are separate concerns: the JWKS endpoint publishes every non-revoked public key, while only active keys sign new tokens. So: 1. **Create** a new key. It starts **Inactive**: published for verification, not yet signing. 2. Wait for your consumers to refresh their cached JWKS. 3. **Use Key** on the new key so it starts signing. 4. Leave the old key inactive until every token it signed has expired (at least one **Token Lifetime**). 5. **Revoke** the old key. Skipping step 4 rejects tokens that are still legitimately in flight. ## JWT settings The JWT Settings section controls how tokens are issued for the current environment. Two range sliders let you configure: | Setting | Range | Default | Description | |---------|-------|---------|-------------| | **Token Lifetime** | 0–100,000 seconds | 3,600 seconds (1 hour) | How long an access token remains valid after it is issued. | | **Allowed Clock Skew** | 0–600 seconds | 60 seconds | The tolerance window for clock differences between the issuer and the verifier. Helps avoid token rejection due to minor time drift. | The current value in seconds is displayed below each slider. Adjust the sliders and click **"Save Settings"** to apply changes. The button is disabled until you change a value. The new settings take effect for all tokens issued after saving. > **Tip** > > A token lifetime of 3,600 seconds (1 hour) is a reasonable default for most applications. > Reduce it for high-security environments or increase it for long-lived sessions. ## Claims preview The claims preview panel shows a live JSON representation of the JWT payload structure for the current environment. It is displayed in a dark-themed code block alongside the JWT settings form. The preview dynamically reflects the current token lifetime value as you adjust the slider. ```json { "root": { "iss": "https://id.authdog.com", "iat": "{{ ISSUED_TIME }}", "exp": "{{ ISSUED_TIME + tokenLifetime }}", "groups": "{{user.groups}}", "sub": "{{user.externalId}}:{{user.environmentId}}" } } ``` Use this preview to verify the shape and expiry of tokens your application will receive before saving changes. ## Supported algorithms | Algorithm | Key Type | Description | |-----------|----------|-------------| | **RS256** | RSA | RSASSA-PKCS1-v1_5 using SHA-256 | | **RS384** | RSA | RSASSA-PKCS1-v1_5 using SHA-384 | | **RS512** | RSA | RSASSA-PKCS1-v1_5 using SHA-512 | | **PS256** | RSA | RSASSA-PSS using SHA-256 | | **PS384** | RSA | RSASSA-PSS using SHA-384 | | **PS512** | RSA | RSASSA-PSS using SHA-512 | | **Ed25519** | OKP | Edwards-curve Digital Signature Algorithm | Ed25519 keys are advertised as `EdDSA` in the OIDC discovery document, which is the algorithm identifier JWT libraries expect. ## Adding your own claims The preview above shows the claims Authdog issues by default. To add your own (roles, groups, metadata values, or static literals) use **[Authentication > JWT Claims](/docs/console/authentication)**, which writes them into the same token. ## Related | Read | To learn how to | |------|-----------------| | [Authentication](/docs/console/authentication) | Reach this tab and configure custom JWT claims | | [Environments](/docs/console/environments) | Manage deployment environments that scope session configuration | | [Sessions & tokens](/docs/concepts/sessions-tokens) | How issued sessions are validated in your app | | [JWT claims](/docs/jwt-claims) | Map custom claims into issued tokens | | [Backend requests](/docs/backend) | Verify tokens against the JWKS endpoint | ### Authorization Source: https://www.authdog.com/docs/console/authorization.md **Authorization** decides what a signed-in identity may do in this environment. Pick **one** model. The other models' config is kept if you switch. ## Models - **RBAC** (default) — roles carry CRUD on resources. Groups inherit roles. - **ABAC** — Rego policies over user, resource, and environment attributes. - **FGA** — relationship tuples, Zanzibar-style. Managed in [Fine-Grained Authorization](/docs/console/fga). Start with RBAC. Move to ABAC when the decision depends on request context. Move to FGA when permissions inherit through a graph. The rail changes with the model: RBAC shows Resources / Roles / Groups. ABAC shows Policies. FGA lists Getting Started, Model Explorer, Tuple Management, Developer Mode, Permissions Index, Logs, and Store Settings. Those open [Fine-Grained Authorization](/docs/console/fga). Choosing FGA opens Getting Started. An **FGA** item also appears under Identity. ## From the app The check does not change when you switch models. Use the AuthZEN decision API or the SDK. See [Roles & permissions](/docs/permissions), [FGA](/docs/fga), and [AuthZEN](/docs/authzen). ## Related | Read | To learn how to | |------|-----------------| | [Authorization concepts](/docs/concepts/authorization) | Compare the three models | | [Fine-Grained Authorization](/docs/console/fga) | Model, tuples, assertions, logs | | [Users](/docs/console/users) | Assign roles and groups | | [Audit](/docs/console/audit) | Review access decisions | ### Audit Source: https://www.authdog.com/docs/console/audit.md The **Audit** module is the searchable record of everything that happened in an environment: _"Summaries, searchable events, and type breakdowns for this environment."_ Audit uses the **time range** picker in the top bar, the selected window is reflected in the URL as `&rt=24h`, so a filtered view is a shareable link. Two controls sit in the top-right of every tab: **Saved filters** and **Filters**. ## Overview ``?tab=overview``: the summary tab. **Activity** counters, each with a percentage change against the previous window: | Counter | Meaning | |---------|---------| | **Total Events** | All events recorded in the window. | | **Actors** | Distinct users and services that caused events. | | **Signals** | Security detections raised in the window (see [Lidar](/docs/console/lidar)). | | **Event Types** | How many distinct types fired. | Below that, an **Activity timeline** charts **Events** and **Signals** across the window. Quiet environments read _"No audit activity in current time range."_ ## Custom actions ``?tab=actions``: register a business event name for this environment so it appears in the catalog before the first event. **Event Types** lists the name under **custom** with a count of 0 until a real event is recorded. The four built-in categories stay as they are. **custom** appears only for names registered here. | Field | Rule | |-------|------| | **Name** | 2–64 characters. Starts with a letter. A–Z, digits, and underscores. Spaces and hyphens become underscores, and the stored value is uppercase. `invoice paid` and `invoice-paid` both become `INVOICE_PAID`. | | **Description** | Optional. Up to 280 characters. Saving the same name again updates the description. | | **Add** | Registers the name, or updates the description when the name already exists. | | **Remove** | Deletes the registration. Events already recorded stay in the log. If none were recorded, the name leaves the catalog. | To write an event, call `POST /v1/events/audit` with an API credential for this environment. A name that is not registered is rejected. The body is `action`, plus optional `external_id` and `payload`. Authdog sets `origin` to `api` and `channel` to `api.audit_action` on the stored payload, replacing any caller values. See [Audit logs](/docs/audit-logs#record-a-custom-action). > **Tip** > > Register the name before you ship the producer. Filters, webhooks, and Event Types can target it while the count is still 0. ## Events Explore ``?tab=events``: _"Browse events for the selected time range."_ The event table, with a count badge showing how many matched. Use **Filters** to narrow by event type, actor, category, or outcome, and **Saved filters** to keep an investigation you run often. Rows are selectable and the table pages at 25 rows by default. This is the tab the Dashboard's **"View all activity"** link lands on, carrying the same time range across. ## Event Types ``?tab=types``: _"Explore event types, inspect recent samples, and infer their schema from observed payloads."_ Header counters show how many **types** and **categories** exist and how many **usage events** were recorded. Types are grouped by category. The built-in catalogue has 22 types across four categories. Names you register on [Custom actions](#custom-actions) add a **custom** category. | Category | Covers | |----------|--------| | **Administration** | Impersonation and Elevate lifecycle: `USER_IMPERSONATION_STARTED`, `USER_IMPERSONATION_GRANT_CREATED`, `USER_IMPERSONATION_GRANT_REVOKED`, and `ELEVATE_ACCESS_REQUESTED` / `APPROVED` / `DENIED` / `CANCELLED` / `ACTIVATED` / `REVOKED` / `EXPIRED`. | | **Authn** | Sign-in, sign-up, and session lifecycle. | | **Security** | Threat detections. | | **Team Management** | Console team and invite changes. | Each type row shows its recent activity and event count. Selecting one opens the **Schema explorer** on the right, which has two views: - **Schema**: an inferred prototype merged from up to 20 observed payload samples. - **Payload**: a recent sample. Before any events arrive the explorer explains that the schema is inferred once events exist, and shows an example payload so you know the webhook body shape you will receive. > **Tip** > > Use Event Types as the reference when building a webhook consumer: the inferred > schema is derived from your own traffic, so it reflects the fields you will > actually get rather than a generic spec. ## Forwarding events Audit events do not have to live only in the console. Configure a channel under [Notifications > Channels](/docs/console/notifications) to forward them to a webhook, Slack, Datadog, Sysdig, Splunk HEC, or Sumo Logic. Delivery is retried durably, so a brief outage at the destination does not lose events. ## Related | Read | To learn how to | |------|-----------------| | [Dashboard](/docs/console/dashboard) | See the live event log feeding this history | | [Lidar](/docs/console/lidar) | Investigate the security signals counted here | | [Notifications](/docs/console/notifications) | Stream events to a SIEM or webhook | | [Users](/docs/console/users) | Trace an event back to the user who caused it | | [Audit logs](/docs/audit-logs) | Query audit events over the API | | [Events & webhooks](/docs/events-webhooks) | Consume events in your own systems | ### Lidar Source: https://www.authdog.com/docs/console/lidar.md **Lidar** is Authdog's identity SIEM. Open it from the sidebar (`/dashboard/security` — the route is `security`, the label is Lidar). The console subtitle is *"Threat detection, monitoring, and security intelligence for your application"*. For the model, see [Lidar concepts](/docs/concepts/lidar). Per environment. A selected project and environment are required. Lidar is a paid-plan feature (Team, Pro, Enterprise; feature slug `lidar`). Until the organization has the grant, the page shows a locked landing — *"Protect against bots, fraud, and abuse"* — with **Request access**. That landing is not a tab. After approval, the default tab is **Signals**. See [pricing](/pricing). Optional [notification channels](/docs/events-webhooks) copy the same trail to another SIEM if you already run one. ## Signals `?tab=signals` (default after approval). Live findings for the selected environment. Time range comes from the top bar. Columns: Time, Severity, Signal, User, IP, Rule (`lidar.`). Facets: Severity, Detection type, Origin, Synthetic, Country, Client IP, User. **Generate events** (dev) and **Clear synthetics** exercise the pipeline with console fixtures. Label those rows synthetic. Do not use them in production screenshots. ## Scan Logs `?tab=scan-logs`. Analysis-run history for the environment: what the pipeline executed, raw events, and flags. ## Scan Runs `?tab=scan-runs`. Detector sweep history (queue, console analyze, or sweep). Expand a run for per-detector status and severity. ## Monitors `?tab=monitors`. Detector catalogue. Enable or disable each monitor, edit the `detection:` query, set severity. Disabled monitors produce no new Signals. Default monitors cover brute force, credential stuffing, impossible travel, MFA fatigue, bots, breached passwords, unrecognized devices, stale accounts, repeat signup, rate-limit spikes, and privilege changes. Query tokens: `detection:` (required), `window:`, `attempts:`, `priorAccounts:`, `challenges:`, `dormantDays:`, `botScore:`, `distanceKm:`, `observed:`, `action:`. ## Posture `?tab=posture`. Configuration posture for this environment — not behavioral detection. Score 0–100 plus category subscores (Authentication, Access Control, Secrets & Keys, Threat Protection, Monitoring). Findings are deterministic checks over the environment's own settings (password policy, redirect URIs, JWKS, rate limiting, bot/breach add-ons, webhook signing, Elevate MFA, audit forwarding). **Rescan** writes a history row. **Acknowledge** mutes a finding. No external cloud connectors. This is Authdog-config posture only. ## Device Intelligence `?tab=device-intelligence` on the same security page. Score a browser, then map that score to allow, challenge, or block. Two thresholds set the floor. Extra rules can only raise the verdict (allow to challenge, or either to block). A rule cannot turn a block into a challenge or an allow. The same card lists known devices for the environment: user, label, browser, country, and last seen. That list does not include the fingerprint hash or the signal sketch. ## Limits - Access requires a Team, Pro, or Enterprise plan (or an approved grant) before Signals, Scan Logs, Scan Runs, Monitors, and Posture are usable. - Breached-password *policy* in [Authentication](/docs/console/authentication) is a separate control from the breached-password monitor. - There is no Dashboards tab. Severity over time lives on Signals. ## Related | Read | To learn how to | |------|-----------------| | [Lidar concepts](/docs/concepts/lidar) | How monitors become Signals | | [Recipes](/docs/recipes) | Pair Lidar with bot protection | | [Events & webhooks](/docs/events-webhooks) | Optional export to another SIEM | | [Agents](/docs/console/agents) | MCP tool-abuse and rug-pull Signals start from agent identity | | [Support Center](/docs/console/support) | How gated-feature requests are reviewed | ### Agents Source: https://www.authdog.com/docs/console/agents.md **Agents** is the environment registry of AI agents and other non-human identities that call your APIs and MCP servers. Open it from the sidebar (`/dashboard/agents`). The console subtitle is *"Register, authorize, and audit the AI agents and machine identities calling your APIs and MCP servers"*. Agents is self-serve (feature slug `agents`) in every environment. The default tab is **Agents**. Usage is bounded by your plan's agent-run quota, not by an access request. Per environment. A selected project and environment are required. Creating an agent in `dev` does not register it in `prod`. This is not a new project type. It sits on top of the environment's trust store: each row is a verified subject (usually an OIDC client ID, a SPIFFE ID, or a URI) with scoped tools, keys, and a revoke switch. For when to use an Agents entry versus an M2M project or a service account, see [Machine identity](/docs/machine-identity). ## Agents The **Agents** tab is the default. It lists trust-store entries. The **Agents** filter shows `identityType=agent` only; **All identities** also shows `mcp_server` and `workload` rows. | Control | Details | |---------|---------| | **New agent** | Opens the register wizard. | | **Verify** | Marks an unverified row verified (`manual_console`). | | **Revoke** | Issuer-side kill switch when the subject is the OIDC `client_id`: **new** tokens fail. SPIFFE / URI subjects are registry + AuthZEN only. Outstanding JWTs last until `expires_in` unless the resource server runs trust-store **enforce**. | | Delete | Removes the entry. Prefer revoke if you may need the record. | Empty: *"No agents registered yet in this environment."* with **Register your first agent**. ### Register an identity The wizard starts with two cards: | Mode | What it does | |------|----------------| | **Create new agent identity** | Provisions an OIDC client (`client_credentials`, optional RFC 8693 token-exchange and identity assertions), then registers a trust-store entry whose subject is the new client ID. The client secret is shown once, on onboard and again when you rotate it. Dynamic registration does not return a client secret. | | **Register existing identity** | Registers a subject that already authenticates elsewhere: an existing client ID, a `spiffe://` ID, or a URI. | Both modes set a name, optional description, allowed scopes, and allowed tools (globs; `*` means all tools). You can verify immediately. Click a row to open the detail sheet: name, description, active flag, allowed scopes and tools, keys (key-challenge, a locally parsed X.509 certificate, or SPIFFE JWT-SVID verify; bearer tokens still use the environment JWKS), manifest pin (SHA-256 of the expected tool list; Lidar `mcp_rug_pull` fires when a live `tools/list` drifts), **Governance** (owner, expiry, suspend, and rotate client secret — the new secret is shown once), and **Delegation** (grant / revoke RFC 8693 token-exchange; nested `act` chain; never impersonation; optional identity-assertion grant). An expired or unparseable certificate does not verify the entry. Certificate checks do not fetch a URL. ## Activity The **Activity** tab is the environment audit for MCP runtime (`mcp_runtime`): tool invoked, denied, failed, auth denied, and trust violation. Filter by subject. Open Activity from an agent's detail sheet and the list is already filtered to that subject. ## Origins The **Origins** tab is a fail-closed allowlist of audience URLs that may appear as `aud` on an identity assertion. An empty list means the grant mints nothing. Origins validate via `/.well-known/authdog-identity-assertion` (issuer, JWKS, 60s lifetime, no refresh). Device authorization is not part of this slice. ## Related | Read | To learn how to | |------|-----------------| | [AgentSec](/docs/agentsec) | The umbrella: agent authentication + access control | | [Agent Sign-In](/docs/agent-sign-in) | Agents signing into your app with a revocable identity | | [Agent frameworks](/docs/agent-frameworks) | Put the agent bearer on A2A, Eve, MCP, LangGraph, or the OpenAI Agents SDK | | [Agents quickstart](/docs/quickstarts/agents) | Token exchange for a new agent | | [Support](/docs/console/support) | Ask a question or report a problem | | [Machine identity](/docs/machine-identity) | Pick M2M, Agents, service account, or PAT | | [MCP servers](/docs/console/mcp) | Front an MCP server and test in the playground | | [Authorization](/docs/console/authorization) | Scope what an agent may do after it authenticates | | [Lidar](/docs/console/lidar) | Detect tool abuse, injection, and rug-pulls | | [Audit](/docs/console/audit) | Broader event trail beyond `mcp_runtime` | ### MCP servers Source: https://www.authdog.com/docs/console/mcp.md An **MCP server** is a project type for Model Context Protocol endpoints. Agents must authenticate before they can call a tool. Create one from [Projects](/docs/console/projects) by choosing **MCP server**. MCP Auth appears in the sidebar only for that project type. **Three MCP meanings in Authdog:** an **MCP project** (this doc) is your customer’s tool server and OAuth resource; the **Agents** module is the machine-identity registry and trust store for non-human callers; the **Management API MCP** is Authdog’s own control-plane tools for operators — not your product’s MCP server. ## Settings Open **MCP Auth → Settings** to enable MCP Auth for the environment, set the default resource (RFC 8707), and turn on strict resource mode. See [MCP authentication](/docs/mcp-authentication) for the authorize and token flow. ## Playground Open **MCP Auth → Playground**. Connect with SSO to mint a short-lived bearer for the environment, then import tools. Tokens are scoped to the selected environment — use `dev` while iterating. A successful playground call proves **this console session** can invoke **that** tool on **that** server. It does not authorize every agent identity in the trust store. Register those in [Agents](/docs/console/agents). ## After registration | Job | Where | |-----|-------| | Clients and scopes | [Authentication](/docs/console/authentication) | | Which agents may call this server | [Agents](/docs/console/agents) | | Which tools an agent may call | [Authorization](/docs/console/authorization) | | Call history | [Agents](/docs/console/agents) activity, or [Audit](/docs/console/audit) | | Abuse on the auth surface | [Lidar](/docs/console/lidar) | | Server credentials | [Vault](/docs/console/vault) | Each project can hold several environments, so staging tools never resolve in production. ## Controls in `@authdog/mcp-sdk` These run inside your server. Each is opt-in. - **Trust store** — verified non-human identities from [Agents](/docs/console/agents). Issuer revoke blocks **new** tokens when the subject is the OIDC `client_id`. Outstanding JWTs stay valid until expiry unless this control is in **enforce**. - **Per-tool authorization** — AuthZEN check before the tool runs. - **Audit** — invocation, denial, failure. No raw arguments. - **Threat detection** — Lidar signals for poisoning, injection, exfil, rate abuse. Happy path is **enforce**. Use `off` / `monitor` only while staging. See [MCP security](/guides/mcp-security) and [machine identity](/docs/machine-identity). The runtime guard is the private `packages/mcp-sdk` in the Authdog monorepo and is not on npm. The npm package `@authdog/mcp-sdk` is the Node.js token verifier, a different package. See [MCP SDK](/docs/mcp-sdk). ## Related | Read | To learn how to | |------|-----------------| | [Projects](/docs/console/projects) | Register an MCP server | | [Authorization](/docs/console/authorization) | Scope tools | | [Agents](/docs/console/agents) | Register, verify, and revoke agent identities | | [Lidar](/docs/console/lidar) | Detect abuse | | [Machine identity](/docs/machine-identity) | Pick M2M, Agents, service account, or PAT | ## Learning ### Quickstarts Source: https://www.authdog.com/docs/quickstarts.md A quickstart takes you from an empty project to a working sign-in in about five minutes. You create an environment, copy its public key, install the SDK for your stack, and add a hosted flow or a set of components. Authdog handles the credentials. You validate the session it issues. ## What a quickstart gives you Every integration uses the same session model. A user signs in against your [environment](/docs/concepts/multi-tenancy), and Authdog issues a **session**: a signed JWT, delivered as the `authdog-session` cookie (you can also send it as `Authorization: Bearer `). Your app never touches the raw credential. It validates that session against the identity provider's `userinfo` endpoint, using the environment's public key (`pk_...`). ## The three primitives Whatever the framework, an integration is three pieces: - **A session resolver**: reads the `authdog-session` cookie (or Bearer token), verifies it, and exposes the current user to your code. - **A protected-route gate**: the server-side enforcement point that rejects unauthenticated requests with a `401` and lets authenticated ones through. - **A sign-out handler**: clears the session cookie and redirects back to your app. Frontend SDKs wrap these in hooks and route guards. Backend SDKs wrap them in middleware. The model is in [Sessions and tokens](/docs/concepts/sessions-tokens). ## The five-minute path 1. In the [console](/docs/console/tenants), create a **tenant**, a **project**, and an **environment** (for example `dev`). 2. Copy the environment's **public key** (`pk_...`). That's all your app needs to validate sessions. 3. Install the SDK for your stack (below) and configure it with the public key and your API base URL (`NEXT_PUBLIC_API_ENDPOINT`). 4. Add the UI: the hosted [Account portal](/docs/account-portal) if you don't want to build screens, or [components](/docs/components) if you want them inside your own pages. 5. Protect a route with the gate, and confirm the session resolves. ## Pick your stack **Frontend**: sign users in and manage the session in the browser: - Next.js, React, Remix, SvelteKit, TanStack Start - Vue / Nuxt, Angular, Astro, Gatsby, RedwoodJS - React Native / Expo **Backend**: validate sessions and call the REST API from your server: - Node (Express, Fastify) - Python (FastAPI, Django, Flask, Starlette, aiohttp) - Go, Rust Each SDK exposes the same three primitives, with bindings that match the framework. There are five-minute paths for [Next.js](/docs/quickstarts?sdk=nextjs), [React](/docs/quickstarts?sdk=react), [Express](/docs/quickstarts?sdk=express), [FastAPI](/docs/quickstarts?sdk=fastapi), [Django](/docs/quickstarts?sdk=django), [Flask](/docs/quickstarts?sdk=flask), [Starlette](/docs/quickstarts?sdk=starlette), [aiohttp](/docs/quickstarts?sdk=aiohttp), and [Go](/docs/quickstarts?sdk=go). Agent workloads use the [Agents quickstart](/docs/quickstarts/agents) from any environment. Other stacks use the [framework guides](/docs/guides/frameworks). ## Next steps | Read | To learn how to | | --- | --- | | [Guides](/docs/guides) | Follow a task: multi-tenant apps, SSO, provisioning | | [Backend requests](/docs/backend) | Verify sessions and protect an API | | [Components](/docs/components) | Drop the UI into your own pages | ### Agents quickstart Source: https://www.authdog.com/docs/quickstarts/agents.md Outcome: an agent identity that can mint a `client_credentials` token and call one protected route. About five minutes from an empty environment. **Self-serve.** [Agents](/docs/console/agents) is open in every environment. Usage counts against your plan's agent-run quota. Do not put a human cookie or PAT in the agent runtime. ## 0. Prerequisites - An Authdog tenant, project, and environment. - A token endpoint from the environment OIDC metadata (`/oauth2/token`). ## 1. Register an agent In the console, open **Agents** (`/dashboard/agents`): 1. **New agent** → **Create new agent identity**. 2. Name it. Give it the `mcp:invoke` scope. Optional: allowed tools (`*` or globs). 3. Copy the client ID and client secret. The secret is shown once. This provisions a confidential OIDC client and a trust-store row whose subject is that client ID. REST equivalent (after you have a management token): ``` POST /v1/tenants/{tenantId}/environments/{environmentId}/mcp/trust-store ``` The wizard is the path that also creates the OIDC client. REST registers a subject that already authenticates. ## 2. Exchange credentials for a token Ask for the `mcp:invoke` scope and name the MCP server as the `resource`. The resource must be an `https` URL. With both, Authdog mints a signed JWT whose audience is that server. Without them the token is opaque and carries no audience. ```bash curl -X POST "$TOKEN_ENDPOINT" \ -u "$AUTHDOG_CLIENT_ID:$AUTHDOG_CLIENT_SECRET" \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "grant_type=client_credentials" \ --data-urlencode "scope=mcp:invoke" \ --data-urlencode "resource=https://mcp.example.com" ``` You get `access_token`, `token_type`, `expires_in`. No refresh token. Repeat the exchange before expiry. A revoked agent whose subject is this `client_id` gets **401** on this call. Tokens already issued stay valid until `expires_in` unless the resource server runs trust-store **enforce**. ## 3. Call a protected route ```http Authorization: Bearer ``` For an HTTP API, use a backend SDK `require_auth` gate ([Express](/docs/quickstarts?sdk=express), [FastAPI](/docs/quickstarts?sdk=fastapi)). For an MCP server in Node.js, install the verifier and the MCP SDK: ```bash npm install @authdog/mcp-sdk @modelcontextprotocol/sdk express ``` Then guard the `/mcp` route. Setting `issuer` verifies the JWT locally against your environment's keys, and `audience` rejects tokens minted for another server: ```ts import express from "express" import { requireBearerAuth } from "@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js" import { AuthdogVerifier } from "@authdog/mcp-sdk" import { createMcpTokenVerifier } from "@authdog/mcp-sdk/mcp" const authdog = new AuthdogVerifier({ baseUrl: "https://api.authdog.com", issuer: "https://api.authdog.com/oidc/", audience: "https://mcp.example.com", }) const app = express() app.use( "/mcp", requireBearerAuth({ verifier: createMcpTokenVerifier(authdog, { resource: "https://mcp.example.com", }), requiredScopes: ["mcp:invoke"], resourceMetadataUrl: "https://mcp.example.com/.well-known/oauth-protected-resource", expectedResource: "https://mcp.example.com", }), ) app.listen(3000) ``` See the [Node.js SDK](/docs/mcp-sdk/node) for every option. To also enforce allowed tools and per-tool decisions, add the private runtime guard described in [MCP SDK](/docs/mcp-sdk#mcp-runtime-guard), or run `examples/mcp-auth-sample` with `MCP_TRUST_STORE_MODE=enforce` and `MCP_AUTHZ_MODE=enforce`. [Agent frameworks](/docs/agent-frameworks) puts this same bearer on the hook that framework already exposes. ## 4. Verify - Token exchange without credentials → 401. - `GET` the protected route without a bearer → 401. - With the bearer → the caller is the agent, not a human session. ## Next - [Agent frameworks](/docs/agent-frameworks): the hook for each framework, including the ones whose samples are still planned. - [Agents](/docs/console/agents): verify, revoke, pin, activity. - [Machine identity](/docs/machine-identity): M2M project vs Agents vs service account vs PAT. - [AI agent security](/guides/ai-agent-security): same primitives, longer form. - [MCP security](/guides/mcp-security): protect your MCP server. ### Guides Source: https://www.authdog.com/docs/guides.md Guides pick up after the first sign-in works: enterprise SSO, an authorization model, hardening for production. Each one starts from a job you already have. If you don't have sign-in yet, start with a [quickstart](/docs/quickstarts). ## Integrate your framework Use the [Framework guides](/docs/guides/frameworks) to add Authdog to the 16 frontend and backend stacks we support. Each guide covers install, the callback, how the session behaves, and how that framework protects a route. ## Structure multi-tenant workspaces Use the platform hierarchy ([Organization](/docs/concepts/organizations) → [Tenant](/docs/console/tenants) → Project → Environment) so billing, admin memberships, and deployment stages stay apart. Create an organization when you need one billing umbrella. Use separate tenants for products or clients that shouldn't share projects or teams. See [Multi-tenancy](/docs/concepts/multi-tenancy). ## Onboard enterprise SSO Let a customer sign in with their own identity provider over SAML 2.0 or OIDC. Okta, Microsoft Entra ID, JumpCloud, Ping Identity, and BeyondTrust are connections you configure per environment. [Enterprise SSO](/docs/sso) walks through the metadata exchange, domain routing, and how to test a connection before you hand it to the customer's IT team. ## Provision users automatically Keep your user store in step with a customer's directory using [provisioning](/docs/concepts/provisioning). Set up SCIM 2.0 (`/v1/scim/v2`, tokens prefixed `adscim_`) or the HRIS connector (`/v1/hris/v1`, tokens prefixed `adhris_`), then map identity-provider or HRIS groups to roles. Membership changes grant and revoke access without a manual step. ## Choose an authorization model Every environment uses exactly one of three models. Pick the one that matches how your access rules are shaped: - **RBAC**: roles grant permissions; best for seat-based, role-driven apps. - **ABAC**: attribute policies written in Rego, evaluated at the edge. - **FGA**: Zanzibar-style relationship tuples for fine-grained, per-object access. [Authorization](/docs/concepts/authorization) compares the trade-offs so you can pick one and stay with it. ## Harden for production Before you ship, walk [Securing your app](/docs/security) and match each threat to a control. Use a separate environment per stage (dev, staging, production) so keys and connections never cross. Rotate signing keys on a schedule, restrict the redirect URIs you allow, and subscribe to [events](/docs/backend) with HMAC-signed webhooks (`X-Authdog-Signature: t=...,v1=...`). Delivery is retried, so you can use them for audit and downstream sync. ## Migrate an existing user base Move users from a legacy system into Authdog without forcing everyone to reset their password. If you're leaving a known provider, use that guide. Otherwise use bulk import, link identities by verified email, and cut over in phases so both systems can validate sessions during the transition. - [Migrate from Auth0](/docs/guides/migrate-from-auth0): export tenants, map identities and roles, preserve passwords or run progressive migration, then cut over applications in stages. - [Migrate from Amazon Cognito](/docs/guides/migrate-from-cognito): export user pools and groups, preserve `sub` identifiers, choose a credential transition, and move applications by pool or cohort. - [Migrate from Clerk](/docs/guides/migrate-from-clerk): move users, organizations, memberships, and external accounts while replacing Clerk session validation in stages. - [Migrate from Descope](/docs/guides/migrate-from-descope): map users, tenants, roles, and authentication methods into Authdog environments and organizations. - [Migrate from Firebase](/docs/guides/migrate-from-firebase): export Firebase Authentication users, retain `uid` as an external identifier, translate custom claims, and switch clients off Firebase ID tokens. - [Migrate from Stytch](/docs/guides/migrate-from-stytch): move consumer or B2B users, organizations, and authentication methods without treating active Stytch sessions as portable. - [Migrate from WorkOS](/docs/guides/migrate-from-workos): move AuthKit users, organizations, SSO connections, and Directory Sync with a staged B2B cutover. - [Migrate from Supabase](/docs/guides/migrate-from-supabase): export `auth.users`, preserve Auth UUIDs for RLS joins, import bcrypt hashes when possible, and retire Supabase JWTs. - [Migrate from Better Auth](/docs/guides/migrate-from-better-auth): map adapter records, accounts, sessions, organizations, and plugins into explicit Authdog resources. - [Migrate from another identity system](/docs/guides/migrate-from-other): inventory a custom user store, preserve source identifiers, test credential compatibility, and plan rollback before cutover. ## Related | Read | To learn how to | | --- | --- | | [Users](/docs/users) | Work with the user record and its lifecycle | | [Backend requests](/docs/backend) | Validate the session on your server | | [Account portal](/docs/account-portal) | Use the hosted sign-in UI | ### Framework guides Source: https://www.authdog.com/docs/guides/frameworks.md Choose the framework closest to your application. Each guide covers the integration surface that exists today and identifies whether identity is merely available to the client or verified by trusted server code. ## Frontend frameworks - [Next.js](/docs/frameworks/nextjs): App Router callbacks, client identity, middleware, and server enforcement boundaries. - [React](/docs/frameworks/react): Presentational Authdog identity components for React applications. - [Remix](/docs/frameworks/remix): Loaders, redirects, cookies, and client identity. - [React Native / Expo](/docs/frameworks/expo): Mobile sign-in with secure storage and cold or warm deep links. - [SvelteKit](/docs/frameworks/sveltekit): Server hooks, load functions, and validated user information. - [TanStack Start](/docs/frameworks/tanstack-start): Full-stack React integration with server functions. - [Vue](/docs/frameworks/vue): Vue composables, client setup, and server cookie handling. - [Angular](/docs/frameworks/angular): Standalone providers, guards, and interceptors. - [Astro](/docs/frameworks/astro): SSR middleware, pages, endpoints, and client bootstrap. - [Gatsby](/docs/frameworks/gatsby): Browser provider plus server-side Functions integration. - [RedwoodJS](/docs/frameworks/redwood): Web provider and API-side authentication helpers. ## Backend frameworks - [Express](/docs/backend/express): Session middleware and route enforcement for Express 4 and 5. - [Fastify](/docs/backend/fastify): Authdog plugin, request decoration, and protected routes. - [Python](/docs/backend/python): hub for the five bindings. - [FastAPI](/docs/backend/fastapi): dependencies for session and `require_auth`. - [Django](/docs/backend/django): middleware and a view decorator. - [Flask](/docs/backend/flask): session on `flask.g` and a decorator. - [Starlette](/docs/backend/starlette): ASGI middleware and async gates. - [aiohttp](/docs/backend/aiohttp): middleware and a handler wrapper. - [Go](/docs/backend/go): Core client with a first-class Gin adapter and manual router composition. - [Rust](/docs/backend/rust): Core SDK and integrations for Axum, Actix Web, Rocket, Warp, and Poem. ## No framework match? Authdog exposes OpenID Connect discovery and standard bearer tokens. Start with [Backend requests](/docs/backend) or the [API reference](/docs/api) when your framework is not listed. ### Examples Source: https://www.authdog.com/docs/examples.md Each app below is a small product with Authdog on the private surface. Open one to see the screen or route the sample actually serves, then clone the same folder from [authdog/samples](https://github.com/authdog/samples/tree/main/starters) under `starters/`. A signed-in session is not a permission grant. Apply [authorization](/docs/concepts/authorization) after the identity check. ## Frontend - [Team dashboard](/docs/examples/nextjs) — Next.js. Project status for the signed-in member. Billing, Mobile, and Docs render only after userinfo accepts the session. - [Customer navbar](/docs/examples/react) — React Elements. Shop catalog under a navbar that opens hosted sign-in. Orders stay on a server app that holds the session. - [Support inbox](/docs/examples/remix) — Remix. Customer tickets on `/inbox`. The loader redirects anonymous agents before the list renders. - [Field companion](/docs/examples/expo) — React Native / Expo. Today's site visits after a deep-link return. The job list renders only after userinfo accepts the token in secure storage. - [Member site](/docs/examples/sveltekit) — SvelteKit. Public home, member library on `/library`. `getUser` redirects anonymous readers before the guides load. - [Ops console](/docs/examples/tanstack-start) — TanStack Start. Open incidents for the on-call operator. Anonymous visitors see the sign-in link and no incident rows. - [Workspace settings](/docs/examples/vue) — Vue. Workspace name, timezone, and join notification after hosted sign-in. The fields stay hidden until userinfo returns a user. - [Admin console](/docs/examples/angular) — Angular. Team directory on `/directory`. The route guard keeps the list off screen until `fetchUser` returns a user. - [Handbook](/docs/examples/astro) — Astro. Public chapters on the home page. Member chapters on `/handbook` load only after `getUser` accepts the session. - [Resource library](/docs/examples/gatsby) — Gatsby. Titles on the public page. `GET /api/files` returns the board pack and runbook only after `requireAuth`. - [Job board](/docs/examples/redwood) — RedwoodJS. Listings stay public. The applications function returns candidates only after `requireAuth`. ## Backend - [Orders API](/docs/examples/express) — Express. `GET /orders` returns open orders for the signed-in clerk and 401 for everyone else. - [Inventory API](/docs/examples/fastify) — Fastify. `GET /skus` returns on-hand stock for the signed-in clerk and 401 for everyone else. - [Booking API](/docs/examples/fastapi) — FastAPI. `GET /bookings` returns room reservations. Routes without a session return 401. - [Clinic portal](/docs/examples/django) — Django. `GET /appointments` returns today's clinic slots. Anonymous callers are rejected. - [Status board](/docs/examples/flask) — Flask. `GET /` publishes component state. `GET /notes` returns the internal timeline only with a session. - [Intake service](/docs/examples/starlette) — Starlette. `GET /intakes` returns the waiting queue. Anonymous callers are rejected. - [Event ingest](/docs/examples/aiohttp) — aiohttp. `GET /events` returns recent door and check-in events. Handlers without a session are rejected. - [Billing service](/docs/examples/go) — Go. `GET /invoices` returns open and paid invoices. Gin returns 401 unless Authdog authenticates the caller. - [Policy service](/docs/examples/rust) — Rust. `GET /policies` returns shipping and refund rules. The Axum gate returns 401 without a session. ### Sign up & sign in Source: https://www.authdog.com/docs/authentication.md Add sign-up and sign-in without building the credential screens yourself. Turn on the methods you want, choose how the UI is rendered, and wire up the session Authdog issues. The model behind those steps is [Authentication](/docs/concepts/authentication). ## Enable sign-in methods You configure methods **per environment** in the [Authdog console](https://console.authdog.com). Turn on any combination of: - **Password**, with a breach-aware password policy. - **Social OAuth2**, including Google, GitHub, and other providers. - **Passwordless**, with magic links delivered by email. - **MFA**, with TOTP authenticator apps prompted after the first factor. - **Enterprise SSO**, over SAML 2.0 or OIDC, through Okta, Entra ID, JumpCloud, Ping, or BeyondTrust. Each environment has its own signing keys, connections, and user store, plus a **public key** (`pk_...`) your frontend uses to talk to that environment. ### Where to go deeper These topics are configured on the environment. The links match what ships today: | Topic | Where to read | | --- | --- | | Passkeys (WebAuthn) | [Passkeys](/docs/passkeys), [Authentication → Methods](/docs/console/authentication) | | MFA (TOTP, SMS, backup codes) | [MFA](/docs/mfa), Authentication → Features | | Magic link and email OTP | [Authentication → Methods](/docs/console/authentication), [Notifications](/docs/console/notifications) | | SMS / WhatsApp OTP sign-in | Authentication → Methods (requires an SMS provider) | | Waitlist and invite-only signup | [Users → Settings](/docs/console/users#require-signup-for-first-login), signup mode restrictions | | Identity linking and reauthentication | [Account Portal](/docs/account-portal), [Custom flows](/docs/custom-flows) | | Enterprise SSO connections | [SSO providers catalog](/marketplace), Authentication → Providers | | Admin Portal (embeddable) | [Account Portal](/docs/account-portal) | ## Choose how the UI is rendered You have three options, from least custom to most: - **Hosted [Account Portal](/docs/account-portal).** Authdog renders sign-in, sign-up, and MFA. Send people there, and they come back signed in. You don't write that UI. - **[Components](/docs/components).** Drop prebuilt, themeable sign-in widgets into your own pages. - **[Custom flows](/docs/custom-flows).** Call the flow primitives and build the experience yourself. A minimal hosted sign-in with the browser SDK looks like this: ```ts import { createAuthdogClient } from "@authdog/javascript"; const authdog = createAuthdogClient({ publicKey: process.env.NEXT_PUBLIC_PK_AUTHDOG, }); authdog.handleRedirectCallback(); // consume ?token= after the redirect back authdog.signIn(); // send the user to the hosted sign-in flow ``` ## Configure social and enterprise connections Set up social OAuth2 providers and enterprise SSO under the environment's **Connections**. For social login, register the provider's client ID and secret. For enterprise SSO, exchange SAML or OIDC metadata with the customer's identity provider. The SSO providers catalog has a setup page for each one. Connections belong to the environment, so development and production stay fully separate. ## The session A successful sign-in issues a **signed JWT**. Authdog sets it as the `authdog-session` cookie (`HttpOnly`, `SameSite=Lax`, `Secure` in production). Clients that can't use cookies send it as `Authorization: Bearer `. Validate that session on each request. Don't trust it just because it showed up on the request. See [Backend requests](/docs/backend) for the server-side check, and [Sessions and tokens](/docs/concepts/sessions-tokens) for the token model and lifetimes. Revoking a session sets `revoked_at` on that session. The next session check rejects it. Authdog does not push the revoke into a page that is already open. ## Sign-out Sign-out lives in the same portal or component you used for sign-in. It expires the `authdog-session` cookie and sends the user back to your app. If you built a custom flow, call the logout primitive and clear the cookie on the server. ## Device authorization (RFC 8628) Use the device flow when the client has no browser, or no comfortable keyboard: a TV, a CLI, a printer. The client requests a **device code** and a **user code**, shows a verification URL, and polls until someone who is already signed in to the same environment approves or denies the request. Per-environment issuer paths: - `POST {issuer}/device` starts the grant. The client is confidential and uses HTTP Basic auth. - `POST {issuer}/token` with `grant_type=urn:ietf:params:oauth:grant-type:device_code` polls for tokens. - `GET {issuer}/device/verify?user_code=…` is where the user enters the code. When sign-in is configured, that page sends them through the environment's sign-in flow first. Poll errors follow RFC 8628: `authorization_pending`, `slow_down`, `expired_token`, `access_denied`, and `invalid_grant`. This grant doesn't issue refresh tokens. Discovery advertises `device_authorization_endpoint` on the environment's issuer document. You can try the poll with the CLI: ```bash ISSUER=https://id.example/oidc/$ENVIRONMENT_ID \ CLIENT_ID=... CLIENT_SECRET=... \ authdog device authorize --poll ``` The person who approves needs a user session in that same environment. ## Next steps - [Permissions](/docs/permissions) covers what a signed-in user is allowed to do. - [Users](/docs/users) and [Organizations](/docs/concepts/organizations) cover the accounts behind those sessions. ### Users Source: https://www.authdog.com/docs/users.md A user is the identity record in an environment. Every environment has its own user store, so the same person signing in to two of your environments is two users. Below is how those records get created, how identities link together, and how access control applies. ## How users are created Authdog creates or updates the user on the first successful sign-in, whichever method they used: - **Self-service sign-up**: password or passwordless / magic link. - **Social OAuth2**: Google, GitHub, Microsoft, Apple, and dozens more. - **Enterprise SSO**: SAML 2.0 or OIDC, with the user created just-in-time (JIT) on first login. - **[Provisioning](/docs/concepts/provisioning)**: created ahead of time via SCIM 2.0 or the HRIS connector, before the user ever signs in. ## Identity linking A person can sign in more than one way. Authdog links those methods to a single user by **verified email**. If someone signs up with a password and later signs in with a Google account that owns the same verified email, both identities resolve to the same user. An unverified address can't claim an existing account. ## Profile and metadata Each user has a profile (email, name, and attributes from the provider) plus metadata you attach yourself. Use metadata for fields that belong to your app (a plan tier, a preferred locale, an internal ID). Those fields travel with the user, and they're available when you resolve a session. Profile fields that come from SSO or HRIS refresh on later sign-ins and on provisioning syncs. ## Lifecycle state A user is either **active** or **suspended**. Suspending someone blocks new sign-ins right away, and they can't establish a session. The record and its history stay, which is what you want for offboarding or a compromised account. When an identity provider deactivates a user through [provisioning](/docs/concepts/provisioning), Authdog suspends them the same way. ## Users and roles End users live in an environment's user store. That's separate from [organization](/docs/concepts/organizations) membership, which is for the people who administer the Authdog account and its tenants. What an end user can do comes from the environment's authorization model. See [Roles and permissions](/docs/permissions). Group-to-role mappings from an identity provider or HRIS can grant those roles at provisioning time. ## Managing users Manage users in the [console](/docs/console/tenants): search, inspect a profile, suspend or reactivate, and review sign-in activity. The same actions are on the REST API under `/v1/...`, so a [backend](/docs/backend) can list, create, update, and suspend users too. ### Roles & permissions Source: https://www.authdog.com/docs/permissions.md Sign-in tells you who someone is. This page is what they're allowed to do. The models behind the checks are in [Authorization](/docs/concepts/authorization). ## Pick a model for the environment Each environment enforces **one** model. You choose it in the [Authdog console](https://console.authdog.com): - **RBAC**: roles hold permissions; seat-based. The default and simplest to reason about. - **ABAC**: Rego attribute policies, evaluated at the edge. - **FGA**: Zanzibar-style relationship tuples for fine-grained, resource-level access. The rest of this page uses RBAC, because it covers most apps. All three models are enforced on the server. ## Define roles and permissions In the console, create **permissions** as specific capability strings (for example `invoices:read` and `invoices:write`) and group them into **roles** (`admin`, `member`, `billing`). A role is a named bundle of permissions. Keep the permissions specific, and keep the role list small. Assignment stays simple, and the check on a route stays precise. ## Assign roles to users A [user](/docs/users) can receive a role two ways: - **Directly.** Assign the role in the console or through the API. - **From an identity-provider group.** Map the provider's groups to Authdog roles, so access is granted at sign-in. See [Provisioning](/docs/concepts/provisioning). Organization membership is for account administrators under an [organization](/docs/concepts/organizations). It doesn't grant end-user permissions inside an environment. For enterprise customers, group mappings are the path we recommend. When they deprovision someone in their identity provider, access is revoked with no extra step on your side. ## Enforce a check on a route Roles and permissions travel on the validated session. On your backend, gate the route on the specific permission: ```ts app.get("/invoices", requirePermission("invoices:read"), (req, res) => { res.json(listInvoices()); }); ``` Always enforce this on the server. A check in the browser is for the interface only. It isn't a security boundary. See [Backend requests](/docs/backend) to wire the gate into your framework. ## ABAC and FGA alternatives If RBAC is too coarse for your product: - **ABAC** lets you write **Rego** policies over user, resource, and environment attributes. A typical rule is "editors can edit only their own region." - **FGA** stores **relationship tuples** (`document:42#editor@user:alice`) for per-object sharing, the way a document product shares a file. See [FGA](/docs/fga) for the tuple model and current availability. You configure both on the environment, and both are enforced on the server. See [Authorization](/docs/concepts/authorization) when you're choosing. Whichever model the environment runs, an external service or gateway can ask Authdog for a decision over the [AuthZEN Authorization API](/docs/authzen) instead of rebuilding the check. ### Backend requests Source: https://www.authdog.com/docs/backend.md Your backend never sees a password. It checks the session Authdog already issued, then allows or denies the route. If you're using an Authdog SDK, the middleware does that check for you. This page is the model underneath, and the shape of a protected endpoint when you want to see it yourself. ## What arrives at your backend After a successful sign-in, Authdog issues a signed JWT. Every request from your frontend carries it one of two ways: - The `authdog-session` **cookie** (set automatically by the browser SDK). - An `Authorization: Bearer ` **header** (for native apps and API clients). Read whichever one is present, and validate it before you trust anything else on the request. ## The verification model Validation needs two inputs: your environment's **public key** (`pk_...`) and the identity provider's `userinfo` endpoint. The SDK parses the public key once at startup. A malformed key, or a key whose identity host isn't on the trusted allowlist, fails immediately, not on the first real request. On each request the SDK reads the token, checks it against `userinfo`, and returns the current user. See [Sessions and tokens](/docs/concepts/sessions-tokens) and [Authentication](/docs/concepts/authentication) for the full model. ## Protecting a route Resolving a session only tells you who the caller is, or that there isn't one. The enforcement point is a guard that rejects unauthenticated requests with `401`. In FastAPI that guard is a dependency: ```python import os from fastapi import Depends, FastAPI from authdog.fastapi import Authdog app = FastAPI() authdog = Authdog(public_key=os.environ["PK_AUTHDOG"]) @app.get("/me") async def me(user=Depends(authdog.require_auth)): # reached only for a valid session; 401 otherwise return user ``` Every SDK has the same shape: a resolver you can read when you're shaping a response, and a `require_auth` gate that is the security boundary. A resolved session is not a permission grant. Pair it with your environment's [authorization](/docs/concepts/authorization) model. ## Service-to-service (M2M) For a request with no end user (a cron job, or one backend calling another), use **machine-to-machine** auth. An M2M application uses the OAuth 2.0 `client_credentials` grant to get a token, then sends it as `Authorization: Bearer `. The receiving service validates it the same way it validates a user session, and resolves a service identity instead of a person. ## Calling the REST API Once a request is authenticated, your backend can call the [Authdog REST API](/docs/api) at `https://api.authdog.com/v1/...` to read users, organizations, and more. The language clients are typed wrappers over that API, so you rarely build the raw request yourself. Language clients for that API: [Python](/docs/sdks/python), [Node.js](/docs/sdks/node), [Go](/docs/sdks/go), [Rust](/docs/sdks/rust), [Java](/docs/sdks/java), [C#](/docs/sdks/csharp), and [Zig](/docs/sdks/zig). Python is `authdog` 0.1.1 on [PyPI](https://pypi.org/project/authdog/0.1.1/). The others ship from [authdog/sdk](https://github.com/authdog/sdk). ## Shipped backend SDKs All expose the same three primitives: session resolver, `require_auth` gate, and logout handler: - **Node**: Express, Fastify, Hono, and Koa - **Python**: FastAPI, Django, Flask, Starlette, aiohttp - **Go** - **Rust** - **Kotlin** ## Events and webhooks Identity also emits events (`SIGNIN_SUCCESS`, `NEW_USER`, and others) your backend can subscribe to. Webhooks are HMAC-signed (`X-Authdog-Signature: t=..., v1=...`), and delivery is retried. Verify the signature before you act on the payload. See [Events and webhooks](/docs/events-webhooks) for the catalog, the signature check, and retry behavior. ### API reference Source: https://www.authdog.com/docs/api.md The Authdog REST API manages identity resources programmatically. Live endpoint schemas and request examples are available at [api.authdog.com](https://api.authdog.com); machine-readable OpenAPI JSON is at [`/v1/openapi`](https://api.authdog.com/v1/openapi). ## Base URL and authentication Supported public endpoints live under `https://api.authdog.com/v1/...`. Authenticate every call with a scoped Bearer token: ```bash curl "https://api.authdog.com/v1/tenants" \ -H "Authorization: Bearer $AUTHDOG_API_TOKEN" ``` Keep tokens server-side and grant only required scopes. Never embed management tokens in browser bundles or commit them to source control. Management GraphQL used by Authdog Console is internal and unsupported for external integrations. Use REST `/v1` so authentication, errors, and compatibility follow public API guarantees. ## Public GraphQL (limited) `POST https://api.authdog.com/v1/graphql` accepts an environment secret (`Authorization: Bearer adenv_…`) and a single selection set with only `health` and `eventQueryWindowDays`. Unknown fields, mutations, and unauthenticated calls are rejected. This endpoint is for lightweight health and read-policy checks—not a substitute for management GraphQL or REST directory APIs. ```bash curl -s "https://api.authdog.com/v1/graphql" \ -H "Authorization: Bearer $ADENV_SECRET" \ -H "Content-Type: application/json" \ -d '{"query":"{ health eventQueryWindowDays }"}' ``` ## Responses and compatibility Success and error schemas are documented per endpoint in OpenAPI. Clients should handle standard HTTP status codes and JSON error bodies, preserve unknown response fields, and avoid depending on undocumented properties. Existing public fields are deprecated before removal; incompatible changes use a new API version. ## Resource families The core resources mirror the console: - **Tenants and environments**: projects and deployment boundaries. - **Directory**: users, groups, and memberships. - **Organizations**: customer organizations and invitations. - **Roles and permissions**: RBAC resources assigned to users and groups. - **Audit and events**: identity and administrative activity for sync and investigation. See [Audit logs](/docs/audit-logs). ## Provisioning endpoints For automated user lifecycle from external systems: - **SCIM 2.0** at `/v1/scim/v2`, authenticated with `adscim_` tokens: standard provisioning from IdPs like Okta and Entra ID. - **HRIS** at `/v1/hris/v1`, authenticated with `adhris_` tokens: inbound employee and department sync from HR systems. Both are covered in [Provisioning](/docs/concepts/provisioning) and configured per environment in the console. ## Agent and machine identity Non-human callers: pick one of [M2M, Agents, service account, or PAT](/docs/machine-identity). Do not stack them. **Agents / MCP trust store** (environment-scoped; the console module is self-serve): ``` /v1/tenants/{tenantId}/environments/{environmentId}/mcp/trust-store ``` List, create, get, patch, delete, verify, and revoke entries. Patch accepts `allowedTools` and, when `toolManifestHash` is set, pins the manifest (same as console pin). Keys live under `.../trust-store/{entryId}/keys`. Runtime (authenticate with an environment secret `adenv_`): - `GET /v1/mcp/trust-store/resolve?subject=` - `POST /v1/mcp/events` OpenAPI: [`/v1/openapi`](https://api.authdog.com/v1/openapi). Console GraphQL is internal. ## MCP access token validation (resource servers) MCP resource servers validate **user-issued OAuth tokens** from your environment OIDC issuer. Prefer **JWT + JWKS** (`MCP_JWT_ONLY=true` in the private runtime guard, or `issuer` on the Node.js verifier `@authdog/mcp-sdk`); see [MCP authentication](/docs/mcp-authentication). | Path | Use when | | --- | --- | | JWT + JWKS | Desktop MCP clients (default) | | RFC 7662 introspection | Opaque access tokens only, with the environment **validation credential** from MCP Auth → Connect | Introspection is `POST {issuer}/introspect` with HTTP Basic auth using the validation client id and secret. The validation credential may introspect user access tokens for that environment. Issuing clients may still introspect only their own tokens. Response fields follow `specs/024-mcp-auth-platform/contracts/introspection-schema.json`. Environment integration URLs (issuer, JWKS, sdk-config) are also available via `GET /v1/tenants/{tenantId}/environments/{environmentId}/sdk-config` with a management API token. ## Authorization endpoints Access decisions are served over the OpenID [AuthZEN Authorization API](/docs/authzen) at `/access/v1`, deliberately outside the `/v1` base path because the specification fixes those paths. Authenticate with an environment API secret (`adenv_`); discovery at `/.well-known/authzen-configuration` is unauthenticated by design. ## Webhooks Subscribe to identity events and Authdog delivers them to your endpoint. Each request is **HMAC-signed** with an `X-Authdog-Signature: t=..., v1=...` header, so verify it before trusting the payload. Delivery uses **durable retry**, so a temporary outage on your side won't drop events. See [Events and webhooks](/docs/events-webhooks) for setup and verification. ## Language SDKs Official clients wrap this REST surface. They live in [authdog/sdk](https://github.com/authdog/sdk). Python is on PyPI. The other clients are source packages in that monorepo. | Language | Guide | Package | | --- | --- | --- | | Python | [Python SDK](/docs/sdks/python) | `authdog` 0.1.1 on PyPI | | Node.js | [Node.js SDK](/docs/sdks/node) | `@authdog/node-sdk` 0.2.0, not on npm | | Go | [Go SDK](/docs/sdks/go) | `github.com/authdog/go-sdk` | | Rust | [Rust SDK](/docs/sdks/rust) | crate `authdog` 0.2.0, not on crates.io | | Java | [Java SDK](/docs/sdks/java) | `com.authdog:authdog-java-sdk` 0.2.0, not on Maven Central | | C# | [C# SDK](/docs/sdks/csharp) | `Authdog.Sdk` 0.2.0, not on NuGet | | Zig | [Zig SDK](/docs/sdks/zig) | `authdog` 0.1.0 from `build.zig.zon` | Do not confuse these management clients with the FastAPI / Django / Flask session bindings. Those live under [Python backends](/docs/backend/python) and are a different package. ## Next steps - [Language SDKs](/docs/sdks/python): Python, Node.js, Go, Rust, Java, C#, and Zig clients. - [Backend requests](/docs/backend): validate sessions and call the API from your server. - [Integrations](/docs/integrations): connect Authdog to the rest of your stack. ### Python SDK Source: https://www.authdog.com/docs/sdks/python.md `authdog` 0.1.1 is the official Python client for the [Authdog REST API](/docs/api). Install it from PyPI. It is a management and userinfo client, not a web-framework session binding. Need to protect FastAPI, Django, Flask, Starlette, or aiohttp routes? That is a different package. See [Python backends](/docs/backend/python). ## Install ```bash python -m pip install "authdog==0.1.1" ``` Requires Python 3.8.1+ and `httpx` 0.24+. Source: [`python/`](https://github.com/authdog/sdk/tree/main/python) in [authdog/sdk](https://github.com/authdog/sdk). PyPI: [authdog](https://pypi.org/project/authdog/0.1.1/). ## Configure Construct one client with the public API base URL. Pass a management Bearer credential (`ad_…`) when you call privileged endpoints: ```python import os from authdog import AuthdogClient client = AuthdogClient( "https://api.authdog.com", api_key=os.environ["AUTHDOG_API_TOKEN"], ) ``` Keep the token server-side. `get_userinfo` still uses the caller access token, not the management key. Use the client as a context manager so the HTTP session closes: ```python from authdog import AuthdogClient with AuthdogClient("https://api.authdog.com", api_key="ad_...") as client: probe = client.health() ``` `health()` is public and works without an API key. ## Resolve a user from an access token ```python from authdog import APIError, AuthenticationError, AuthdogClient client = AuthdogClient("https://api.authdog.com") try: info = client.get_userinfo(access_token) print(info.user.display_name) print(info.user.emails[0].value) except AuthenticationError: # 401: missing, invalid, or expired access token raise except APIError: # transport or non-401 HTTP failure raise ``` `UserInfoResponse` uses snake_case attributes (`user.display_name`, `session.remaining_seconds`). ## Call the management API Namespaces wrap Waves 1–2 of the public `/v1` surface: | Attribute | Resources | | --- | --- | | `organizations` | Organizations, invitations, members, keys | | `tenants` | Tenants, domains, seats | | `projects` | Applications under a tenant | | `environments` | Environment records | | `users` | Directory users in a tenant + environment | | `groups` | Groups and membership | | `rbac` | Roles, permissions, resources, mappings, ABAC | | `audit` | Administrative audit logs | | `events` | Identity event stream | | `webhooks` | Webhook subscriptions | | `notification_channels` | SIEM / notification channels | | `service_accounts` | Service accounts | | `personal_access_tokens` | PATs | | `api_secrets` | Environment API secrets | ```python with AuthdogClient("https://api.authdog.com", api_key="ad_...") as client: orgs = client.organizations.list() users = client.users.list("ten_123", "env_456", limit=25) ``` Directory calls take `tenant_id` then `environment_id`. OpenAPI at [`/v1/openapi`](https://api.authdog.com/v1/openapi) is the field-level contract. ## Errors | Error | When | | --- | --- | | `AuthdogError` | Base class | | `AuthenticationError` | 401 | | `APIError` | Transport failure or other HTTP 4xx/5xx (`status_code` when present) | ## Next - [API reference](/docs/api): auth, versioning, and resource families - [Backend requests](/docs/backend): validate sessions on incoming requests - [Python backends](/docs/backend/python): FastAPI / Django / Flask / Starlette / aiohttp session bindings - [Node.js](/docs/sdks/node), [Go](/docs/sdks/go), [Rust](/docs/sdks/rust), [Java](/docs/sdks/java), [C#](/docs/sdks/csharp), and [Zig](/docs/sdks/zig): the same client in the other languages - [Users](/docs/users): directory model the `users` namespace talks to ### Node.js SDK Source: https://www.authdog.com/docs/sdks/node.md `@authdog/node-sdk` 0.2.0 is the official Node.js client for the [Authdog REST API](/docs/api). It lives in [`node/`](https://github.com/authdog/sdk/tree/main/node) of [authdog/sdk](https://github.com/authdog/sdk). It is a management and userinfo client, not a web-framework session binding. The public npm registry does not host this package. Need to protect an Express, Fastify, Hono, or Koa route? Use a [backend SDK](/docs/backend). ## Install Clone the monorepo and build the package: ```bash git clone https://github.com/authdog/sdk.git cd sdk/node pnpm install pnpm build ``` The package is ESM and ships TypeScript types from `dist/`. Depend on that build from your app (a `file:` dependency, or your own registry). `npm install @authdog/node-sdk` does not resolve on the public registry. ## Configure Construct one client with the public API base URL. Pass a management Bearer credential (`ad_…`) when you call privileged endpoints: ```ts import { AuthdogClient } from "@authdog/node-sdk" const client = new AuthdogClient({ baseUrl: "https://api.authdog.com", apiKey: process.env.AUTHDOG_API_TOKEN, }) ``` Keep the token server-side. `getUserInfo` still uses the caller access token, not the management key. Optional config fields `environmentSecret` (`adenv_`), `scimToken` (`adscim_`), and `hrisToken` (`adhris_`) are the AuthZEN/MCP runtime, SCIM, and HRIS Bearers. `timeout` is milliseconds and defaults to `10000`. `health()` is public and works without an API key: ```ts const probe = await client.health() ``` Call `client.close()` when the process is done with the client. ## Resolve a user from an access token ```ts import { APIError, AuthenticationError, AuthdogClient } from "@authdog/node-sdk" const client = new AuthdogClient({ baseUrl: "https://api.authdog.com" }) try { const info = await client.getUserInfo(accessToken) console.log(info.user.displayName) console.log(info.user.emails[0]?.value) } catch (error) { if (error instanceof AuthenticationError) { // 401: missing, invalid, or expired access token throw error } if (error instanceof APIError) { // transport or non-401 HTTP failure throw error } throw error } ``` `GET /v1/userinfo` always sends `Authorization: Bearer `. A constructor API key does not replace that header. `UserInfoResponse` uses camelCase fields (`user.displayName`, `session.remainingSeconds`). ## Call the management API Namespaces on the client wrap Waves 1–5 of the public `/v1` surface: | Attribute | Resources | | --- | --- | | `organizations` | Organizations, invitations, members, keys | | `tenants` | Tenants, domains, seats | | `projects` | Applications under a tenant | | `environments` | Environment records | | `users` | Directory users in a tenant + environment | | `groups` | Groups and membership | | `rbac` | Roles, permissions, resources, mappings, ABAC | | `audit` | Administrative audit logs | | `events` | Identity event stream | | `webhooks` | Webhook subscriptions | | `notificationChannels` | SIEM / notification channels | | `serviceAccounts` | Service accounts | | `personalAccessTokens` | PATs | | `apiSecrets` | Environment API secrets | | `authzen` | AuthZEN evaluate, search, and discovery | | `scim` | SCIM 2.0 directory | | `hris` | HRIS employees and departments | | `mcp` | MCP runtime | | `otel` | OpenTelemetry exporters | | `oidcClients` | OIDC clients | | `actions` | Environment actions | | `addons` | Add-ons | | `billing` | Billing | | `settings` | Environment settings | | `elevate` | Elevate | | `emailProviders` | Email providers | | `featureFlags` | Feature flags | | `forms` | Forms | | `provisioningTokens` | Provisioning tokens | | `impersonation` | Impersonation | | `portal` | Account portal | | `security` | Security settings | | `threats` | Threats | | `vanityDomains` | Vanity domains | | `widgets` | Widgets | | `smsProviders` | SMS providers | | `connectedApps` | Connected-app grants | ```ts const orgs = await client.organizations.list() const users = await client.users.list("ten_123", "env_456") ``` Directory calls take the tenant id, then the environment id. AuthZEN discovery is unauthenticated. Evaluate, search, and the MCP runtime use `environmentSecret`. SCIM uses `scimToken`. HRIS uses `hrisToken`. OpenAPI at [`/v1/openapi`](https://api.authdog.com/v1/openapi) is the field-level contract. ## Errors | Error | When | | --- | --- | | `AuthenticationError` | HTTP 401 | | `APIError` | Other HTTP failures and transport errors | ## Other languages | Language | Guide | | --- | --- | | Python | [Python SDK](/docs/sdks/python) | | Go | [Go SDK](/docs/sdks/go) | | Rust | [Rust SDK](/docs/sdks/rust) | | Java | [Java SDK](/docs/sdks/java) | | C# | [C# SDK](/docs/sdks/csharp) | | Zig | [Zig SDK](/docs/sdks/zig) | ## Next - [API reference](/docs/api): auth, versioning, and resource families - [Backend requests](/docs/backend): validate sessions on incoming requests - [Users](/docs/users): directory model the `users` namespace talks to ### Go SDK Source: https://www.authdog.com/docs/sdks/go.md `github.com/authdog/go-sdk` is the official Go client for the [Authdog REST API](/docs/api). The module requires Go 1.21+. Source is [`go/`](https://github.com/authdog/sdk/tree/main/go) in [authdog/sdk](https://github.com/authdog/sdk). It is a management and userinfo client, not a web-framework session binding. Need to protect an HTTP route? Use the [Go backend SDK](/docs/backend/go). ## Install The module path is `github.com/authdog/go-sdk`, but there is no `authdog/go-sdk` repository and the Go module proxy has no tagged version. A bare `go get github.com/authdog/go-sdk` does not resolve. Check out [authdog/sdk](https://github.com/authdog/sdk) and point a `replace` at the `go/` directory: ``` require github.com/authdog/go-sdk v0.0.0 replace github.com/authdog/go-sdk => ../sdk/go ``` ```go import "github.com/authdog/go-sdk" ``` ## Configure Construct one client with the public API base URL. Pass a management Bearer credential (`ad_…`) when you call privileged endpoints: ```go client := authdog.NewClient(authdog.ClientConfig{ BaseURL: "https://api.authdog.com", APIKey: os.Getenv("AUTHDOG_API_TOKEN"), }) ``` Keep the token server-side. `GetUserInfo` still uses the caller access token, not the management key. Optional config fields `EnvironmentSecret` (`adenv_`), `SCIMToken` (`adscim_`), and `HRISToken` (`adhris_`) are the AuthZEN/MCP runtime, SCIM, and HRIS Bearers. `Timeout` defaults to 10 seconds. `HTTPClient` overrides the default client. `Health` is public and works without an API key: ```go probe, err := client.Health(ctx) ``` ## Resolve a user from an access token ```go userInfo, err := client.GetUserInfo(ctx, accessToken) if authdog.IsAuthenticationError(err) { // 401: missing, invalid, or expired access token return err } if authdog.IsAPIError(err) { // transport or non-401 HTTP failure return err } if err != nil { return err } fmt.Println(userInfo.User.DisplayName) if len(userInfo.User.Emails) > 0 { fmt.Println(userInfo.User.Emails[0].Value) } ``` `GET /v1/userinfo` always sends `Authorization: Bearer `. A constructor API key does not replace that header. ## Call the management API Exported fields on `Client` wrap Waves 1–5 of the public `/v1` surface: | Attribute | Resources | | --- | --- | | `Organizations` | Organizations, invitations, members, keys | | `Tenants` | Tenants, domains, seats | | `Projects` | Applications under a tenant | | `Environments` | Environment records | | `Users` | Directory users in a tenant + environment | | `Groups` | Groups and membership | | `Rbac` | Roles, permissions, resources, mappings, ABAC | | `Audit` | Administrative audit logs | | `Events` | Identity event stream | | `Webhooks` | Webhook subscriptions | | `NotificationChannels` | SIEM / notification channels | | `ServiceAccounts` | Service accounts | | `PersonalAccessTokens` | PATs | | `ApiSecrets` | Environment API secrets | | `Authzen` | AuthZEN evaluate, search, and discovery | | `Scim` | SCIM 2.0 directory | | `Hris` | HRIS employees and departments | | `Mcp` | MCP runtime | | `Otel` | OpenTelemetry exporters | | `OidcClients` | OIDC clients | | `Actions` | Environment actions | | `Addons` | Add-ons | | `Billing` | Billing | | `Settings` | Environment settings | | `Elevate` | Elevate | | `EmailProviders` | Email providers | | `FeatureFlags` | Feature flags | | `Forms` | Forms | | `ProvisioningTokens` | Provisioning tokens | | `Impersonation` | Impersonation | | `Portal` | Account portal | | `Security` | Security settings | | `Threats` | Threats | | `VanityDomains` | Vanity domains | | `Widgets` | Widgets | | `SmsProviders` | SMS providers | | `ConnectedApps` | Connected-app grants | AuthZEN discovery is unauthenticated. Evaluate, search, and the MCP runtime use `EnvironmentSecret`. SCIM uses `SCIMToken`. HRIS uses `HRISToken`. OpenAPI at [`/v1/openapi`](https://api.authdog.com/v1/openapi) is the field-level contract. ## Errors | Check | When | | --- | --- | | `IsAuthenticationError` | HTTP 401 | | `IsAPIError` | Other HTTP failures and transport errors | | `IsAuthdogError` | Any error from this module | ## Other languages | Language | Guide | | --- | --- | | Python | [Python SDK](/docs/sdks/python) | | Node.js | [Node.js SDK](/docs/sdks/node) | | Rust | [Rust SDK](/docs/sdks/rust) | | Java | [Java SDK](/docs/sdks/java) | | C# | [C# SDK](/docs/sdks/csharp) | | Zig | [Zig SDK](/docs/sdks/zig) | ## Next - [API reference](/docs/api): auth, versioning, and resource families - [Backend requests](/docs/backend): validate sessions on incoming requests - [Go backend](/docs/backend/go): session binding for HTTP handlers - [Users](/docs/users): directory model the `Users` namespace talks to ### Rust SDK Source: https://www.authdog.com/docs/sdks/rust.md `authdog` 0.2.0 is the official Rust client for the [Authdog REST API](/docs/api). Source is [`rust/`](https://github.com/authdog/sdk/tree/main/rust) in [authdog/sdk](https://github.com/authdog/sdk). It is a management and userinfo client, not a web-framework session binding. crates.io does not host this crate. Need to protect an HTTP route? Use the [Rust backend SDK](/docs/backend/rust). ## Install Depend on the crate by path from a checkout of the monorepo: ```toml [dependencies] authdog = { path = "../sdk/rust" } ``` Client methods are async. The crate uses `tokio` and `reqwest`. ## Configure Construct one client with the public API base URL. Pass a management Bearer credential (`ad_…`) when you call privileged endpoints: ```rust use authdog::{AuthdogClient, AuthdogClientConfig}; use std::time::Duration; let client = AuthdogClient::new(AuthdogClientConfig { base_url: "https://api.authdog.com".to_string(), api_key: std::env::var("AUTHDOG_API_TOKEN").ok(), timeout: Some(Duration::from_secs(10)), ..Default::default() })?; ``` Keep the token server-side. `get_user_info` still uses the caller access token, not the management key. Optional config fields `environment_secret` (`adenv_`), `scim_token` (`adscim_`), and `hris_token` (`adhris_`) are the AuthZEN/MCP runtime, SCIM, and HRIS Bearers. `health()` is public and works without an API key: ```rust let probe = client.health().await?; ``` ## Resolve a user from an access token ```rust match client.get_user_info(access_token).await { Ok(info) => { println!("{}", info.user.display_name); if let Some(email) = info.user.emails.first() { println!("{}", email.value); } } Err(err) if err.is_authentication() => { // 401: missing, invalid, or expired access token return Err(err.into()); } Err(err) if err.is_api() => { // transport or non-401 HTTP failure return Err(err.into()); } Err(err) => return Err(err.into()), } ``` `GET /v1/userinfo` always sends `Authorization: Bearer `. A constructor API key does not replace that header. `UserInfoResponse` uses snake_case fields (`user.display_name`, `session.remaining_seconds`). ## Call the management API Methods on the client wrap Waves 1–5 of the public `/v1` surface: | Method | Resources | | --- | --- | | `organizations()` | Organizations, invitations, members, keys | | `tenants()` | Tenants, domains, seats | | `projects()` | Applications under a tenant | | `environments()` | Environment records | | `users()` | Directory users in a tenant + environment | | `groups()` | Groups and membership | | `rbac()` | Roles, permissions, resources, mappings, ABAC | | `audit()` | Administrative audit logs | | `events()` | Identity event stream | | `webhooks()` | Webhook subscriptions | | `notification_channels()` | SIEM / notification channels | | `service_accounts()` | Service accounts | | `personal_access_tokens()` | PATs | | `api_secrets()` | Environment API secrets | | `authzen()` | AuthZEN evaluate, search, and discovery | | `scim()` | SCIM 2.0 directory | | `hris()` | HRIS employees and departments | | `mcp()` | MCP runtime | | `otel()` | OpenTelemetry exporters | | `oidc_clients()` | OIDC clients | | `actions()` | Environment actions | | `addons()` | Add-ons | | `billing()` | Billing | | `settings()` | Environment settings | | `elevate()` | Elevate | | `email_providers()` | Email providers | | `feature_flags()` | Feature flags | | `forms()` | Forms | | `provisioning_tokens()` | Provisioning tokens | | `impersonation()` | Impersonation | | `portal()` | Account portal | | `security()` | Security settings | | `threats()` | Threats | | `vanity_domains()` | Vanity domains | | `widgets()` | Widgets | | `sms_providers()` | SMS providers | | `connected_apps()` | Connected-app grants | AuthZEN discovery is unauthenticated. Evaluate, search, and the MCP runtime use `environment_secret`. SCIM uses `scim_token`. HRIS uses `hris_token`. OpenAPI at [`/v1/openapi`](https://api.authdog.com/v1/openapi) is the field-level contract. ## Errors | Error | When | | --- | --- | | `AuthdogError::Authentication` | HTTP 401. `is_authentication()` is true. | | `AuthdogError::Api` | Other HTTP failures and transport errors. `is_api()` is true. | ## Other languages | Language | Guide | | --- | --- | | Python | [Python SDK](/docs/sdks/python) | | Node.js | [Node.js SDK](/docs/sdks/node) | | Go | [Go SDK](/docs/sdks/go) | | Java | [Java SDK](/docs/sdks/java) | | C# | [C# SDK](/docs/sdks/csharp) | | Zig | [Zig SDK](/docs/sdks/zig) | ## Next - [API reference](/docs/api): auth, versioning, and resource families - [Backend requests](/docs/backend): validate sessions on incoming requests - [Rust backend](/docs/backend/rust): session binding for HTTP handlers - [Users](/docs/users): directory model the `users` namespace talks to ### Java SDK Source: https://www.authdog.com/docs/sdks/java.md `com.authdog:authdog-java-sdk` 0.2.0 is the official Java client for the [Authdog REST API](/docs/api). It requires Java 11+. Source is [`java/`](https://github.com/authdog/sdk/tree/main/java) in [authdog/sdk](https://github.com/authdog/sdk). It is a management and userinfo client, not a web-framework session binding. Maven Central does not host this artifact. ## Install From a checkout of `java/`, install the artifact into the local Maven repository: ```bash mvn install ``` Then depend on it: ```xml com.authdog authdog-java-sdk 0.2.0 ``` Gradle, after the same local install: ```gradle implementation "com.authdog:authdog-java-sdk:0.2.0" ``` The client uses OkHttp and Jackson. ## Configure Construct one client with the public API base URL. Pass a management Bearer credential (`ad_…`) when you call privileged endpoints. `AuthdogClient` implements `AutoCloseable`: ```java try (AuthdogClient client = new AuthdogClient( "https://api.authdog.com", System.getenv("AUTHDOG_API_TOKEN"))) { Probe probe = client.health(); } ``` Keep the token server-side. `getUserInfo` still uses the caller access token, not the management key. A third constructor argument is the timeout in milliseconds. Optional credentials `environmentSecret` (`adenv_`), `scimToken` (`adscim_`), and `hrisToken` (`adhris_`) are the AuthZEN/MCP runtime, SCIM, and HRIS Bearers. `health()` is public and works without an API key. ## Resolve a user from an access token ```java try (AuthdogClient client = new AuthdogClient("https://api.authdog.com")) { UserInfoResponse info = client.getUserInfo(accessToken); System.out.println(info.getUser().getDisplayName()); } catch (AuthenticationException e) { // 401: missing, invalid, or expired access token throw e; } catch (ApiException e) { // transport or non-401 HTTP failure throw e; } ``` `GET /v1/userinfo` always sends `Authorization: Bearer `. A constructor API key does not replace that header. ## Call the management API Methods on the client wrap Waves 1–5 of the public `/v1` surface: | Method | Resources | | --- | --- | | `organizations()` | Organizations, invitations, members, keys | | `tenants()` | Tenants, domains, seats | | `projects()` | Applications under a tenant | | `environments()` | Environment records | | `users()` | Directory users in a tenant + environment | | `groups()` | Groups and membership | | `rbac()` | Roles, permissions, resources, mappings, ABAC | | `audit()` | Administrative audit logs | | `events()` | Identity event stream | | `webhooks()` | Webhook subscriptions | | `notificationChannels()` | SIEM / notification channels | | `serviceAccounts()` | Service accounts | | `personalAccessTokens()` | PATs | | `apiSecrets()` | Environment API secrets | | `authzen()` | AuthZEN evaluate, search, and discovery | | `scim()` | SCIM 2.0 directory | | `hris()` | HRIS employees and departments | | `mcp()` | MCP runtime | | `otel()` | OpenTelemetry exporters | | `oidcClients()` | OIDC clients | | `actions()` | Environment actions | | `addons()` | Add-ons | | `billing()` | Billing | | `settings()` | Environment settings | | `elevate()` | Elevate | | `emailProviders()` | Email providers | | `featureFlags()` | Feature flags | | `forms()` | Forms | | `provisioningTokens()` | Provisioning tokens | | `impersonation()` | Impersonation | | `portal()` | Account portal | | `security()` | Security settings | | `threats()` | Threats | | `vanityDomains()` | Vanity domains | | `widgets()` | Widgets | | `smsProviders()` | SMS providers | | `connectedApps()` | Connected-app grants | AuthZEN discovery is unauthenticated. Evaluate, search, and the MCP runtime use `environmentSecret`. SCIM uses `scimToken`. HRIS uses `hrisToken`. OpenAPI at [`/v1/openapi`](https://api.authdog.com/v1/openapi) is the field-level contract. ## Errors | Exception | When | | --- | --- | | `AuthenticationException` | HTTP 401 | | `ApiException` | Other HTTP failures and transport errors | ## Other languages | Language | Guide | | --- | --- | | Python | [Python SDK](/docs/sdks/python) | | Node.js | [Node.js SDK](/docs/sdks/node) | | Go | [Go SDK](/docs/sdks/go) | | Rust | [Rust SDK](/docs/sdks/rust) | | C# | [C# SDK](/docs/sdks/csharp) | | Zig | [Zig SDK](/docs/sdks/zig) | ## Next - [API reference](/docs/api): auth, versioning, and resource families - [Backend requests](/docs/backend): validate sessions on incoming requests - [Users](/docs/users): directory model the `users` namespace talks to ### C# SDK Source: https://www.authdog.com/docs/sdks/csharp.md `Authdog.Sdk` 0.2.0 is the official C# client for the [Authdog REST API](/docs/api). The project targets `net8.0`. Source is [`csharp/`](https://github.com/authdog/sdk/tree/main/csharp) in [authdog/sdk](https://github.com/authdog/sdk). It is a management and userinfo client, not a web-framework session binding. NuGet.org does not host this package. ## Install Reference the project from a checkout of the monorepo: ```xml ``` Or pack it locally and add the nupkg: ```bash dotnet pack sdk/csharp/Authdog.Sdk.csproj ``` The package id is `Authdog.Sdk`. It depends on Newtonsoft.Json and Microsoft.Extensions.Http. ## Configure Construct one client with the public API base URL. Pass a management Bearer credential (`ad_…`) when you call privileged endpoints. Dispose the client when you are done: ```csharp using Authdog; using var client = new AuthdogClient( "https://api.authdog.com", Environment.GetEnvironmentVariable("AUTHDOG_API_TOKEN")); var probe = await client.HealthAsync(); ``` Keep the token server-side. `GetUserInfoAsync` still uses the caller access token, not the management key. A third constructor argument is a custom `HttpClient`. Optional credentials `environmentSecret` (`adenv_`), `scimToken` (`adscim_`), and `hrisToken` (`adhris_`) are the AuthZEN/MCP runtime, SCIM, and HRIS Bearers. `HealthAsync` is public and works without an API key. `Health()` is the synchronous wrapper. ## Resolve a user from an access token ```csharp using Authdog; using Authdog.Exceptions; try { var info = await client.GetUserInfoAsync(accessToken); Console.WriteLine(info.User.DisplayName); } catch (AuthenticationException) { // 401: missing, invalid, or expired access token throw; } catch (ApiException) { // transport or non-401 HTTP failure throw; } ``` `GetUserInfo` is the synchronous wrapper. `GET /v1/userinfo` always sends `Authorization: Bearer `. A constructor API key does not replace that header. ## Call the management API Properties on the client wrap Waves 1–5 of the public `/v1` surface: | Property | Resources | | --- | --- | | `Organizations` | Organizations, invitations, members, keys | | `Tenants` | Tenants, domains, seats | | `Projects` | Applications under a tenant | | `Environments` | Environment records | | `Users` | Directory users in a tenant + environment | | `Groups` | Groups and membership | | `Rbac` | Roles, permissions, resources, mappings, ABAC | | `Audit` | Administrative audit logs | | `Events` | Identity event stream | | `Webhooks` | Webhook subscriptions | | `NotificationChannels` | SIEM / notification channels | | `ServiceAccounts` | Service accounts | | `PersonalAccessTokens` | PATs | | `ApiSecrets` | Environment API secrets | | `Authzen` | AuthZEN evaluate, search, and discovery | | `Scim` | SCIM 2.0 directory | | `Hris` | HRIS employees and departments | | `Mcp` | MCP runtime | | `Otel` | OpenTelemetry exporters | | `OidcClients` | OIDC clients | | `Actions` | Environment actions | | `Addons` | Add-ons | | `Billing` | Billing | | `Settings` | Environment settings | | `Elevate` | Elevate | | `EmailProviders` | Email providers | | `FeatureFlags` | Feature flags | | `Forms` | Forms | | `ProvisioningTokens` | Provisioning tokens | | `Impersonation` | Impersonation | | `Portal` | Account portal | | `Security` | Security settings | | `Threats` | Threats | | `VanityDomains` | Vanity domains | | `Widgets` | Widgets | | `SmsProviders` | SMS providers | | `ConnectedApps` | Connected-app grants | AuthZEN discovery is unauthenticated. Evaluate, search, and the MCP runtime use `environmentSecret`. SCIM uses `scimToken`. HRIS uses `hrisToken`. OpenAPI at [`/v1/openapi`](https://api.authdog.com/v1/openapi) is the field-level contract. ## Errors | Exception | When | | --- | --- | | `AuthenticationException` | HTTP 401 | | `ApiException` | Other HTTP failures and transport errors | ## Other languages | Language | Guide | | --- | --- | | Python | [Python SDK](/docs/sdks/python) | | Node.js | [Node.js SDK](/docs/sdks/node) | | Go | [Go SDK](/docs/sdks/go) | | Rust | [Rust SDK](/docs/sdks/rust) | | Java | [Java SDK](/docs/sdks/java) | | Zig | [Zig SDK](/docs/sdks/zig) | ## Next - [API reference](/docs/api): auth, versioning, and resource families - [Backend requests](/docs/backend): validate sessions on incoming requests - [Users](/docs/users): directory model the `Users` namespace talks to ### Zig SDK Source: https://www.authdog.com/docs/sdks/zig.md `authdog` 0.1.0 is the official Zig client for the [Authdog REST API](/docs/api). Add it from the [authdog/sdk](https://github.com/authdog/sdk) monorepo. It is a management and userinfo client, not a web-framework session binding. Need to protect a web route? Use a [backend SDK](/docs/backend) for your server framework, or call `userinfo` yourself with this client. ## Install Add the dependency to `build.zig.zon`: ```zig .dependencies = .{ .authdog = .{ .url = "https://github.com/authdog/sdk/archive/refs/heads/main.tar.gz", .hash = "YOUR_PACKAGE_HASH", }, }, ``` Then in `build.zig`: ```zig const authdog_dep = b.dependency("authdog", .{ .target = target, .optimize = optimize, }); exe.root_module.addImport("authdog", authdog_dep.module("authdog")); ``` Pin a commit (or a tagged archive) and put the matching package hash in `build.zig.zon`. Requires Zig 0.16.0+. The SDK uses only the Zig standard library. Source: [`zig/`](https://github.com/authdog/sdk/tree/main/zig) in [authdog/sdk](https://github.com/authdog/sdk). ## Configure Construct one client with the public API base URL. Pass a management Bearer credential (`ad_…`) when you call privileged endpoints: ```zig const std = @import("std"); const authdog = @import("authdog"); var client = try authdog.AuthdogClient.init(allocator, .{ .base_url = "https://api.authdog.com", .api_key = std.posix.getenv("AUTHDOG_API_TOKEN"), .timeout_ms = 10_000, }); defer client.deinit(); ``` Keep the token server-side. `getUserInfo` still uses the caller access token, not the management key. `health()` is public and works without an API key: ```zig const probe = try client.health(); ``` Optional constructor fields `environment_secret` (`adenv_`), `scim_token` (`adscim_`), and `hris_token` (`adhris_`) are the AuthZEN/MCP runtime, SCIM, and HRIS Bearers. ## Resolve a user from an access token ```zig const user_info = client.getUserInfo(access_token) catch |err| { if (authdog.isAuthenticationError(err)) { // 401: missing, invalid, or expired access token return err; } if (authdog.isApiError(err)) { // transport or non-401 HTTP failure return err; } return err; }; defer user_info.deinit(); _ = user_info.user.display_name; if (user_info.user.emails.len > 0) { _ = user_info.user.emails[0].value; } ``` `GET /v1/userinfo` always sends `Authorization: Bearer `. A constructor API key does not replace that header. `UserInfoResponse` uses snake_case fields (`user.display_name`, `session.remaining_seconds`). ## Call the management API Namespaces wrap Waves 1–3 of the public `/v1` surface: | Attribute | Resources | | --- | --- | | `organizations` | Organizations, invitations, members, keys | | `tenants` | Tenants, domains, seats | | `projects` | Applications under a tenant | | `environments` | Environment records | | `users` | Directory users in a tenant + environment | | `groups` | Groups and membership | | `rbac` | Roles, permissions, resources, mappings, ABAC | | `audit` | Administrative audit logs | | `events` | Identity event stream | | `webhooks` | Webhook subscriptions | | `notificationChannels` | SIEM / notification channels | | `serviceAccounts` | Service accounts | | `personalAccessTokens` | PATs | | `apiSecrets` | Environment API secrets | | `authzen` | AuthZEN evaluate, search, and discovery | | `scim` | SCIM 2.0 directory | | `hris` | HRIS employees and departments | | `mcp` | MCP runtime | | `otel` | OpenTelemetry exporters | | `oidcClients` | OIDC clients | | `actions` | Environment actions | | `addons` | Add-ons | | `billing` | Billing | | `settings` | Environment settings | | `elevate` | Elevate | | `emailProviders` | Email providers | | `featureFlags` | Feature flags | | `forms` | Forms | | `provisioningTokens` | Provisioning tokens | | `impersonation` | Impersonation | | `portal` | Account portal | | `security` | Security settings | | `threats` | Threats | | `vanityDomains` | Vanity domains | | `widgets` | Widgets | ```zig const orgs = try client.organizations.list(); defer orgs.deinit(); const users = try client.users.list("ten_123", "env_456", .{ .limit = 25 }); defer users.deinit(); ``` Directory calls take `tenant_id` then `environment_id`. OpenAPI at [`/v1/openapi`](https://api.authdog.com/v1/openapi) is the field-level contract. ## Errors | Error | When | | --- | --- | | `AuthenticationFailed` | HTTP 401 | | `ApiError` | Other HTTP failures and transport errors | | `ParseError` | HTTP 200 with a body that is not valid user-info JSON | Use `isAuthenticationError` / `isApiError` to branch on type. Use `client.lastErrorMessage()` for the stable message text. ## Next - [API reference](/docs/api): auth, versioning, and resource families - [Backend requests](/docs/backend): validate sessions on incoming requests - [Python](/docs/sdks/python), [Node.js](/docs/sdks/node), [Go](/docs/sdks/go), [Rust](/docs/sdks/rust), [Java](/docs/sdks/java), and [C#](/docs/sdks/csharp): the same client in the other languages - [Users](/docs/users): directory model the `users` namespace talks to ## AI ### Using Authdog with AI Source: https://www.authdog.com/docs/ai.md Authdog is the identity provider. The agent framework keeps the auth hook it already has. You register the caller, mint a short-lived bearer, and verify that bearer before the agent, tool, or graph runs. Use this section when a person is not sitting in a browser session. An agent that signs into your product UI uses [Agent Sign-In](/docs/agent-sign-in). ## Start here | You are | Open | | --- | --- | | Letting a coding agent read Authdog docs | [Skills](/docs/ai/skills) | | Letting that agent change tenants, projects, or users | [Use Authdog's MCP server](/docs/ai/mcp-server) | | Putting Authdog on an agent framework | The framework group in the sidebar | | Building the MCP server your users' agents call | [Build an MCP server](/docs/ai/mcp-development/build-server) | | Connecting an MCP client to that server | [Connect an MCP client](/docs/ai/mcp-development/connect-client) | ## One bearer, the framework's hook Mint the access token once, with `client_credentials`. Present it on the hook the framework already exposes. The shared exchange, the userinfo result, and what to do after the subject is known are in [Agent frameworks](/docs/agent-frameworks). | Framework | Hook | Guide | | --- | --- | --- | | A2A | Agent card, then `require_auth` on the JSON-RPC mount | [A2A](/docs/agent-frameworks/a2a) | | Eve | Channel `auth` walk: `oidc({ issuer, audiences })` | [Eve](/docs/agent-frameworks/eve) | | MCP | `token_verifier` on your MCP server | [MCP agents](/docs/agent-frameworks/mcp) | | LangGraph | `require_auth` on `POST /invoke` | [LangGraph](/docs/agent-frameworks/langgraph) | | OpenAI Agents SDK | Tool `RunContext`, then `require_auth` on your API | [OpenAI Agents SDK](/docs/agent-frameworks/openai-agents) | | CrewAI | `EXECUTION_START` raises `HookAborted` | [CrewAI](/docs/agent-frameworks/crewai) | | Google ADK | `before_agent_callback` | [Google ADK](/docs/agent-frameworks/adk) | | Pydantic AI | `RunContext` deps, then `require_auth` on your API | [Pydantic AI](/docs/agent-frameworks/pydantic-ai) | | Claude Agent SDK | `PreToolUse` returns `permissionDecision: deny` | [Claude Agent SDK](/docs/agent-frameworks/claude-agent) | | Mastra | `server.auth.authenticateToken` | [Mastra](/docs/agent-frameworks/mastra) | | LlamaIndex Workflows | `require_auth`, then the first workflow step | [LlamaIndex Workflows](/docs/agent-frameworks/llamaindex) | | Semantic Kernel | `FUNCTION_INVOCATION` filter skips `next` | [Semantic Kernel](/docs/agent-frameworks/semantic-kernel) | | Vercel AI SDK | `toolsContext`, then `require_auth` on your API | [Vercel AI SDK](/docs/agent-frameworks/ai-sdk) | | Strands Agents | `BeforeToolCallEvent.cancel_tool` | [Strands Agents](/docs/agent-frameworks/strands) | | LangChain | `wrap_tool_call` does not call the handler | [LangChain](/docs/agent-frameworks/langchain) | Each group has the same four follow-ups: the auth boundary, machine credentials, tool checks, and what you put in the prompt. Getting started is the framework page. A page whose sample is still planned says so. The Agents module is self-serve. If the caller is a workload rather than a named agent, an [M2M application](/docs/machine-identity) issues the same `client_credentials` grant. ## Three different MCP servers | Name | What it is | | --- | --- | | Authdog's MCP server | `https://api.authdog.com/mcp`. Control-plane tools for the signed-in account. | | Your MCP server | The resource your users' agents call. You verify the bearer. | | MCP client login | Hosted OAuth so that client can obtain a user token. | ## Related | Read | To learn how to | | --- | --- | | [Agent frameworks](/docs/agent-frameworks) | Mint the bearer and read the userinfo result | | [Machine identity](/docs/machine-identity) | Choose M2M, Agents, a service account, or a PAT | | [Agents quickstart](/docs/quickstarts/agents) | Register an agent and mint the first token | | [AgentSec](/docs/agentsec) | See the full agent security story | ### Skills Source: https://www.authdog.com/docs/ai/skills.md A coding agent should read Authdog's published docs, then call the management MCP when it needs a live account. Paste those URLs into the agent's context, or let it fetch them. ## Docs the agent can fetch | URL | What the agent gets | | --- | --- | | `https://www.authdog.com/SKILL.md` | Skill for adding Authdog to an app. | | `https://www.authdog.com/llms.txt` | Index of the docs. Each link is raw Markdown. | | `https://www.authdog.com/llms-full.txt` | Those documents in one file. | | `https://www.authdog.com/instructions.txt` | What Authdog is, which claims to avoid, and where to read next. | `instructions.txt` outranks a web-search snippet. If the agent is about to invent a price, a certification, or an API, point it at that file first. Framework samples live in [authdog/agents-samples](https://github.com/authdog/agents-samples). Each folder is its own project. The matching getting-started page names the package pin and the auth hook. ## Live account work Reading the docs does not create a tenant. [Use Authdog's MCP server](/docs/ai/mcp-server) lets the agent list tenants, create a project, and read SDK config for the account you sign in with. The CLI registers that server for you: ```bash authdog mcp install --client cursor ``` `--client` accepts `cursor`, `claude`, `vscode`, or `codex`. `--all` writes every client that is installed. The command stores the server URL. It does not store an access token. ## What to hand the agent Give it the framework page you want it to follow, the sample folder, and the environment it may touch. Keep the client secret out of the prompt. The process that mints tokens can read the secret from the environment. The agent can ask you to confirm a mutation. It should not invent a tenant id, a project id, or a client id. ## Related | Read | To learn how to | | --- | --- | | [Use Authdog's MCP server](/docs/ai/mcp-server) | Connect the control-plane tools | | [Using Authdog with AI](/docs/ai) | Pick a framework hook or an MCP path | | [Agents quickstart](/docs/quickstarts/agents) | Register the identity the sample will mint | ### Use Authdog's MCP server Source: https://www.authdog.com/docs/ai/mcp-server.md Authdog's MCP server is the control plane for the account you sign in with. A coding agent can list tenants, create projects, set redirect URIs, and inspect users. It is not the MCP server your product exposes to its own agents. That server is [Build an MCP server](/docs/ai/mcp-development/build-server). The endpoint is `https://api.authdog.com/mcp` (streamable HTTP). ## Connect a client The CLI writes the URL into the client config. The client signs in when the server answers `401` with `WWW-Authenticate`. The CLI does not put a bearer token in that file. ```bash authdog mcp install --client cursor authdog mcp list authdog doctor --mcp ``` | Client | Flag | | --- | --- | | Cursor | `--client cursor` (this is the default) | | Claude Code | `--client claude` | | VS Code | `--client vscode` | | Codex | `--client codex` | | Every installed client | `--all` | `authdog mcp uninstall` removes the Authdog entry and leaves your other MCP servers in place. `authdog doctor --mcp` checks that the protected-resource metadata is reachable. Sign in with the Authdog account that should own the changes. The tools run as that person. ## How the agent should behave The server instructions tell the agent to show options and wait. A few rules are worth knowing before you let it click through: - It lists tenants, then projects, then fetches the project. It should not pick one for you, and it should not guess the project type from the name. Ask it whether you want web (OAuth app), m2m (machine-to-machine), or mcp (MCP server). - Mutations send `confirm=true` only after you confirm. Deletes need the exact name you expect. - After it creates a project, it should call `get_sdk_config` for the issuer, the client id, and the public key. The public key (`pk_...`) selects the identity host. It is not the OIDC `client_id`. - `create_client` creates a public PKCE client. It does not hand back a client secret. Redirect URIs come from you, not from a guess. Tools cover tenants, projects, environments, OIDC clients, redirect URIs, and end users. Two prompts are built in: `provision_web_app` for a web app wire-up, and `explore_tenant` to walk tenants and projects. ## What this server is not | Job | Where | | --- | --- | | Your product's MCP tools | [Build an MCP server](/docs/ai/mcp-development/build-server) | | A user signing an MCP client into your server | [Connect an MCP client](/docs/ai/mcp-development/connect-client) | | An agent calling your API with its own credential | [Agent frameworks](/docs/agent-frameworks) | | The console page for an MCP project | [MCP servers](/docs/console/mcp) | ## Related | Read | To learn how to | | --- | --- | | [Skills](/docs/ai/skills) | Give the agent the docs before it calls tools | | [MCP servers](/docs/console/mcp) | Register the MCP project your users call | | [Machine identity](/docs/machine-identity) | Issue a credential for an unattended workload | ### Custom channel auth for A2A Source: https://www.authdog.com/docs/ai/a2a/channel-auth.md A2A's auth boundary has two surfaces. The agent card is public and tells a client how to get a token. The JSON-RPC mount is the gate. Publishing the card does not protect `SendMessage`. Getting started, including the runnable sample, is [A2A](/docs/agent-frameworks/a2a). ## Declare Authdog on the card Build an OAuth 2 client-credentials scheme whose token URL is the environment `token_endpoint`. Attach it as security scheme `authdog` with scope `agent:invoke`. ```python flow = ClientCredentialsOAuthFlow(token_url=token_url) flow.scopes["agent:invoke"] = "Call this agent" scheme = SecurityScheme( oauth2_security_scheme=OAuth2SecurityScheme( description="Authdog agent identity (client credentials)", flows=OAuthFlows(client_credentials=flow), ) ) ``` The token URL is public configuration. The client secret never goes on the card. `GET /.well-known/agent-card.json` stays open so a client can discover the scheme. ## Require the bearer on JSON-RPC Construct `Authdog(public_key=...)` from `PK_AUTHDOG`. Wrap `/a2a` so `require_auth` runs before the executor. ```python class _Gate(BaseHTTPMiddleware): async def dispatch(self, request: Request, call_next): try: user = await authdog.require_auth(request) except HTTPException: return JSONResponse( {"error": "Unauthorized"}, status_code=401, headers={"WWW-Authenticate": "Bearer"}, ) request.state.authdog_subject = subject_of(user) return await call_next(request) ``` `subject_of` reads `user["id"]`, then `user["sub"]`. A missing bearer, a userinfo rejection, or a payload with no subject is `401`. The executor reads `authdog_subject` from request state. It does not read the raw `Authorization` header. `SendMessage` also requires `A2A-Version: 1.0`. A protocol error from a missing version header is separate from the `401`. Leave the client secret on the process that mints the token. The server process holds `PK_AUTHDOG` and the token URL. See [API keys and M2M for A2A](/docs/ai/a2a/api-keys-and-m2m). ## Related | Read | To learn how to | | --- | --- | | [A2A](/docs/agent-frameworks/a2a) | Run the sample and see the `401` | | [API keys and M2M for A2A](/docs/ai/a2a/api-keys-and-m2m) | Put the secret on the client, not the card | | [Authorizing tool calls for A2A](/docs/ai/a2a/authorize-tool-calls) | Decide what that subject may do | | [Agent frameworks](/docs/agent-frameworks) | Mint the bearer this gate checks | ### API keys and M2M for A2A Source: https://www.authdog.com/docs/ai/a2a/api-keys-and-m2m.md The A2A server never sees the client secret. The client exchanges `client_credentials` out of band and sends `Authorization: Bearer` on the HTTP request. The JSON-RPC body stays the message. The exchange itself is in [Agent frameworks](/docs/agent-frameworks#mint-the-bearer). This page is where that secret is allowed to live. ## Which credential | Credential | Use it when | | --- | --- | | Agent identity | A named agent inside an existing project. You want verify, revoke, allowed tools, and activity. The [Agents](/docs/console/agents) module is self-serve in every environment. | | M2M application | The caller is a backend, job, or CI workload, and Agents access is not approved yet. Same `client_credentials` grant. | | Personal access token | One person's local script. Keep it off the unattended A2A client. | | Service account | Organization-owned automation through the Authdog REST API, not this agent card. | Create the agent or the M2M application in the console and copy the client id and client secret. The secret is shown once. ## Where each value goes | Variable | Process | Purpose | | --- | --- | --- | | `AUTHDOG_CLIENT_ID` / `AUTHDOG_CLIENT_SECRET` | A2A client | HTTP basic auth on the token endpoint. | | `AUTHDOG_TOKEN_URL` | Client and server | Discovery on the card, and the URL the client calls. | | `PK_AUTHDOG` | Server | Public key. Selects the identity host for `require_auth`. | | Access token | HTTP `Authorization` header | What the gate checks. Mint again before `expires_in`. | There is no refresh token. The sample client performs one exchange per run. The same HTTP client can fetch the public card and send the message, so the bearer rides on both. Card fetch succeeds without a token. Message calls do not. `PK_AUTHDOG` does not mint tokens. A vault secret or an API key stored for something else is not this bearer. The gate calls userinfo with the access token from the exchange. ## Acting for a person Keep the same gate. Replace the `client_credentials` access token with an [RFC 8693 token](/guides/agent-delegation). `sub` stays the user. `act` names the agent. Grant token exchange on the agent in [Agents](/docs/console/agents). ## Related | Read | To learn how to | | --- | --- | | [Custom channel auth for A2A](/docs/ai/a2a/channel-auth) | See why the card is public and the mount is not | | [Machine identity](/docs/machine-identity) | Compare M2M, Agents, service account, and PAT | | [Agents quickstart](/docs/quickstarts/agents) | Register the client that mints the token | | [A2A](/docs/agent-frameworks/a2a) | Run the client that performs the exchange | ### Authorizing tool calls for A2A Source: https://www.authdog.com/docs/ai/a2a/authorize-tool-calls.md `require_auth` tells you who called `SendMessage`. It does not mean that subject may run every task the executor knows how to do. The sample stops once the subject is known and replies with `subject:`. ## What the card scope does The agent card advertises scope `agent:invoke` on the Authdog security scheme. That is how a client discovers the scope to request. The sample middleware does not read that scope. It admits the call when userinfo accepts the bearer and the payload has a subject. If a task needs a scope, check it in the executor after `authdog_subject` is on the request. Reject there, before the executor performs the task. A `401` from the middleware means "no accepted bearer." A later refusal means "this subject may not do this." ## What to check next The [Agents](/docs/console/agents) registry is the next check on that subject when the caller is an agent: | Control | Effect | | --- | --- | | Allowed scopes and tools | Globs on the trust-store row. `*` allows every tool. | | Trust-store enforce | Your server applies that row. This A2A sample does not. | | Revoke | New tokens fail when the subject is the OIDC client id. Tokens already issued last until `expires_in` unless you enforce the trust store. | Apply [authorization](/docs/concepts/authorization) in the executor when the task is yours: a role, a policy, or an AuthZEN decision. The public skill on the card (`whoami`, "Returns the Authdog subject of the caller") describes the sample. It is not an allow list. A `200` that contains `subject:` means userinfo accepted the bearer. Treat a missing subject, or a task the subject is not allowed to run, as a refusal the caller can see. Do not run the task and hide the denial inside a model reply. ## Related | Read | To learn how to | | --- | --- | | [Custom channel auth for A2A](/docs/ai/a2a/channel-auth) | Put `require_auth` in front of the executor | | [Enriching instructions for A2A](/docs/ai/a2a/enriching-instructions) | Return the subject without leaking the token | | [Agents](/docs/console/agents) | Set allowed tools and revoke the identity | | [Authorization](/docs/concepts/authorization) | Choose the check that runs after identity | ### Enriching instructions for A2A Source: https://www.authdog.com/docs/ai/a2a/enriching-instructions.md The A2A card is public. The model reply can include the subject. Neither one should include the access token or the client secret. ## What the sample publishes The card lists one skill: | Field | Value | | --- | --- | | id | `whoami` | | name | Who am I | | description | Returns the Authdog subject of the caller. | | tags | `identity` | That description is discovery. Write it as if anyone can read `/.well-known/agent-card.json`, because they can. Leave issuer secrets, client secrets, and example bearer tokens off the card. The executor replies with the text `subject:` taken from request state. It does not look at the `Authorization` header, so the token is not in the message the caller gets back. ## If you add a model Pass `authdog_subject` into the task context the model is allowed to see. Ask it to use that subject and to stop when the executor refused the task. Keep these out of the prompt, the skill description, and the message text: - `AUTHDOG_CLIENT_SECRET` - the raw access token - `PK_AUTHDOG`, which belongs on the server, not in a prompt Instructions that say "you are whoever the user claims" undo the gate. The subject comes from `require_auth`. The model does not choose it. ## Related | Read | To learn how to | | --- | --- | | [Authorizing tool calls for A2A](/docs/ai/a2a/authorize-tool-calls) | Refuse a task after the subject is known | | [A2A](/docs/agent-frameworks/a2a) | See the executor return `subject:` | | [API keys and M2M for A2A](/docs/ai/a2a/api-keys-and-m2m) | Keep the secret on the client process | | [Agent Sign-In](/docs/agent-sign-in) | Let an agent sign into your product UI | ### Custom channel auth for Eve Source: https://www.authdog.com/docs/ai/eve/channel-auth.md Eve authorizes inbound requests with a list of authenticators on the channel. Put Authdog's issuer and audience in `oidc()` and pass that function as the only entry. Session routes then require a bearer JWT Eve can verify from the issuer's discovery document. Getting started, including the runnable sample, is [Eve](/docs/agent-frameworks/eve). This is who may open a session. Outbound tool sign-in is a separate Eve concern and stays off this walk. ## The authenticator `agent/channels/authdog-oidc.mjs` reads the environment and returns Eve's OIDC authenticator: ```javascript import { oidc } from "eve/channels/auth"; export function authdogOidc() { const issuer = process.env.AUTHDOG_ISSUER; const audience = process.env.AUTHDOG_AUDIENCE; if (!issuer || !audience) { throw new Error("AUTHDOG_ISSUER and AUTHDOG_AUDIENCE are required"); } return oidc({ issuer, audiences: [audience], }); } ``` Mount it alone: ```typescript import { eveChannel } from "eve/channels/eve"; import { authdogOidc } from "./authdog-oidc.mjs"; export default eveChannel({ auth: [authdogOidc()], }); ``` `oidc()` verifies the bearer as a JWT against the issuer discovery document: signature, issuer, expiry, and audience. An opaque token fails this check. Mint a JWT from the same issuer, and set `AUTHDOG_AUDIENCE` to the `aud` that token carries. The Eve process holds no client secret and no `PK_AUTHDOG`. Identity lives on the channel. `defineAgent` stays the model. ## What else stays off the list Leave `localDev()`, `none()`, and another vendor's workload identity off `auth`. A laptop process is not an Authdog subject. The sample's test fails if the channel lists those next to `authdogOidc()`. Eve's health route does not use this walk. A public health check is expected. `POST /eve/v1/session` with no `Authorization` header returns `401`. ## Related | Read | To learn how to | | --- | --- | | [Eve](/docs/agent-frameworks/eve) | Run `npm test` and `npx eve dev` | | [API keys and M2M for Eve](/docs/ai/eve/api-keys-and-m2m) | Set issuer and audience from a real token | | [Authorizing tool calls for Eve](/docs/ai/eve/authorize-tool-calls) | Authorize the API a tool calls | | [Agent frameworks](/docs/agent-frameworks#eve-checks-the-jwt) | See why Eve verifies the JWT itself | ### API keys and M2M for Eve Source: https://www.authdog.com/docs/ai/eve/api-keys-and-m2m.md Eve verifies a JWT. It does not call userinfo, and it does not read the client secret. You mint an Authdog access token somewhere else, then send it as `Authorization: Bearer` on `POST /eve/v1/session`. The exchange is in [Agent frameworks](/docs/agent-frameworks#mint-the-bearer). Agent access tokens for the environment are JWTs. An opaque token fails `oidc()`. ## Which credential | Credential | Use it when | | --- | --- | | Agent identity | A named agent. The [Agents](/docs/console/agents) module is self-serve in every environment. | | M2M application | A backend or job, or Agents access is not approved yet. Same `client_credentials` grant, and the access token is still a JWT. | | Personal access token | One person's script. Keep it off the Eve process. | | Service account | Authdog REST automation. Not the bearer Eve's channel verifies. | ## Where each value goes | Variable | Process | Purpose | | --- | --- | --- | | `AUTHDOG_CLIENT_ID` / `AUTHDOG_CLIENT_SECRET` | The process that calls the token endpoint | Mint the JWT. Eve never reads these. | | `AUTHDOG_ISSUER` | Eve | Environment issuer, `https:///oidc/`. Must match the token's `iss`. | | `AUTHDOG_AUDIENCE` | Eve | Must match the token's `aud`. | | Access token | `Authorization` header on the session request | What `oidc()` verifies. | There is no refresh token. Repeat the exchange before `expires_in`. `npx eve dev` listens on port 2000 unless `PORT` is set. `PK_AUTHDOG` is not an Eve setting. Eve discovers signing keys from the issuer document. Putting the client secret in the Eve environment does not help `oidc()`, and it puts the secret next to the agent. ## Acting for a person Send an [RFC 8693 token](/guides/agent-delegation) in the same header. Eve still checks signature, issuer, expiry, and audience. `sub` on that token is the user, and `act` names the agent. The sample path mints a `client_credentials` token whose subject is the agent client. ## Related | Read | To learn how to | | --- | --- | | [Custom channel auth for Eve](/docs/ai/eve/channel-auth) | Mount `oidc()` as the only authenticator | | [Machine identity](/docs/machine-identity) | Choose the credential that mints the JWT | | [Eve](/docs/agent-frameworks/eve) | See the `401` when the bearer is missing | | [Agent delegation](/guides/agent-delegation) | Keep the user as `sub` | ### Authorizing tool calls for Eve Source: https://www.authdog.com/docs/ai/eve/authorize-tool-calls.md Eve's channel auth decides who may open a session. It does not decide which outbound tool that session may call. The sample stops at the session `401`. Tool authorization belongs on the API the tool calls. Outbound tool sign-in, where Eve itself fetches a provider token, is a separate Eve concern. This guide does not configure it. If your tool calls an Authdog-protected API, that API is the gate. ## Check the API the tool calls Put `require_auth` (or a `token_verifier`, if the tool is an MCP server) on that API. Pass the same bearer the caller already presented, or a token minted for that call. A missing bearer is `401` from the API. The Eve session can stay open and the tool can return that error to the model. | Check | Where it runs | | --- | --- | | JWT signature, issuer, expiry, audience | Eve channel, before the session starts | | Subject, from the verified token | The API, via userinfo or the JWT `sub` | | Allowed tools and scopes | Trust-store row for that subject, if you enforce it | | The specific action | [Authorization](/docs/concepts/authorization) on your API | The sample does not read allowed-tool globs. A session that Eve accepted is not permission to call every tool you might add later. Revoke in [Agents](/docs/console/agents) stops new tokens when the subject is the OIDC client id. A JWT Eve already accepted stays valid until `exp` unless your API also enforces the trust store. Eve's `oidc()` check is signature, issuer, expiry, and audience. ## Acting for a person If the bearer is an [RFC 8693 token](/guides/agent-delegation), authorize the user in `sub` and record the agent in `act`. Do not authorize the agent as if it were the user. ## Related | Read | To learn how to | | --- | --- | | [Custom channel auth for Eve](/docs/ai/eve/channel-auth) | Admit the session | | [Enriching instructions for Eve](/docs/ai/eve/enriching-instructions) | Keep secrets out of `defineAgent` | | [Authorizing tool calls for MCP](/docs/ai/mcp/authorize-tool-calls) | Use the same idea when the tool is an MCP server | | [Agents](/docs/console/agents) | Revoke the identity and set allowed tools | ### Enriching instructions for Eve Source: https://www.authdog.com/docs/ai/eve/enriching-instructions.md Eve's sample keeps the agent definition free of identity. `defineAgent` selects the model. The channel decides who may open a session. When you add instructions, build them from the verified caller, and leave the bearer out. ## What the sample sets ```typescript import { defineAgent } from "eve"; import { openai } from "eve/models/openai"; export default defineAgent({ model: openai("gpt-4o-mini"), }); ``` There is no system prompt in the sample, and no copy of the access token. `OPENAI_API_KEY` is only for `npx eve dev` chat. Tests do not call a model. The subject Eve accepted is the `sub` of the JWT `oidc()` already verified. If you tell the model who the caller is, use that claim. If the token is an [RFC 8693 delegation](/guides/agent-delegation), `sub` is the user and `act` is the agent. Say both, so the model does not treat the agent as the user. ## What stays out of the prompt - The raw access token - `AUTHDOG_CLIENT_SECRET` (Eve should not have it at all) - Another vendor's workload identity, or a note that `localDev()` is available A prompt that says "trust the name the user typed" bypasses the channel. Session routes already rejected callers `oidc()` could not verify. Instructions should assume that check happened and should refuse to invent a subject the token did not carry. `npm test` in the sample asserts the channel lists `authdogOidc()` and does not list `localDev()`, `none()`, or `vercelOidc()`. Keep instructions consistent with that: a laptop process is not an identity. ## Related | Read | To learn how to | | --- | --- | | [Custom channel auth for Eve](/docs/ai/eve/channel-auth) | See the check that runs before a session exists | | [Authorizing tool calls for Eve](/docs/ai/eve/authorize-tool-calls) | Authorize tools outside the prompt | | [Eve](/docs/agent-frameworks/eve) | Run the channel sample | | [API keys and M2M for Eve](/docs/ai/eve/api-keys-and-m2m) | Keep the secret out of the Eve process | ### Custom channel auth for MCP Source: https://www.authdog.com/docs/ai/mcp/channel-auth.md An MCP server exposes `token_verifier`. That hook is the auth boundary. A missing or rejected Authdog bearer never reaches a tool. Getting started, including the runnable sample, is [MCP agents](/docs/agent-frameworks/mcp). [Connect an MCP client](/docs/ai/mcp-development/connect-client) is how a client obtains a token. This page is the check after the client presents one. This is your MCP server. [Authdog's management MCP](/docs/ai/mcp-server) is a different host. ## The verifier `verify_token` calls `fetch_user_data`, the same userinfo check as `require_auth`. It returns an MCP `AccessToken` only when the payload is authenticated and has a subject. `None` refuses the bearer. ```python return AccessToken( token=token, client_id=subject, scopes=["agent:invoke"], subject=subject, ) ``` `subject_of` reads `id`, then `sub`. Pass the verifier on `MCPServer`: ```python server = MCPServer( name="authdog-mcp", token_verifier=AuthdogTokenVerifier(payload), auth=AuthSettings( issuer_url=issuer, resource_server_url=resource_url, required_scopes=["agent:invoke"], validate_token_resource=False, ), ) ``` `validate_token_resource=False` because this verifier checks the token by userinfo. It does not require an `aud` claim equal to the MCP resource URL. Set the audience on the Authdog client when you need that binding. The sample does not add a second JWT parser. `PK_AUTHDOG` selects the identity host. The server never sees the client secret. ## What you should see `POST /mcp` with no bearer returns `401`. The `initialize` method does not run as an authenticated session. Repeat with a bearer userinfo accepts, then call `whoami`. The text is the Authdog subject. The tool reads that subject from `get_access_token()`, which is the context the verifier filled in. It does not parse the header again. ## Related | Read | To learn how to | | --- | --- | | [MCP agents](/docs/agent-frameworks/mcp) | Run the sample | | [API keys and M2M for MCP](/docs/ai/mcp/api-keys-and-m2m) | Choose the token the client presents | | [Authorizing tool calls for MCP](/docs/ai/mcp/authorize-tool-calls) | Allow a tool after the verifier returns | | [Build an MCP server](/docs/ai/mcp-development/build-server) | Add the runtime guard beside this verifier | ### API keys and M2M for MCP Source: https://www.authdog.com/docs/ai/mcp/api-keys-and-m2m.md The MCP server holds `PK_AUTHDOG`. The caller holds the bearer. Whichever process mints a `client_credentials` token also holds the client secret. The sample's `token_verifier` accepts the token when userinfo does, whether the subject is an agent or a person. ## Two ways to get the bearer | Caller | How they get a token | | --- | --- | | Unattended agent | `client_credentials` at the environment token endpoint. The exchange is in [Agent frameworks](/docs/agent-frameworks#mint-the-bearer). | | MCP client acting for a person | Hosted OAuth with PKCE. See [Connect an MCP client](/docs/ai/mcp-development/connect-client). | | Agent acting for a person | An [RFC 8693 token](/guides/agent-delegation) in the same `Authorization` header. `sub` stays the user. `act` names the agent. | ## Which machine credential | Credential | Use it when | | --- | --- | | Agent identity | A named agent, with verify, revoke, and allowed tools. [Agents](/docs/console/agents) is self-serve in every environment. | | M2M application | A backend or job, or Agents access is not approved yet. Same grant. | | Personal access token | One person's local script. Keep it out of the MCP server process. | | Service account | Authdog REST automation. Not what `token_verifier` is written to consume. | There is no refresh token on the `client_credentials` response. Repeat the exchange before `expires_in`. User tokens from the hosted login follow the session lifetime and can be revoked from Connected Apps. ## Where each value goes | Variable | Process | | --- | --- | | `PK_AUTHDOG` | MCP server. Selects the identity host. | | `MCP_RESOURCE_URL` | MCP server. Passed to `AuthSettings`. Defaults to `http://127.0.0.1:8000` in the sample. | | `AUTHDOG_CLIENT_ID` / `AUTHDOG_CLIENT_SECRET` | The process that mints the agent token. The server does not read them. | | Access token | `Authorization` header on `POST /mcp`. | A vault API key is not this bearer. The verifier calls userinfo with the access token. ## Related | Read | To learn how to | | --- | --- | | [Custom channel auth for MCP](/docs/ai/mcp/channel-auth) | See what the verifier accepts | | [Machine identity](/docs/machine-identity) | Pick M2M, Agents, service account, or PAT | | [Connect an MCP client](/docs/ai/mcp-development/connect-client) | Let a user sign in through the MCP client | | [MCP agents](/docs/agent-frameworks/mcp) | Run `whoami` with the bearer | ### Authorizing tool calls for MCP Source: https://www.authdog.com/docs/ai/mcp/authorize-tool-calls.md `token_verifier` proves the bearer. The sample's `whoami` tool then returns `token.subject`. That `200` means userinfo accepted the caller. It does not mean the caller may invoke every tool on the server. ## What the sample enforces ```python @server.tool() def whoami() -> str: """Return the Authdog subject of the calling agent.""" token = get_access_token() if token is None or not token.subject: raise PermissionError("Unauthorized") return token.subject ``` `get_access_token()` is the context the verifier filled in. The tool raises if that context is missing. It does not check an allow list. The verifier records scope `agent:invoke` on the `AccessToken`. `AuthSettings.required_scopes` lists the same scope. A finer permission is a separate check. ## Allowed tools When the caller is an agent, the trust-store row can name allowed tools: | Control | Effect | | --- | --- | | Allowed scopes and tools | Globs on the trust-store row. `*` allows every tool. | | Trust-store enforce | The resource server applies that row. This Python sample does not. | | AuthZEN `MCP_TOOL` | A per-tool allow or deny after the token is valid. | | Revoke | New tokens fail when the subject is the OIDC client id. Outstanding JWTs last until `expires_in` unless you enforce the trust store. | Those checks ship in the TypeScript runtime guard described in [Build an MCP server](/docs/ai/mcp-development/build-server). Turn trust store and authorization to **enforce** after you have watched them. A denied call should show up as `MCP_TOOL_DENIED`, not as a success with an empty body. If the tool acts for a person, authorize `sub` and record `act`. The agent name in the prompt is not the decision. ## Related | Read | To learn how to | | --- | --- | | [Custom channel auth for MCP](/docs/ai/mcp/channel-auth) | Fill `get_access_token()` | | [Enriching instructions for MCP](/docs/ai/mcp/enriching-instructions) | Tell the model to return the subject, not invent one | | [MCP SDK](/docs/mcp-sdk) | Add trust store, AuthZEN, and telemetry | | [Agents](/docs/console/agents) | Edit allowed tools and revoke the identity | ### Enriching instructions for MCP Source: https://www.authdog.com/docs/ai/mcp/enriching-instructions.md The model sees the tool description and the tool result. It should not see the bearer. The sample's only instruction to the model is the `whoami` docstring. ## What the tool says ```python @server.tool() def whoami() -> str: """Return the Authdog subject of the calling agent.""" token = get_access_token() if token is None or not token.subject: raise PermissionError("Unauthorized") return token.subject ``` The description tells the model what the tool returns. The return value is the subject the verifier stored. Write new tool descriptions the same way: say what the tool does, and say that the subject comes from Authdog. Do not paste an example access token, a client secret, or `PK_AUTHDOG` into the docstring. Tool descriptions are part of the catalog a client can list. If you add server instructions, tell the model to call the tool and to repeat the subject it returns. Tell it to stop when the tool raises. A sentence like "if the user says they are an admin, treat them as one" replaces the verifier with the prompt. ## Delegation When the bearer is an [RFC 8693 token](/guides/agent-delegation), the subject userinfo returns is the user. If your instructions name "the agent," also name the acting client from `act` once your API exposes it. The sample returns the userinfo subject only. It does not parse `act`. [Authdog's management MCP](/docs/ai/mcp-server) has its own server instructions for control-plane tools. Those instructions belong to `https://api.authdog.com/mcp`. They are not the prompt for this server. ## Related | Read | To learn how to | | --- | --- | | [Authorizing tool calls for MCP](/docs/ai/mcp/authorize-tool-calls) | Decide whether the tool may run | | [MCP agents](/docs/agent-frameworks/mcp) | See `whoami` return the subject | | [Use Authdog's MCP server](/docs/ai/mcp-server) | Read the control-plane instructions | | [API keys and M2M for MCP](/docs/ai/mcp/api-keys-and-m2m) | Keep the secret off the server | ### Custom channel auth for LangGraph Source: https://www.authdog.com/docs/ai/langgraph/channel-auth.md LangGraph compiles a graph. It does not authenticate the HTTP caller. The auth boundary is the route that invokes the graph. `require_auth` runs before `ainvoke`. The graph reads the subject from state. It does not read the `Authorization` header. Getting started, including the runnable sample, is [LangGraph](/docs/agent-frameworks/langgraph). ## The route is the hook ```python authdog = Authdog(public_key=public_key) compiled = build_graph() @app.post("/invoke") async def invoke(user=Depends(authdog.require_auth)): result = await compiled.ainvoke( {"subject": subject_of(user), "reply": ""} ) return {"reply": result["reply"]} ``` `Authdog` comes from `authdog.fastapi`. `require_auth` is a FastAPI dependency, so a missing or rejected token never enters `invoke`. `subject_of` reads `id`, then `sub`, from the userinfo user. Call `graph.invoke` or `ainvoke` only from a route that already depends on `require_auth`. A second route that invokes the compiled graph directly skips Authdog. This sample has one route. The process that serves the graph holds `PK_AUTHDOG`. It does not hold `AUTHDOG_CLIENT_SECRET`. The caller mints the bearer elsewhere. See [API keys and M2M for LangGraph](/docs/ai/langgraph/api-keys-and-m2m). ## What you should see `POST /invoke` with no token returns `401`. The body does not contain `subject:`. The same call with a bearer userinfo accepts returns `{"reply":"subject:"}`. ## Related | Read | To learn how to | | --- | --- | | [LangGraph](/docs/agent-frameworks/langgraph) | Run the sample | | [API keys and M2M for LangGraph](/docs/ai/langgraph/api-keys-and-m2m) | Mint the bearer the route expects | | [Authorizing tool calls for LangGraph](/docs/ai/langgraph/authorize-tool-calls) | Stop a node the subject may not run | | [Enriching instructions for LangGraph](/docs/ai/langgraph/enriching-instructions) | Pass the subject in state | ### API keys and M2M for LangGraph Source: https://www.authdog.com/docs/ai/langgraph/api-keys-and-m2m.md The LangGraph process holds the public key. The caller holds the access token and, if it minted that token, the client secret. `POST /invoke` expects `Authorization: Bearer`. The exchange is in [Agent frameworks](/docs/agent-frameworks#mint-the-bearer). This server does not take `AUTHDOG_CLIENT_SECRET`. ## Which credential | Credential | Use it when | | --- | --- | | Agent identity | A named agent inside a project that already has users. [Agents](/docs/console/agents) is self-serve in every environment. | | M2M application | The caller is a job or another backend, or Agents access is not approved yet. Same `client_credentials` grant. | | Personal access token | One person's script. Keep it off an unattended caller. | | Service account | Authdog REST automation. Not the bearer `require_auth` checks here. | ## Where each value goes | Variable | Process | Purpose | | --- | --- | --- | | `PK_AUTHDOG` | The process that serves `/invoke` | `Authdog(public_key=...)`. Selects the identity host. | | `AUTHDOG_CLIENT_ID` / `AUTHDOG_CLIENT_SECRET` | The process that calls the token endpoint | Mint the access token. | | Access token | `Authorization` header | What `require_auth` sends to userinfo. | There is no refresh token. Repeat the exchange before `expires_in`. `PK_AUTHDOG` does not mint tokens. A vault API key is not this bearer. To act for a person, keep the same route and put an [RFC 8693 token](/guides/agent-delegation) in the header. `sub` stays the user. `act` names the agent. The sample mints a token whose subject is the agent client. ## Related | Read | To learn how to | | --- | --- | | [Custom channel auth for LangGraph](/docs/ai/langgraph/channel-auth) | Keep `ainvoke` behind `require_auth` | | [Machine identity](/docs/machine-identity) | Choose the credential | | [LangGraph](/docs/agent-frameworks/langgraph) | Call `/invoke` and read `subject:` | | [OpenAI Agents SDK](/docs/agent-frameworks/openai-agents) | Present this same token from a tool | ### Authorizing tool calls for LangGraph Source: https://www.authdog.com/docs/ai/langgraph/authorize-tool-calls.md The route admits a caller. Each node still has to respect what that subject may do. The sample graph has one node, `respond`, and it only formats `subject:`. It does not call another tool. ## Read the subject from state ```python def respond(state: State) -> dict[str, str]: return {"reply": f"subject:{state['subject']}"} ``` `state["subject"]` was copied from userinfo inside the route. A node that needs a privileged action should see that string and refuse when the subject is missing or not allowed. Do not re-read the `Authorization` header inside the node. The graph was compiled once and can be invoked from anywhere. The header is only present on the HTTP request, and only the route is supposed to see it. If you add a tool node, treat it like any other API call: | Check | Where | | --- | --- | | Bearer accepted | `require_auth` on `/invoke`, before `ainvoke` | | Allowed tools | Trust-store row, if your process enforces it. This sample does not. | | The action itself | [Authorization](/docs/concepts/authorization) in the node, using `state["subject"]` | A reply of `subject:` means the route accepted the bearer. It is not an allow list. Revoke in [Agents](/docs/console/agents) stops new tokens when the subject is the OIDC client id. Tokens already issued last until `expires_in` unless the route's userinfo check, or a trust-store check you add, rejects them. When the token is an [RFC 8693 delegation](/guides/agent-delegation), put the user id in `subject` and keep the acting agent beside it if the node needs both. Authorizing the agent as the user skips the delegation. ## Related | Read | To learn how to | | --- | --- | | [Custom channel auth for LangGraph](/docs/ai/langgraph/channel-auth) | Stop unauthenticated invokes | | [Enriching instructions for LangGraph](/docs/ai/langgraph/enriching-instructions) | Give a model the subject from state | | [Authorization](/docs/concepts/authorization) | Pick the check the node should call | | [Agents](/docs/console/agents) | Revoke the caller and set allowed tools | ### Enriching instructions for LangGraph Source: https://www.authdog.com/docs/ai/langgraph/enriching-instructions.md The sample graph has no model and no system prompt. `reply` is `subject:` plus the id the route already verified. If you add a model node, feed it that state. Leave the bearer in the HTTP header. ## What the graph can see ```python class State(TypedDict): subject: str reply: str ``` The route sets `subject` from userinfo, then calls `ainvoke`. A model node should read `state["subject"]` and should be told not to invent a different one. If the node calls a tool, the tool's permission check uses that same subject. The prompt does not grant the tool. Keep the access token and `AUTHDOG_CLIENT_SECRET` out of state. The secret never belonged on this process. The token was consumed by `require_auth`. Copying it into state puts it in checkpoints, traces, and logs. When the caller is acting for a person, `subject` is the user from the [delegation token](/guides/agent-delegation). If the model needs to know an agent was involved, pass `act` as its own field. Do not overwrite `subject` with the agent id and then tell the model it is the user. ## A prompt that matches the gate Tell the model the subject is already decided, and that a node which returns an authorization error should be repeated to the caller as a refusal. A prompt that says "ask the user who they are" ignores the route. The route already returned `401` when userinfo did not accept the bearer. ## Related | Read | To learn how to | | --- | --- | | [Authorizing tool calls for LangGraph](/docs/ai/langgraph/authorize-tool-calls) | Refuse work inside a node | | [Custom channel auth for LangGraph](/docs/ai/langgraph/channel-auth) | Copy the subject into state | | [LangGraph](/docs/agent-frameworks/langgraph) | See the `reply` the sample returns | | [API keys and M2M for LangGraph](/docs/ai/langgraph/api-keys-and-m2m) | Keep the secret on the caller | ### Custom channel auth for OpenAI Agents SDK Source: https://www.authdog.com/docs/ai/openai-agents/channel-auth.md The OpenAI Agents SDK tool is the caller. It reads an access token from run context and sends `Authorization: Bearer` to your API. `require_auth` on that API is the auth boundary. The tool does not decide who the caller is. Getting started, including the runnable sample, is [OpenAI Agents SDK](/docs/agent-frameworks/openai-agents). ## Hold the token in context ```python @dataclass(frozen=True) class Caller: token: str @function_tool async def whoami(ctx: RunContextWrapper[Caller]) -> str: """Return the Authdog subject for this caller. Do not invent one.""" return await fetch_subject(ctx.context.token, client, url) ``` `whoami` sits inside `build_agent`. `client` and `url` come from that function. The full sample is on [OpenAI Agents SDK](/docs/agent-frameworks/openai-agents). Run it with `context=Caller(token=...)` passed to `Runner.run`. `fetch_subject` sets the header only when `token` is non-empty. An empty token still hits the API, and the API returns `401`. ## The route is the gate ```python authdog = Authdog(public_key=public_key) @app.get("/whoami") async def whoami(user=Depends(authdog.require_auth)): return {"subject": subject_of(user)} ``` `Authdog` comes from `authdog.fastapi`. A missing or rejected token is `401` from this route, including when the tool sends an empty header. The model sees the tool result, which is the subject string the API returned, or the error from a rejected call. The API process holds `PK_AUTHDOG`. The agent process holds `AUTHDOG_ACCESS_TOKEN`. The client secret stays in the process that minted the token. See [API keys and M2M for the OpenAI Agents SDK](/docs/ai/openai-agents/api-keys-and-m2m). ## Related | Read | To learn how to | | --- | --- | | [OpenAI Agents SDK](/docs/agent-frameworks/openai-agents) | Run the tool and the route | | [API keys and M2M for the OpenAI Agents SDK](/docs/ai/openai-agents/api-keys-and-m2m) | Fill `Caller.token` | | [Authorizing tool calls for the OpenAI Agents SDK](/docs/ai/openai-agents/authorize-tool-calls) | Let the API refuse, and let the model repeat that | | [LangGraph](/docs/agent-frameworks/langgraph) | Protect the workflow this tool calls | ### API keys and M2M for OpenAI Agents SDK Source: https://www.authdog.com/docs/ai/openai-agents/api-keys-and-m2m.md `Caller.token` is an access token. It is not the client secret. The process that calls the token endpoint holds the secret, puts the access token in the environment the agent reads, and leaves the secret out of `Runner.run`. The exchange is in [Agent frameworks](/docs/agent-frameworks#mint-the-bearer). ## Which credential | Credential | Use it when | | --- | --- | | Agent identity | A named agent. [Agents](/docs/console/agents) is self-serve in every environment. | | M2M application | A job or backend, or Agents access is not approved yet. Same `client_credentials` grant. | | Personal access token | One person's script. Keep it out of `Caller` on an unattended agent. | | Service account | Authdog REST automation. Not what `/whoami` verifies. | ## Where each value goes | Variable | Process | Purpose | | --- | --- | --- | | `AUTHDOG_CLIENT_ID` / `AUTHDOG_CLIENT_SECRET` | Minting process | Token endpoint. Not `Caller`. | | `AUTHDOG_ACCESS_TOKEN` | Agent | `Caller(token=...)`. | | `WHOAMI_URL` | Agent | Absolute URL of `GET /whoami`. | | `PK_AUTHDOG` | API | `require_auth`. | | `OPENAI_API_KEY` | Agent | Only for `Runner.run`. Proving the tool attaches the header does not need a model. | There is no refresh token. Replace `AUTHDOG_ACCESS_TOKEN` before `expires_in`. Putting the client secret in `Caller` or in the agent instructions sends it to the model and to your API as if it were a bearer. The API will reject it. The model will have seen it. To act for a person, keep this route and this tool. Put an [RFC 8693 token](/guides/agent-delegation) in `Caller.token` so `sub` stays the user and `act` names the agent. ## Related | Read | To learn how to | | --- | --- | | [Custom channel auth for the OpenAI Agents SDK](/docs/ai/openai-agents/channel-auth) | See the tool attach the header | | [Machine identity](/docs/machine-identity) | Choose the credential that mints the token | | [Agents quickstart](/docs/quickstarts/agents) | Register the client | | [OpenAI Agents SDK](/docs/agent-frameworks/openai-agents) | Run `GET /whoami` | ### Authorizing tool calls for OpenAI Agents SDK Source: https://www.authdog.com/docs/ai/openai-agents/authorize-tool-calls.md The model can choose to call `whoami`. It cannot choose the subject. The API returns `401` when the bearer is missing or rejected, and `{"subject":""}` when userinfo accepts it. The tool returns that result to the model. ## What the sample checks The route depends on `require_auth`. The tool copies `Caller.token` into the `Authorization` header and returns the JSON `subject`, or raises when the response is not successful. `pytest` covers the route and `fetch_subject`. It does not call `Runner`. That is the whole permission check in the sample. A subject in the tool result means userinfo accepted this token. If you add a tool that refunds an order or deletes a user, that tool's API has to authorize the action. Returning the error string to the model is how the agent learns the call was refused. Swallowing the `401` and inventing a success is the failure mode the docstring is written to prevent. | Check | Where | | --- | --- | | Bearer present and accepted | `GET /whoami`, or whatever route the tool calls | | Allowed tools for an agent | Trust-store row, if that API enforces it. This sample does not. | | The action | [Authorization](/docs/concepts/authorization) on the route, after `require_auth` | Revoke in [Agents](/docs/console/agents) makes the next token exchange fail when the subject is the OIDC client id. The access token already sitting in `Caller` lasts until `expires_in` unless the API rejects it. When `Caller.token` is an [RFC 8693 token](/guides/agent-delegation), authorize the user in `sub`. The tool does not get to promote the agent to that user. ## Related | Read | To learn how to | | --- | --- | | [Custom channel auth for the OpenAI Agents SDK](/docs/ai/openai-agents/channel-auth) | Put `require_auth` on the route | | [Enriching instructions for the OpenAI Agents SDK](/docs/ai/openai-agents/enriching-instructions) | Tell the model not to invent a subject | | [Authorization](/docs/concepts/authorization) | Add the check the next tool needs | | [LangGraph](/docs/ai/langgraph/authorize-tool-calls) | Apply the same idea inside a graph node | ### Enriching instructions for OpenAI Agents SDK Source: https://www.authdog.com/docs/ai/openai-agents/enriching-instructions.md The agent instructions in the sample are one sentence: call `whoami`, and never invent a subject. The token stays in `Caller`. The model sees the tool result. ## What the model is told ```python return Agent( name="authdog-caller", instructions="Call whoami. Never invent a subject.", tools=[whoami], ) ``` The tool docstring says the same thing: "Return the Authdog subject for this caller. Do not invent one." That is the enrichment. The subject is not written into the prompt in advance, because the prompt does not know it yet. `whoami` asks the API, and the API's answer is what the model may repeat. If you add a sentence about who the caller is, take it from that result. Do not take it from the user message. ## What stays out of the instructions - `Caller.token` and `AUTHDOG_ACCESS_TOKEN` - `AUTHDOG_CLIENT_SECRET` - `PK_AUTHDOG` - `OPENAI_API_KEY` `Runner.run` needs `OPENAI_API_KEY` in the agent process environment. It does not need that key inside the instructions string. The same goes for the Authdog token: `context=Caller(token=os.environ["AUTHDOG_ACCESS_TOKEN"])` passes it to the tool, not to the prompt. If the API returns `401`, the instructions should leave that as a refusal. A follow-up sentence that says "if whoami fails, ask the user for their id and continue" teaches the model to skip the gate. When the token is a [delegation token](/guides/agent-delegation), the subject string is the user. Say so in the instructions if the model must distinguish the user from the agent. The sample does not. It prints the subject the API returned. ## Related | Read | To learn how to | | --- | --- | | [Authorizing tool calls for the OpenAI Agents SDK](/docs/ai/openai-agents/authorize-tool-calls) | Let the API be the one that refuses | | [API keys and M2M for the OpenAI Agents SDK](/docs/ai/openai-agents/api-keys-and-m2m) | Keep the secret out of `Caller` | | [OpenAI Agents SDK](/docs/agent-frameworks/openai-agents) | See the tool and the route together | | [Custom channel auth for the OpenAI Agents SDK](/docs/ai/openai-agents/channel-auth) | Pass `Caller` into `Runner.run` | ### Custom channel auth for CrewAI Source: https://www.authdog.com/docs/ai/crewai/channel-auth.md Abort kickoff at `EXECUTION_START` when userinfo rejects the bearer. `HookAborted` is the refusal. A missing token takes the same path. Getting started, including the planned sample, is [CrewAI](/docs/agent-frameworks/crewai). ## What fails closed Kickoff with no token raises `HookAborted`. No tool result contains a subject. Kickoff with a bearer userinfo accepts stores that subject and continues. `whoami` returns it. A resolved subject is not authorization. Scopes and allowed tools are a later check. The [trust-store sample](https://github.com/authdog/agents-samples/tree/main/trust-store/python) is that later check for MCP. This page stops at admission. ## Related | Read | To learn how to | | --- | --- | | [CrewAI](/docs/agent-frameworks/crewai) | See the hook | | [API keys and M2M for CrewAI](/docs/ai/crewai/api-keys-and-m2m) | Where the secret is allowed to live | | [Authorizing tool calls for CrewAI](/docs/ai/crewai/authorize-tool-calls) | What happens after the subject is known | ### API keys and M2M for CrewAI Source: https://www.authdog.com/docs/ai/crewai/api-keys-and-m2m.md The hook reads `authdog_token` from crew inputs, then removes that key before tasks render. The process that calls the token endpoint holds the client secret. This crew does not. The exchange is in [Agent frameworks](/docs/agent-frameworks#mint-the-bearer). ## Which credential | Credential | Use it when | | --- | --- | | Agent identity | A named agent inside a project that already has users. [Agents](/docs/console/agents) is self-serve in every environment. | | M2M application | The caller is a job, or Agents access is not approved yet. Same `client_credentials` grant. | | Personal access token | One person's script. Keep it off an unattended caller. | | Service account | Authdog REST automation. Not the bearer this hook checks. | To act for a person, keep this hook and present an [RFC 8693 token](/guides/agent-delegation). `sub` stays the user. `act` names the agent. The planned sample uses a token whose subject is the agent. ## Related | Read | To learn how to | | --- | --- | | [Custom channel auth for CrewAI](/docs/ai/crewai/channel-auth) | Refuse a missing bearer | | [Machine identity](/docs/machine-identity) | Choose the credential | | [CrewAI](/docs/agent-frameworks/crewai) | See the hook | ### Authorizing tool calls for CrewAI Source: https://www.authdog.com/docs/ai/crewai/authorize-tool-calls.md A crew that started has a subject. That is not an allow list. A tool the subject may not call still has to be refused after this hook, the way the trust-store sample refuses `charge`. ## After the subject is known | Check | Where | | --- | --- | | Bearer accepted | The hook on [CrewAI](/docs/agent-frameworks/crewai) | | Allowed tools | Trust-store row, when your process enforces it | | The action itself | [Authorization](/docs/concepts/authorization) using the subject | Revoke in [Agents](/docs/console/agents) stops new tokens when the subject is the OIDC client id. Tokens already issued last until `expires_in` unless a later check rejects them. When the token is an [RFC 8693 delegation](/guides/agent-delegation), the person stays the subject and the agent stays the actor. Authorizing the agent as the user skips the delegation. ## Related | Read | To learn how to | | --- | --- | | [Custom channel auth for CrewAI](/docs/ai/crewai/channel-auth) | Admit the caller | | [Enriching instructions for CrewAI](/docs/ai/crewai/enriching-instructions) | What the model is allowed to see | | [Authorization](/docs/concepts/authorization) | Pick the check after the subject | ### Enriching instructions for CrewAI Source: https://www.authdog.com/docs/ai/crewai/enriching-instructions.md Leave the bearer out of task descriptions. Naming the subject is fine. Interpolating `authdog_token` into a task puts the bearer in the prompt, which this hook is written to prevent. ## What the model can see The model can see the subject the hook already verified, and a refusal when the hook stopped the call. It cannot see the access token or the client secret. A prompt that asks the model who the caller is ignores the hook. The hook already returned unauthorized when userinfo did not accept the bearer. Do not copy the token into graph state, crew inputs that tasks render, callback state the model reads, or telemetry context. ## Related | Read | To learn how to | | --- | --- | | [Authorizing tool calls for CrewAI](/docs/ai/crewai/authorize-tool-calls) | Refuse work after the subject is known | | [Custom channel auth for CrewAI](/docs/ai/crewai/channel-auth) | Keep the bearer on the hook | | [CrewAI](/docs/agent-frameworks/crewai) | See the planned sample | | [API keys and M2M for CrewAI](/docs/ai/crewai/api-keys-and-m2m) | Keep the secret on the caller | ### Custom channel auth for Google ADK Source: https://www.authdog.com/docs/ai/adk/channel-auth.md `before_agent_callback` returns content and skips the agent when the bearer is missing or userinfo rejects it. Returning `None` is the only path into the agent body. Getting started, including the planned sample, is [Google ADK](/docs/agent-frameworks/adk). ## What fails closed A callback with no token, or a token userinfo rejects, returns content whose text is `unauthorized`. The agent body does not run. A callback with a bearer userinfo accepts stores the subject and returns `None`. A resolved subject is not authorization. Scopes and allowed tools are a later check. The [trust-store sample](https://github.com/authdog/agents-samples/tree/main/trust-store/python) is that later check for MCP. This page stops at admission. ## Related | Read | To learn how to | | --- | --- | | [Google ADK](/docs/agent-frameworks/adk) | See the hook | | [API keys and M2M for Google ADK](/docs/ai/adk/api-keys-and-m2m) | Where the secret is allowed to live | | [Authorizing tool calls for Google ADK](/docs/ai/adk/authorize-tool-calls) | What happens after the subject is known | ### API keys and M2M for Google ADK Source: https://www.authdog.com/docs/ai/adk/api-keys-and-m2m.md Keep the token in callback state for the duration of the check, then clear it. The client secret stays in the process that minted the token. Do not put either value in `AuthCredential`. That type is how a tool calls some other API. The exchange is in [Agent frameworks](/docs/agent-frameworks#mint-the-bearer). ## Which credential | Credential | Use it when | | --- | --- | | Agent identity | A named agent inside a project that already has users. [Agents](/docs/console/agents) is self-serve in every environment. | | M2M application | The caller is a job, or Agents access is not approved yet. Same `client_credentials` grant. | | Personal access token | One person's script. Keep it off an unattended caller. | | Service account | Authdog REST automation. Not the bearer this hook checks. | To act for a person, keep this hook and present an [RFC 8693 token](/guides/agent-delegation). `sub` stays the user. `act` names the agent. The planned sample uses a token whose subject is the agent. ## Related | Read | To learn how to | | --- | --- | | [Custom channel auth for Google ADK](/docs/ai/adk/channel-auth) | Refuse a missing bearer | | [Machine identity](/docs/machine-identity) | Choose the credential | | [Google ADK](/docs/agent-frameworks/adk) | See the hook | ### Authorizing tool calls for Google ADK Source: https://www.authdog.com/docs/ai/adk/authorize-tool-calls.md `before_tool_callback` can skip one tool by returning a dict. The agent callback is the admission check. Skipping one tool does not decide the rest. An allow list is a later check on the subject. ## After the subject is known | Check | Where | | --- | --- | | Bearer accepted | The hook on [Google ADK](/docs/agent-frameworks/adk) | | Allowed tools | Trust-store row, when your process enforces it | | The action itself | [Authorization](/docs/concepts/authorization) using the subject | Revoke in [Agents](/docs/console/agents) stops new tokens when the subject is the OIDC client id. Tokens already issued last until `expires_in` unless a later check rejects them. When the token is an [RFC 8693 delegation](/guides/agent-delegation), the person stays the subject and the agent stays the actor. Authorizing the agent as the user skips the delegation. ## Related | Read | To learn how to | | --- | --- | | [Custom channel auth for Google ADK](/docs/ai/adk/channel-auth) | Admit the caller | | [Enriching instructions for Google ADK](/docs/ai/adk/enriching-instructions) | What the model is allowed to see | | [Authorization](/docs/concepts/authorization) | Pick the check after the subject | ### Enriching instructions for Google ADK Source: https://www.authdog.com/docs/ai/adk/enriching-instructions.md Do not put the bearer in the user message. The callback already decided. If a model instruction needs an id, use the subject that userinfo returned. ## What the model can see The model can see the subject the hook already verified, and a refusal when the hook stopped the call. It cannot see the access token or the client secret. A prompt that asks the model who the caller is ignores the hook. The hook already returned unauthorized when userinfo did not accept the bearer. Do not copy the token into graph state, crew inputs that tasks render, callback state the model reads, or telemetry context. ## Related | Read | To learn how to | | --- | --- | | [Authorizing tool calls for Google ADK](/docs/ai/adk/authorize-tool-calls) | Refuse work after the subject is known | | [Custom channel auth for Google ADK](/docs/ai/adk/channel-auth) | Keep the bearer on the hook | | [Google ADK](/docs/agent-frameworks/adk) | See the planned sample | | [API keys and M2M for Google ADK](/docs/ai/adk/api-keys-and-m2m) | Keep the secret on the caller | ### Custom channel auth for Pydantic AI Source: https://www.authdog.com/docs/ai/pydantic-ai/channel-auth.md The tool reads the token from `RunContext`. `require_auth` on your API is the gate. Pydantic AI does not verify the bearer itself. Getting started, including the planned sample, is [Pydantic AI](/docs/agent-frameworks/pydantic-ai). ## What fails closed `GET /whoami` with no token is `401`. The helper, given an empty token, does not return a subject. The same call with a bearer userinfo accepts returns `{"subject":""}`. The tool result is that string. A resolved subject is not authorization. Scopes and allowed tools are a later check. The [trust-store sample](https://github.com/authdog/agents-samples/tree/main/trust-store/python) is that later check for MCP. This page stops at admission. ## Related | Read | To learn how to | | --- | --- | | [Pydantic AI](/docs/agent-frameworks/pydantic-ai) | See the hook | | [API keys and M2M for Pydantic AI](/docs/ai/pydantic-ai/api-keys-and-m2m) | Where the secret is allowed to live | | [Authorizing tool calls for Pydantic AI](/docs/ai/pydantic-ai/authorize-tool-calls) | What happens after the subject is known | ### API keys and M2M for Pydantic AI Source: https://www.authdog.com/docs/ai/pydantic-ai/api-keys-and-m2m.md Put the access token in `deps`. Leave the client secret in the process that minted it. `OPENAI_API_KEY` is only for a live `agent.run`. The sample test does not need it. The exchange is in [Agent frameworks](/docs/agent-frameworks#mint-the-bearer). ## Which credential | Credential | Use it when | | --- | --- | | Agent identity | A named agent inside a project that already has users. [Agents](/docs/console/agents) is self-serve in every environment. | | M2M application | The caller is a job, or Agents access is not approved yet. Same `client_credentials` grant. | | Personal access token | One person's script. Keep it off an unattended caller. | | Service account | Authdog REST automation. Not the bearer this hook checks. | To act for a person, keep this hook and present an [RFC 8693 token](/guides/agent-delegation). `sub` stays the user. `act` names the agent. The planned sample uses a token whose subject is the agent. ## Related | Read | To learn how to | | --- | --- | | [Custom channel auth for Pydantic AI](/docs/ai/pydantic-ai/channel-auth) | Refuse a missing bearer | | [Machine identity](/docs/machine-identity) | Choose the credential | | [Pydantic AI](/docs/agent-frameworks/pydantic-ai) | See the hook | ### Authorizing tool calls for Pydantic AI Source: https://www.authdog.com/docs/ai/pydantic-ai/authorize-tool-calls.md A tool result is what your API returned. The model does not grant access. A `200` with a subject is not an allow list for the next tool. ## After the subject is known | Check | Where | | --- | --- | | Bearer accepted | The hook on [Pydantic AI](/docs/agent-frameworks/pydantic-ai) | | Allowed tools | Trust-store row, when your process enforces it | | The action itself | [Authorization](/docs/concepts/authorization) using the subject | Revoke in [Agents](/docs/console/agents) stops new tokens when the subject is the OIDC client id. Tokens already issued last until `expires_in` unless a later check rejects them. When the token is an [RFC 8693 delegation](/guides/agent-delegation), the person stays the subject and the agent stays the actor. Authorizing the agent as the user skips the delegation. ## Related | Read | To learn how to | | --- | --- | | [Custom channel auth for Pydantic AI](/docs/ai/pydantic-ai/channel-auth) | Admit the caller | | [Enriching instructions for Pydantic AI](/docs/ai/pydantic-ai/enriching-instructions) | What the model is allowed to see | | [Authorization](/docs/concepts/authorization) | Pick the check after the subject | ### Enriching instructions for Pydantic AI Source: https://www.authdog.com/docs/ai/pydantic-ai/enriching-instructions.md Tell the agent to call `whoami` and to return that subject. Do not tell it to invent one, and do not paste the bearer into the instructions. ## What the model can see The model can see the subject the hook already verified, and a refusal when the hook stopped the call. It cannot see the access token or the client secret. A prompt that asks the model who the caller is ignores the hook. The hook already returned unauthorized when userinfo did not accept the bearer. Do not copy the token into graph state, crew inputs that tasks render, callback state the model reads, or telemetry context. ## Related | Read | To learn how to | | --- | --- | | [Authorizing tool calls for Pydantic AI](/docs/ai/pydantic-ai/authorize-tool-calls) | Refuse work after the subject is known | | [Custom channel auth for Pydantic AI](/docs/ai/pydantic-ai/channel-auth) | Keep the bearer on the hook | | [Pydantic AI](/docs/agent-frameworks/pydantic-ai) | See the planned sample | | [API keys and M2M for Pydantic AI](/docs/ai/pydantic-ai/api-keys-and-m2m) | Keep the secret on the caller | ### Custom channel auth for Claude Agent SDK Source: https://www.authdog.com/docs/ai/claude-agent/channel-auth.md A `PreToolUse` hook denies the tool when the bearer is missing or userinfo rejects it. Deny runs before permission rules, so an allow rule does not skip this check. Getting started, including the planned sample, is [Claude Agent SDK](/docs/agent-frameworks/claude-agent). ## What fails closed The hook returns `permissionDecision` `deny` when the token is missing or userinfo rejects it. The tool does not run. The hook returns `{}` when userinfo accepts the bearer. A `whoami` tool may then return that subject. A resolved subject is not authorization. Scopes and allowed tools are a later check. The [trust-store sample](https://github.com/authdog/agents-samples/tree/main/trust-store/python) is that later check for MCP. This page stops at admission. ## Related | Read | To learn how to | | --- | --- | | [Claude Agent SDK](/docs/agent-frameworks/claude-agent) | See the hook | | [API keys and M2M for Claude Agent SDK](/docs/ai/claude-agent/api-keys-and-m2m) | Where the secret is allowed to live | | [Authorizing tool calls for Claude Agent SDK](/docs/ai/claude-agent/authorize-tool-calls) | What happens after the subject is known | ### API keys and M2M for Claude Agent SDK Source: https://www.authdog.com/docs/ai/claude-agent/api-keys-and-m2m.md The hook reads a token the host stored for the run. The client secret stays in the process that minted the token. Do not put the bearer in tool input. The model can see tool input. The exchange is in [Agent frameworks](/docs/agent-frameworks#mint-the-bearer). ## Which credential | Credential | Use it when | | --- | --- | | Agent identity | A named agent inside a project that already has users. [Agents](/docs/console/agents) is self-serve in every environment. | | M2M application | The caller is a job, or Agents access is not approved yet. Same `client_credentials` grant. | | Personal access token | One person's script. Keep it off an unattended caller. | | Service account | Authdog REST automation. Not the bearer this hook checks. | To act for a person, keep this hook and present an [RFC 8693 token](/guides/agent-delegation). `sub` stays the user. `act` names the agent. The planned sample uses a token whose subject is the agent. ## Related | Read | To learn how to | | --- | --- | | [Custom channel auth for Claude Agent SDK](/docs/ai/claude-agent/channel-auth) | Refuse a missing bearer | | [Machine identity](/docs/machine-identity) | Choose the credential | | [Claude Agent SDK](/docs/agent-frameworks/claude-agent) | See the hook | ### Authorizing tool calls for Claude Agent SDK Source: https://www.authdog.com/docs/ai/claude-agent/authorize-tool-calls.md Deny means this call does not run. It is not an allow list. A tool the subject may not call still has to be refused after the bearer is accepted. ## After the subject is known | Check | Where | | --- | --- | | Bearer accepted | The hook on [Claude Agent SDK](/docs/agent-frameworks/claude-agent) | | Allowed tools | Trust-store row, when your process enforces it | | The action itself | [Authorization](/docs/concepts/authorization) using the subject | Revoke in [Agents](/docs/console/agents) stops new tokens when the subject is the OIDC client id. Tokens already issued last until `expires_in` unless a later check rejects them. When the token is an [RFC 8693 delegation](/guides/agent-delegation), the person stays the subject and the agent stays the actor. Authorizing the agent as the user skips the delegation. ## Related | Read | To learn how to | | --- | --- | | [Custom channel auth for Claude Agent SDK](/docs/ai/claude-agent/channel-auth) | Admit the caller | | [Enriching instructions for Claude Agent SDK](/docs/ai/claude-agent/enriching-instructions) | What the model is allowed to see | | [Authorization](/docs/concepts/authorization) | Pick the check after the subject | ### Enriching instructions for Claude Agent SDK Source: https://www.authdog.com/docs/ai/claude-agent/enriching-instructions.md Leave the bearer out of the prompt. The hook already ran, and it did not read the token from the model. ## What the model can see The model can see the subject the hook already verified, and a refusal when the hook stopped the call. It cannot see the access token or the client secret. A prompt that asks the model who the caller is ignores the hook. The hook already returned unauthorized when userinfo did not accept the bearer. Do not copy the token into graph state, crew inputs that tasks render, callback state the model reads, or telemetry context. ## Related | Read | To learn how to | | --- | --- | | [Authorizing tool calls for Claude Agent SDK](/docs/ai/claude-agent/authorize-tool-calls) | Refuse work after the subject is known | | [Custom channel auth for Claude Agent SDK](/docs/ai/claude-agent/channel-auth) | Keep the bearer on the hook | | [Claude Agent SDK](/docs/agent-frameworks/claude-agent) | See the planned sample | | [API keys and M2M for Claude Agent SDK](/docs/ai/claude-agent/api-keys-and-m2m) | Keep the secret on the caller | ### Custom channel auth for Mastra Source: https://www.authdog.com/docs/ai/mastra/channel-auth.md `authenticateToken` admits a route only when userinfo accepts the bearer. Without `server.auth`, Mastra leaves routes public. This sample sets the callback. Getting started, including the planned sample, is [Mastra](/docs/agent-frameworks/mastra). ## What fails closed A request with no bearer is `401`. A bearer userinfo rejects is `401`. The body has no subject. A bearer userinfo accepts reaches `whoami`, which returns that subject. A resolved subject is not authorization. Scopes and allowed tools are a later check. The [trust-store sample](https://github.com/authdog/agents-samples/tree/main/trust-store/python) is that later check for MCP. This page stops at admission. ## Related | Read | To learn how to | | --- | --- | | [Mastra](/docs/agent-frameworks/mastra) | See the hook | | [API keys and M2M for Mastra](/docs/ai/mastra/api-keys-and-m2m) | Where the secret is allowed to live | | [Authorizing tool calls for Mastra](/docs/ai/mastra/authorize-tool-calls) | What happens after the subject is known | ### API keys and M2M for Mastra Source: https://www.authdog.com/docs/ai/mastra/api-keys-and-m2m.md The Mastra server holds the public key. The caller holds the access token and, if it minted that token, the client secret. There is no refresh token. The exchange is in [Agent frameworks](/docs/agent-frameworks#mint-the-bearer). ## Which credential | Credential | Use it when | | --- | --- | | Agent identity | A named agent inside a project that already has users. [Agents](/docs/console/agents) is self-serve in every environment. | | M2M application | The caller is a job, or Agents access is not approved yet. Same `client_credentials` grant. | | Personal access token | One person's script. Keep it off an unattended caller. | | Service account | Authdog REST automation. Not the bearer this hook checks. | To act for a person, keep this hook and present an [RFC 8693 token](/guides/agent-delegation). `sub` stays the user. `act` names the agent. The planned sample uses a token whose subject is the agent. ## Related | Read | To learn how to | | --- | --- | | [Custom channel auth for Mastra](/docs/ai/mastra/channel-auth) | Refuse a missing bearer | | [Machine identity](/docs/machine-identity) | Choose the credential | | [Mastra](/docs/agent-frameworks/mastra) | See the hook | ### Authorizing tool calls for Mastra Source: https://www.authdog.com/docs/ai/mastra/authorize-tool-calls.md A returned user is the subject. It is not an allow list. Tool and workflow routes still need a second check before they do privileged work. ## After the subject is known | Check | Where | | --- | --- | | Bearer accepted | The hook on [Mastra](/docs/agent-frameworks/mastra) | | Allowed tools | Trust-store row, when your process enforces it | | The action itself | [Authorization](/docs/concepts/authorization) using the subject | Revoke in [Agents](/docs/console/agents) stops new tokens when the subject is the OIDC client id. Tokens already issued last until `expires_in` unless a later check rejects them. When the token is an [RFC 8693 delegation](/guides/agent-delegation), the person stays the subject and the agent stays the actor. Authorizing the agent as the user skips the delegation. ## Related | Read | To learn how to | | --- | --- | | [Custom channel auth for Mastra](/docs/ai/mastra/channel-auth) | Admit the caller | | [Enriching instructions for Mastra](/docs/ai/mastra/enriching-instructions) | What the model is allowed to see | | [Authorization](/docs/concepts/authorization) | Pick the check after the subject | ### Enriching instructions for Mastra Source: https://www.authdog.com/docs/ai/mastra/enriching-instructions.md Pass the subject into the agent if a prompt needs an id. Leave the bearer out of instructions. The server already consumed it. ## What the model can see The model can see the subject the hook already verified, and a refusal when the hook stopped the call. It cannot see the access token or the client secret. A prompt that asks the model who the caller is ignores the hook. The hook already returned unauthorized when userinfo did not accept the bearer. Do not copy the token into graph state, crew inputs that tasks render, callback state the model reads, or telemetry context. ## Related | Read | To learn how to | | --- | --- | | [Authorizing tool calls for Mastra](/docs/ai/mastra/authorize-tool-calls) | Refuse work after the subject is known | | [Custom channel auth for Mastra](/docs/ai/mastra/channel-auth) | Keep the bearer on the hook | | [Mastra](/docs/agent-frameworks/mastra) | See the planned sample | | [API keys and M2M for Mastra](/docs/ai/mastra/api-keys-and-m2m) | Keep the secret on the caller | ### Custom channel auth for LlamaIndex Workflows Source: https://www.authdog.com/docs/ai/llamaindex/channel-auth.md Run the workflow only from a route that depends on `require_auth`. The first step stops when the subject is missing, including a direct `workflow.run()` that skipped the route. Getting started, including the planned sample, is [LlamaIndex Workflows](/docs/agent-frameworks/llamaindex). ## What fails closed `POST /invoke` with no token is `401`. `workflow.run()` with no subject returns `unauthorized` and does not contain `subject:`. The same POST with a bearer userinfo accepts returns `{"reply":"subject:"}`. A resolved subject is not authorization. Scopes and allowed tools are a later check. The [trust-store sample](https://github.com/authdog/agents-samples/tree/main/trust-store/python) is that later check for MCP. This page stops at admission. ## Related | Read | To learn how to | | --- | --- | | [LlamaIndex Workflows](/docs/agent-frameworks/llamaindex) | See the hook | | [API keys and M2M for LlamaIndex Workflows](/docs/ai/llamaindex/api-keys-and-m2m) | Where the secret is allowed to live | | [Authorizing tool calls for LlamaIndex Workflows](/docs/ai/llamaindex/authorize-tool-calls) | What happens after the subject is known | ### API keys and M2M for LlamaIndex Workflows Source: https://www.authdog.com/docs/ai/llamaindex/api-keys-and-m2m.md The route holds the public key. The workflow receives the subject, not the token. The client secret stays in the process that minted the bearer. The exchange is in [Agent frameworks](/docs/agent-frameworks#mint-the-bearer). ## Which credential | Credential | Use it when | | --- | --- | | Agent identity | A named agent inside a project that already has users. [Agents](/docs/console/agents) is self-serve in every environment. | | M2M application | The caller is a job, or Agents access is not approved yet. Same `client_credentials` grant. | | Personal access token | One person's script. Keep it off an unattended caller. | | Service account | Authdog REST automation. Not the bearer this hook checks. | To act for a person, keep this hook and present an [RFC 8693 token](/guides/agent-delegation). `sub` stays the user. `act` names the agent. The planned sample uses a token whose subject is the agent. ## Related | Read | To learn how to | | --- | --- | | [Custom channel auth for LlamaIndex Workflows](/docs/ai/llamaindex/channel-auth) | Refuse a missing bearer | | [Machine identity](/docs/machine-identity) | Choose the credential | | [LlamaIndex Workflows](/docs/agent-frameworks/llamaindex) | See the hook | ### Authorizing tool calls for LlamaIndex Workflows Source: https://www.authdog.com/docs/ai/llamaindex/authorize-tool-calls.md Stopping on a missing subject admits the caller. A step that does privileged work still checks what that subject may do. ## After the subject is known | Check | Where | | --- | --- | | Bearer accepted | The hook on [LlamaIndex Workflows](/docs/agent-frameworks/llamaindex) | | Allowed tools | Trust-store row, when your process enforces it | | The action itself | [Authorization](/docs/concepts/authorization) using the subject | Revoke in [Agents](/docs/console/agents) stops new tokens when the subject is the OIDC client id. Tokens already issued last until `expires_in` unless a later check rejects them. When the token is an [RFC 8693 delegation](/guides/agent-delegation), the person stays the subject and the agent stays the actor. Authorizing the agent as the user skips the delegation. ## Related | Read | To learn how to | | --- | --- | | [Custom channel auth for LlamaIndex Workflows](/docs/ai/llamaindex/channel-auth) | Admit the caller | | [Enriching instructions for LlamaIndex Workflows](/docs/ai/llamaindex/enriching-instructions) | What the model is allowed to see | | [Authorization](/docs/concepts/authorization) | Pick the check after the subject | ### Enriching instructions for LlamaIndex Workflows Source: https://www.authdog.com/docs/ai/llamaindex/enriching-instructions.md Put the subject on the start event if a later step needs an id. Leave the bearer on the HTTP request. A snapshotted event would keep a token you stored there. ## What the model can see The model can see the subject the hook already verified, and a refusal when the hook stopped the call. It cannot see the access token or the client secret. A prompt that asks the model who the caller is ignores the hook. The hook already returned unauthorized when userinfo did not accept the bearer. Do not copy the token into graph state, crew inputs that tasks render, callback state the model reads, or telemetry context. ## Related | Read | To learn how to | | --- | --- | | [Authorizing tool calls for LlamaIndex Workflows](/docs/ai/llamaindex/authorize-tool-calls) | Refuse work after the subject is known | | [Custom channel auth for LlamaIndex Workflows](/docs/ai/llamaindex/channel-auth) | Keep the bearer on the hook | | [LlamaIndex Workflows](/docs/agent-frameworks/llamaindex) | See the planned sample | | [API keys and M2M for LlamaIndex Workflows](/docs/ai/llamaindex/api-keys-and-m2m) | Keep the secret on the caller | ### Custom channel auth for Semantic Kernel Source: https://www.authdog.com/docs/ai/semantic-kernel/channel-auth.md A function-invocation filter skips `next` when the bearer is missing or userinfo rejects it. The function runs only on the path that calls `next`. Getting started, including the planned sample, is [Semantic Kernel](/docs/agent-frameworks/semantic-kernel). ## What fails closed Invoking `whoami` with no token does not enter the function. The result is not a subject. Invoking `whoami` with a bearer userinfo accepts runs the function. The result is that subject. A resolved subject is not authorization. Scopes and allowed tools are a later check. The [trust-store sample](https://github.com/authdog/agents-samples/tree/main/trust-store/python) is that later check for MCP. This page stops at admission. ## Related | Read | To learn how to | | --- | --- | | [Semantic Kernel](/docs/agent-frameworks/semantic-kernel) | See the hook | | [API keys and M2M for Semantic Kernel](/docs/ai/semantic-kernel/api-keys-and-m2m) | Where the secret is allowed to live | | [Authorizing tool calls for Semantic Kernel](/docs/ai/semantic-kernel/authorize-tool-calls) | What happens after the subject is known | ### API keys and M2M for Semantic Kernel Source: https://www.authdog.com/docs/ai/semantic-kernel/api-keys-and-m2m.md The filter reads the token from arguments the host set, then removes it. The client secret is not an argument. A chat completion key is not required for this check. The exchange is in [Agent frameworks](/docs/agent-frameworks#mint-the-bearer). ## Which credential | Credential | Use it when | | --- | --- | | Agent identity | A named agent inside a project that already has users. [Agents](/docs/console/agents) is self-serve in every environment. | | M2M application | The caller is a job, or Agents access is not approved yet. Same `client_credentials` grant. | | Personal access token | One person's script. Keep it off an unattended caller. | | Service account | Authdog REST automation. Not the bearer this hook checks. | To act for a person, keep this hook and present an [RFC 8693 token](/guides/agent-delegation). `sub` stays the user. `act` names the agent. The planned sample uses a token whose subject is the agent. ## Related | Read | To learn how to | | --- | --- | | [Custom channel auth for Semantic Kernel](/docs/ai/semantic-kernel/channel-auth) | Refuse a missing bearer | | [Machine identity](/docs/machine-identity) | Choose the credential | | [Semantic Kernel](/docs/agent-frameworks/semantic-kernel) | See the hook | ### Authorizing tool calls for Semantic Kernel Source: https://www.authdog.com/docs/ai/semantic-kernel/authorize-tool-calls.md Skipping `next` stops that function. It does not decide a later function. Check the subject again before privileged work. ## After the subject is known | Check | Where | | --- | --- | | Bearer accepted | The hook on [Semantic Kernel](/docs/agent-frameworks/semantic-kernel) | | Allowed tools | Trust-store row, when your process enforces it | | The action itself | [Authorization](/docs/concepts/authorization) using the subject | Revoke in [Agents](/docs/console/agents) stops new tokens when the subject is the OIDC client id. Tokens already issued last until `expires_in` unless a later check rejects them. When the token is an [RFC 8693 delegation](/guides/agent-delegation), the person stays the subject and the agent stays the actor. Authorizing the agent as the user skips the delegation. ## Related | Read | To learn how to | | --- | --- | | [Custom channel auth for Semantic Kernel](/docs/ai/semantic-kernel/channel-auth) | Admit the caller | | [Enriching instructions for Semantic Kernel](/docs/ai/semantic-kernel/enriching-instructions) | What the model is allowed to see | | [Authorization](/docs/concepts/authorization) | Pick the check after the subject | ### Enriching instructions for Semantic Kernel Source: https://www.authdog.com/docs/ai/semantic-kernel/enriching-instructions.md Leave the bearer out of the prompt. The plugin can see the subject the filter stored. It should not see the token. ## What the model can see The model can see the subject the hook already verified, and a refusal when the hook stopped the call. It cannot see the access token or the client secret. A prompt that asks the model who the caller is ignores the hook. The hook already returned unauthorized when userinfo did not accept the bearer. Do not copy the token into graph state, crew inputs that tasks render, callback state the model reads, or telemetry context. ## Related | Read | To learn how to | | --- | --- | | [Authorizing tool calls for Semantic Kernel](/docs/ai/semantic-kernel/authorize-tool-calls) | Refuse work after the subject is known | | [Custom channel auth for Semantic Kernel](/docs/ai/semantic-kernel/channel-auth) | Keep the bearer on the hook | | [Semantic Kernel](/docs/agent-frameworks/semantic-kernel) | See the planned sample | | [API keys and M2M for Semantic Kernel](/docs/ai/semantic-kernel/api-keys-and-m2m) | Keep the secret on the caller | ### Custom channel auth for Vercel AI SDK Source: https://www.authdog.com/docs/ai/ai-sdk/channel-auth.md The tool reads `toolsContext`. `require_auth` on your API is the gate. The AI SDK does not decide who the caller is. Getting started, including the planned sample, is [Vercel AI SDK](/docs/agent-frameworks/ai-sdk). ## What fails closed The route with no token is `401`. `execute` with an empty token does not return a subject. The route with a bearer userinfo accepts returns `{ subject: "" }`. That string is the tool result. A resolved subject is not authorization. Scopes and allowed tools are a later check. The [trust-store sample](https://github.com/authdog/agents-samples/tree/main/trust-store/python) is that later check for MCP. This page stops at admission. ## Related | Read | To learn how to | | --- | --- | | [Vercel AI SDK](/docs/agent-frameworks/ai-sdk) | See the hook | | [API keys and M2M for Vercel AI SDK](/docs/ai/ai-sdk/api-keys-and-m2m) | Where the secret is allowed to live | | [Authorizing tool calls for Vercel AI SDK](/docs/ai/ai-sdk/authorize-tool-calls) | What happens after the subject is known | ### API keys and M2M for Vercel AI SDK Source: https://www.authdog.com/docs/ai/ai-sdk/api-keys-and-m2m.md Put the access token in that tool's context. Leave it out of `runtimeContext` and out of the prompt. The client secret stays in the process that minted the token. The exchange is in [Agent frameworks](/docs/agent-frameworks#mint-the-bearer). ## Which credential | Credential | Use it when | | --- | --- | | Agent identity | A named agent inside a project that already has users. [Agents](/docs/console/agents) is self-serve in every environment. | | M2M application | The caller is a job, or Agents access is not approved yet. Same `client_credentials` grant. | | Personal access token | One person's script. Keep it off an unattended caller. | | Service account | Authdog REST automation. Not the bearer this hook checks. | To act for a person, keep this hook and present an [RFC 8693 token](/guides/agent-delegation). `sub` stays the user. `act` names the agent. The planned sample uses a token whose subject is the agent. ## Related | Read | To learn how to | | --- | --- | | [Custom channel auth for Vercel AI SDK](/docs/ai/ai-sdk/channel-auth) | Refuse a missing bearer | | [Machine identity](/docs/machine-identity) | Choose the credential | | [Vercel AI SDK](/docs/agent-frameworks/ai-sdk) | See the hook | ### Authorizing tool calls for Vercel AI SDK Source: https://www.authdog.com/docs/ai/ai-sdk/authorize-tool-calls.md A tool result is what your API returned. The model does not grant access. Accepting the bearer does not allow every later tool. ## After the subject is known | Check | Where | | --- | --- | | Bearer accepted | The hook on [Vercel AI SDK](/docs/agent-frameworks/ai-sdk) | | Allowed tools | Trust-store row, when your process enforces it | | The action itself | [Authorization](/docs/concepts/authorization) using the subject | Revoke in [Agents](/docs/console/agents) stops new tokens when the subject is the OIDC client id. Tokens already issued last until `expires_in` unless a later check rejects them. When the token is an [RFC 8693 delegation](/guides/agent-delegation), the person stays the subject and the agent stays the actor. Authorizing the agent as the user skips the delegation. ## Related | Read | To learn how to | | --- | --- | | [Custom channel auth for Vercel AI SDK](/docs/ai/ai-sdk/channel-auth) | Admit the caller | | [Enriching instructions for Vercel AI SDK](/docs/ai/ai-sdk/enriching-instructions) | What the model is allowed to see | | [Authorization](/docs/concepts/authorization) | Pick the check after the subject | ### Enriching instructions for Vercel AI SDK Source: https://www.authdog.com/docs/ai/ai-sdk/enriching-instructions.md Tell the agent to call `whoami` and to return that subject. Do not paste the bearer into the instructions. ## What the model can see The model can see the subject the hook already verified, and a refusal when the hook stopped the call. It cannot see the access token or the client secret. A prompt that asks the model who the caller is ignores the hook. The hook already returned unauthorized when userinfo did not accept the bearer. Do not copy the token into graph state, crew inputs that tasks render, callback state the model reads, or telemetry context. ## Related | Read | To learn how to | | --- | --- | | [Authorizing tool calls for Vercel AI SDK](/docs/ai/ai-sdk/authorize-tool-calls) | Refuse work after the subject is known | | [Custom channel auth for Vercel AI SDK](/docs/ai/ai-sdk/channel-auth) | Keep the bearer on the hook | | [Vercel AI SDK](/docs/agent-frameworks/ai-sdk) | See the planned sample | | [API keys and M2M for Vercel AI SDK](/docs/ai/ai-sdk/api-keys-and-m2m) | Keep the secret on the caller | ### Custom channel auth for Strands Agents Source: https://www.authdog.com/docs/ai/strands/channel-auth.md `BeforeToolCallEvent` sets `cancel_tool` when the bearer is missing or userinfo rejects it. The tool runs only when the hook leaves `cancel_tool` unset. Getting started, including the planned sample, is [Strands Agents](/docs/agent-frameworks/strands). ## What fails closed A before-tool event with no token sets `cancel_tool` to `unauthorized`. The tool does not run. A before-tool event with a bearer userinfo accepts leaves `cancel_tool` unset and stores the subject. `whoami` returns it. A resolved subject is not authorization. Scopes and allowed tools are a later check. The [trust-store sample](https://github.com/authdog/agents-samples/tree/main/trust-store/python) is that later check for MCP. This page stops at admission. ## Related | Read | To learn how to | | --- | --- | | [Strands Agents](/docs/agent-frameworks/strands) | See the hook | | [API keys and M2M for Strands Agents](/docs/ai/strands/api-keys-and-m2m) | Where the secret is allowed to live | | [Authorizing tool calls for Strands Agents](/docs/ai/strands/authorize-tool-calls) | What happens after the subject is known | ### API keys and M2M for Strands Agents Source: https://www.authdog.com/docs/ai/strands/api-keys-and-m2m.md Pass the token in invocation state from the host. The client secret is not invocation state. A model key is not required for the hook test. The exchange is in [Agent frameworks](/docs/agent-frameworks#mint-the-bearer). ## Which credential | Credential | Use it when | | --- | --- | | Agent identity | A named agent inside a project that already has users. [Agents](/docs/console/agents) is self-serve in every environment. | | M2M application | The caller is a job, or Agents access is not approved yet. Same `client_credentials` grant. | | Personal access token | One person's script. Keep it off an unattended caller. | | Service account | Authdog REST automation. Not the bearer this hook checks. | To act for a person, keep this hook and present an [RFC 8693 token](/guides/agent-delegation). `sub` stays the user. `act` names the agent. The planned sample uses a token whose subject is the agent. ## Related | Read | To learn how to | | --- | --- | | [Custom channel auth for Strands Agents](/docs/ai/strands/channel-auth) | Refuse a missing bearer | | [Machine identity](/docs/machine-identity) | Choose the credential | | [Strands Agents](/docs/agent-frameworks/strands) | See the hook | ### Authorizing tool calls for Strands Agents Source: https://www.authdog.com/docs/ai/strands/authorize-tool-calls.md Cancelling one call is not an allow list for the next tool. Check the subject before privileged work. ## After the subject is known | Check | Where | | --- | --- | | Bearer accepted | The hook on [Strands Agents](/docs/agent-frameworks/strands) | | Allowed tools | Trust-store row, when your process enforces it | | The action itself | [Authorization](/docs/concepts/authorization) using the subject | Revoke in [Agents](/docs/console/agents) stops new tokens when the subject is the OIDC client id. Tokens already issued last until `expires_in` unless a later check rejects them. When the token is an [RFC 8693 delegation](/guides/agent-delegation), the person stays the subject and the agent stays the actor. Authorizing the agent as the user skips the delegation. ## Related | Read | To learn how to | | --- | --- | | [Custom channel auth for Strands Agents](/docs/ai/strands/channel-auth) | Admit the caller | | [Enriching instructions for Strands Agents](/docs/ai/strands/enriching-instructions) | What the model is allowed to see | | [Authorization](/docs/concepts/authorization) | Pick the check after the subject | ### Enriching instructions for Strands Agents Source: https://www.authdog.com/docs/ai/strands/enriching-instructions.md Leave the bearer out of the prompt. The hook reads invocation state the host set, not text the model wrote. ## What the model can see The model can see the subject the hook already verified, and a refusal when the hook stopped the call. It cannot see the access token or the client secret. A prompt that asks the model who the caller is ignores the hook. The hook already returned unauthorized when userinfo did not accept the bearer. Do not copy the token into graph state, crew inputs that tasks render, callback state the model reads, or telemetry context. ## Related | Read | To learn how to | | --- | --- | | [Authorizing tool calls for Strands Agents](/docs/ai/strands/authorize-tool-calls) | Refuse work after the subject is known | | [Custom channel auth for Strands Agents](/docs/ai/strands/channel-auth) | Keep the bearer on the hook | | [Strands Agents](/docs/agent-frameworks/strands) | See the planned sample | | [API keys and M2M for Strands Agents](/docs/ai/strands/api-keys-and-m2m) | Keep the secret on the caller | ### Custom channel auth for LangChain Source: https://www.authdog.com/docs/ai/langchain/channel-auth.md `wrap_tool_call` returns an error tool message and does not call the handler when the bearer is missing or userinfo rejects it. Getting started, including the planned sample, is [LangChain](/docs/agent-frameworks/langchain). ## What fails closed A tool call with no token returns a tool message `unauthorized` and does not call the handler. A tool call with a bearer userinfo accepts calls the handler. `whoami` returns that subject. A resolved subject is not authorization. Scopes and allowed tools are a later check. The [trust-store sample](https://github.com/authdog/agents-samples/tree/main/trust-store/python) is that later check for MCP. This page stops at admission. ## Related | Read | To learn how to | | --- | --- | | [LangChain](/docs/agent-frameworks/langchain) | See the hook | | [API keys and M2M for LangChain](/docs/ai/langchain/api-keys-and-m2m) | Where the secret is allowed to live | | [Authorizing tool calls for LangChain](/docs/ai/langchain/authorize-tool-calls) | What happens after the subject is known | ### API keys and M2M for LangChain Source: https://www.authdog.com/docs/ai/langchain/api-keys-and-m2m.md Read the token from runtime context. Leave it out of tool arguments and the system prompt. The client secret stays in the process that minted the token. The exchange is in [Agent frameworks](/docs/agent-frameworks#mint-the-bearer). ## Which credential | Credential | Use it when | | --- | --- | | Agent identity | A named agent inside a project that already has users. [Agents](/docs/console/agents) is self-serve in every environment. | | M2M application | The caller is a job, or Agents access is not approved yet. Same `client_credentials` grant. | | Personal access token | One person's script. Keep it off an unattended caller. | | Service account | Authdog REST automation. Not the bearer this hook checks. | To act for a person, keep this hook and present an [RFC 8693 token](/guides/agent-delegation). `sub` stays the user. `act` names the agent. The planned sample uses a token whose subject is the agent. ## Related | Read | To learn how to | | --- | --- | | [Custom channel auth for LangChain](/docs/ai/langchain/channel-auth) | Refuse a missing bearer | | [Machine identity](/docs/machine-identity) | Choose the credential | | [LangChain](/docs/agent-frameworks/langchain) | See the hook | ### Authorizing tool calls for LangChain Source: https://www.authdog.com/docs/ai/langchain/authorize-tool-calls.md Skipping the handler stops that call. It does not grant the next one. Check the subject before privileged work. ## After the subject is known | Check | Where | | --- | --- | | Bearer accepted | The hook on [LangChain](/docs/agent-frameworks/langchain) | | Allowed tools | Trust-store row, when your process enforces it | | The action itself | [Authorization](/docs/concepts/authorization) using the subject | Revoke in [Agents](/docs/console/agents) stops new tokens when the subject is the OIDC client id. Tokens already issued last until `expires_in` unless a later check rejects them. When the token is an [RFC 8693 delegation](/guides/agent-delegation), the person stays the subject and the agent stays the actor. Authorizing the agent as the user skips the delegation. ## Related | Read | To learn how to | | --- | --- | | [Custom channel auth for LangChain](/docs/ai/langchain/channel-auth) | Admit the caller | | [Enriching instructions for LangChain](/docs/ai/langchain/enriching-instructions) | What the model is allowed to see | | [Authorization](/docs/concepts/authorization) | Pick the check after the subject | ### Enriching instructions for LangChain Source: https://www.authdog.com/docs/ai/langchain/enriching-instructions.md Leave the bearer out of the system prompt. The tool result the model sees is the subject, or `unauthorized`. ## What the model can see The model can see the subject the hook already verified, and a refusal when the hook stopped the call. It cannot see the access token or the client secret. A prompt that asks the model who the caller is ignores the hook. The hook already returned unauthorized when userinfo did not accept the bearer. Do not copy the token into graph state, crew inputs that tasks render, callback state the model reads, or telemetry context. ## Related | Read | To learn how to | | --- | --- | | [Authorizing tool calls for LangChain](/docs/ai/langchain/authorize-tool-calls) | Refuse work after the subject is known | | [Custom channel auth for LangChain](/docs/ai/langchain/channel-auth) | Keep the bearer on the hook | | [LangChain](/docs/agent-frameworks/langchain) | See the planned sample | | [API keys and M2M for LangChain](/docs/ai/langchain/api-keys-and-m2m) | Keep the secret on the caller | ### Build an MCP server Source: https://www.authdog.com/docs/ai/mcp-development/build-server.md Your MCP server is the resource. Authdog is the authorization server. A tool runs only after you accept the bearer, and only if that caller may use that tool. [Authdog's own MCP server](/docs/ai/mcp-server) is a different process. It manages the Authdog account. This page is the server you ship. ## Pick the check | You need | Use | | --- | --- | | "Is this bearer valid, and who is the caller?" | A token verifier. The Python sample is [MCP agents](/docs/agent-frameworks/mcp). Other languages are in the [MCP SDK](/docs/mcp-sdk). | | Trust store, a per-tool AuthZEN decision, and audit events | The TypeScript runtime guard in [MCP SDK](/docs/mcp-sdk#mcp-runtime-guard) | The Python sample uses `token_verifier` and userinfo. It does not install the runtime guard. Add the guard when you want allowed-tool globs and `MCP_TOOL_DENIED` in the audit log. The runtime guard is private (`packages/mcp-sdk` in the monorepo) and is not on npm. The Node.js verifier is on npm as `@authdog/mcp-sdk`. The other language verifiers are source-available in [authdog/mcp-sdk](https://github.com/authdog/mcp-sdk). ## Register the server In the console, create a project and choose **MCP server**. MCP Auth shows up in the sidebar for that project type. Enable it under **MCP Auth → Settings**, and set the default resource if clients omit `resource`. The playground mints a bearer for your console session and calls a tool. A successful playground call proves that session can invoke that tool. It does not authorize every agent in the trust store. Register those in [Agents](/docs/console/agents). ## Turn on enforcement The runtime guard defaults to modes you can watch before you block: 1. Authenticate the access token against the environment JWKS. 2. Require a verified non-human identity when the caller is an agent. 3. Ask AuthZEN whether this identity may invoke this tool. 4. Emit invoked, denied, and failed events. Move trust store and authorization to **enforce** after you have watched the decisions. Happy path in production is enforce. `off` and `monitor` are for staging. Issuer revoke stops new tokens when the subject is the OIDC client id. Tokens already issued last until `expires_in` unless the resource server enforces the trust store. ## Related | Read | To learn how to | | --- | --- | | [Custom channel auth for MCP](/docs/ai/mcp/channel-auth) | Implement `token_verifier` | | [Authorizing tool calls for MCP](/docs/ai/mcp/authorize-tool-calls) | Apply allowed tools after the subject is known | | [Connect an MCP client](/docs/ai/mcp-development/connect-client) | Let a client obtain a token | | [MCP authentication](/docs/mcp-authentication) | Read the OAuth 2.1 and audit details | ### Connect an MCP client Source: https://www.authdog.com/docs/ai/mcp-development/connect-client.md An MCP client is the app that wants to call tools: Claude, Cursor, or a product you ship. It discovers Authdog from your server's `401`, signs the user in, and sends the access token on the next call. Connecting a coding agent to [Authdog's management MCP](/docs/ai/mcp-server) is a different URL (`https://api.authdog.com/mcp`) and a different install command. ## What the client does 1. It calls your MCP server without a token. 2. Your server responds with **401** and `WWW-Authenticate` pointing at protected-resource metadata. 3. The client loads that metadata, starts authorize with **PKCE (S256)**, and can send a **resource** indicator (RFC 8707). 4. After consent, the client exchanges the code at the token endpoint and calls tools with `Authorization: Bearer`. ```text Client --(no token)--> your MCP server Server --401 + resource_metadata--> Client Client --authorize + PKCE--> Authdog Client --token--> Authdog Client --Bearer token--> your MCP server ``` Your server still has to verify that bearer. Discovery does not do it. See [Build an MCP server](/docs/ai/mcp-development/build-server). ## What you turn on In **MCP Auth → Settings** for the MCP project's environment: - **Enable MCP Auth** turns on default-resource and strict-resource behavior for that issuer. - **Default resource** applies when the client omits `resource`. - **Strict resource mode** rejects authorize and token requests that omit `resource` when no default is set. Dynamic client registration and client-ID metadata (CIMD) are per environment. When MCP Auth is enabled, the issuer registration endpoint honors those toggles. A CIMD client uses an `https` metadata URL as `client_id`. Hosted consent, enterprise SSO, and session policy are the same issuer as the rest of the environment. There is no separate MCP-only identity provider. ## Agent callers A person going through that login gets a user token. An unattended agent usually presents a `client_credentials` token instead, minted the way [API keys and M2M for MCP](/docs/ai/mcp/api-keys-and-m2m) describes. The verifier on your server accepts either, as long as userinfo accepts the bearer. Tool authorization is the next check. ## Related | Read | To learn how to | | --- | --- | | [MCP authentication](/docs/mcp-authentication) | Read metadata, consent, revocation, and SIEM forwarding | | [Build an MCP server](/docs/ai/mcp-development/build-server) | Verify the token the client just sent | | [Use Authdog's MCP server](/docs/ai/mcp-server) | Connect a coding agent to the control plane | | [MCP servers](/docs/console/mcp) | Find Settings and the playground | ## Securing your app ### Securing your app Source: https://www.authdog.com/docs/security.md Sign-in is where stolen passwords, bots, and scripted probes show up first. Authdog already issues a short-lived signed session, isolates keys per environment, and records the attempt. The rest of this section is the controls you turn on when a specific threat shows up in your traffic. Use the tables to jump to the setting. Each row is a threat you can name, and the Authdog control that answers it. ## Attack protection These controls live on the environment. Open **Authentication > Policies** for thresholds, **Authentication > Add-ons** for a CAPTCHA, and **Users > Settings** for account enumeration. | Threat | What it looks like | Authdog control | | --- | --- | --- | | Credential stuffing | A list of stolen passwords tried against your users. | [Device risk](/docs/secure/attack-protection) challenges an unrecognized device. [Breached passwords](/docs/secure/attack-protection) reject known-bad passwords. [Bot protection](/docs/bot-protection) slows scripted sign-in. | | Brute force | Many guesses against one account. | Brute-force protection locks the account after repeated failures and can add a progressive delay. Default is 5 failures and a 15-minute lockout. | | Bot sign-ups | Automated clients creating accounts. | Bot detection scores sign-in and sign-up. reCAPTCHA or Turnstile challenges the hosted flows you select. | | Account enumeration | Probes that learn which emails already have an account. | **Prevent account enumeration** hides whether an email is registered. | | Compromised passwords | A user picks a password that already appeared in a breach. | Breached-password checks compare with k-anonymity and, by default, hard-block on sign-up and password change. | | Spam accounts | Throwaway mailboxes and `+` aliases farming trials or a waitlist. | Block disposable addresses, limit subaddresses, and use an allowlist, a blocklist, [restricted sign-up](/docs/secure/access-control), or a [waitlist](/docs/secure/waitlist). | [Lidar](/docs/concepts/lidar) raises Signals for the same patterns (brute force, bots, impossible travel, MFA fatigue) so you can triage what the policies already blocked. ## Web security The session cookie and the redirect rules are on before you configure a policy. | Threat | What it looks like | How Authdog responds | | --- | --- | --- | | Cross-site scripting | A script on your page tries to read the session. | The `authdog-session` cookie is `HttpOnly`. A short-lived JWT limits how long a stolen bearer is useful. | | Cross-site request forgery | Another site submits a request with the user's browser. | The session cookie is `SameSite=Lax`, and `Secure` in production. | | Open redirect | An authorization code is sent to an attacker's host. | Redirect URIs must match a [verified domain](/docs/console/redirects) exactly. | | Stolen session | A token leaks from logs or a client. | Sessions expire. The next `userinfo` check honors revocation and key rotation. | | Forged webhooks | A caller pretends an event came from Authdog. | Outbound webhooks carry `X-Authdog-Signature: t=...,v1=...`. Verify it before you act. | | Lookalike sign-in page | A user types a password on a host they don't recognize. | The trusted-domain reminder shows the identity host on hosted sign-in and sign-up. | Details and the headers you still set on your own app are in [Web security](/docs/secure/web-security). ## More controls - [Session security](/docs/secure/session-security) sets token lifetime, idle timeout, and whether one user can hold sessions on several devices. - [MFA account recovery](/docs/secure/mfa-recovery) is how you reset a second factor after the authenticator is gone. - [Geo blocking](/docs/secure/geo-blocking) rejects sign-in and sign-up from countries you mark on the map. - [Machine authentication](/docs/secure/machine-authentication) keeps jobs and agents off user cookies. - [Rotate your API keys](/docs/secure/rotate-keys) swaps signing keys without dropping in-flight tokens, and replaces a leaked client secret. - [Legal compliance](/docs/secure/legal-compliance) is where your terms, privacy URL, audit trail, and DPA request sit. ## What is already on You don't enable these. They are how every environment behaves: - **Its own signing keys and user store.** A leaked credential in `dev` cannot validate in `production`. Clients verify with the environment public key (`pk_...`) and never see the private key. - **Edge checks.** The session is validated close to the user, against that public key, before the request needs a central session store. - **Enterprise SSO and provisioning.** SAML or OIDC hands authentication to the customer's identity provider. SCIM and HRIS deactivate users when they leave. See [Enterprise SSO](/docs/sso) and [Provisioning](/docs/concepts/provisioning). - **An audit trail.** Sign-ins, role changes, and provisioning updates are events you can read and forward. See [Audit logs](/docs/audit-logs). ## Next steps - Turn on [attack protection](/docs/secure/attack-protection) for the flows attackers hit first: sign-up, waitlist, and password reset. - Review [session security](/docs/secure/session-security) before you lengthen token lifetime. - If a signing key or client secret has been copied, [rotate it](/docs/secure/rotate-keys) before you do anything else. ## Related | Read | To learn how to | | --- | --- | | [Attack protection](/docs/secure/attack-protection) | Set lockout, bot, and breached-password policies | | [Bot protection](/docs/bot-protection) | Put reCAPTCHA or Turnstile on a hosted flow | | [Lidar](/docs/concepts/lidar) | Triage the Signals those policies produce | | [Sessions and tokens](/docs/concepts/sessions-tokens) | See how the session JWT is issued and checked | ### Attack protection Source: https://www.authdog.com/docs/secure/attack-protection.md Attackers don't need a bug in your app. A stolen password list, a script that guesses, or a disposable inbox is enough. These policies run on the environment, on the hosted sign-in path, before a session is issued. Open the [console](https://console.authdog.com), select the environment, then **Authentication > Policies**. Save each card on its own. Detections also show up as [Lidar](/docs/concepts/lidar) Signals, so a block and an investigation share the same trail. ## Brute force **Brute force** limits consecutive failed sign-ins. **Enable brute-force protection** is on by default: 5 failures, then a 15-minute lockout, with **Progressive delay** adding wait time after each miss. Raise **Max failures before lockout** if real users mistype on a shared kiosk. Shorten **Lockout duration** only after you can see the failures in Lidar. A lockout that never expires turns a guess into a denial of service against the real user. ## Breached passwords **Breached passwords** compares the password with known breach corpora using k-anonymity. Authdog does not send the raw password to the corpus. By default the check runs **On sign-up** and **On password change**, and **When a match is found** is **Hard block**. **Warn only** records the hit and still accepts the password. Use warn while you measure how many real users would be rejected, then switch back to hard block. The password policy on the **Password** card (minimum length, character classes) is a separate rule. A long password can still be breached. Keep both. ## Bots Two layers, and they stack: - **Bot detection** on this Policies tab scores sign-in and sign-up. **Sensitivity** runs from **Low, fewer challenges** to **High, aggressive**. **Challenge mode** is **Silent score only**, **Step-up challenge**, or **Block**. Enforcement uses Cloudflare bot scores when they are present. Lidar bot scoring still runs if you also enable a CAPTCHA. - **[Bot protection](/docs/bot-protection)** under **Authentication > Add-ons** is the hosted-page challenge: Google reCAPTCHA or Cloudflare Turnstile. Put an invisible challenge on `signup`, `waitlist`, and `password_reset` first. Those are the flows a script hits before it ever tries a password. Don't enable both CAPTCHA providers for the same action unless you intend reCAPTCHA to take over. ## Device risk **Device risk** covers two cases: - **Challenge unrecognized devices** asks for step-up when the sign-in fingerprint is new. This is the control for a stolen password used from a machine the user has never used. - **Impossible travel** compares successive sign-in locations with realistic travel time. **Normal** flags the session for Lidar. **Strict** challenges when a CAPTCHA is configured. **Off** skips the check. Strict mode without a CAPTCHA cannot present the challenge it wants. Configure [bot protection](/docs/bot-protection) first if you select Strict. ## Account enumeration A sign-up form that says "this email is taken" tells an attacker which accounts exist. On **Users > Settings**, turn on **Prevent account enumeration**. The form no longer reveals whether the address is registered. ## Rate limiting **Rate limiting** throttles selected endpoints for the environment. Set **Window (seconds)** and **Max attempts**, then **Scope** (`IP address`, `IP + user`, or `User`). Apply it to sign-in (including magic link and SMS or WhatsApp codes), sign-up, password reset, or any combination. **When exceeded** is **Challenge (soft)** or **Deny (hard block)**. Start with challenge on sign-in so a corporate NAT doesn't lock out a whole office. Use deny on password reset, where a burst is almost never a real user. ## What to turn on first 1. Leave brute force and breached-password hard block at their defaults. 2. Add an invisible CAPTCHA on sign-up, waitlist, and password reset. 3. Turn on **Prevent account enumeration** before you launch a public sign-up form. 4. Watch [Lidar Signals](/docs/console/lidar) for a day before you raise bot sensitivity to High. ## Related | Read | To learn how to | | --- | --- | | [Bot protection](/docs/bot-protection) | Configure reCAPTCHA or Turnstile per flow | | [Stop credential stuffing](/docs/recipes/stop-credential-stuffing) | Combine a CAPTCHA with Lidar monitors | | [Prevent signup abuse](/docs/recipes/prevent-signup-abuse) | Stop trial farming and waitlist flooding | | [Lidar](/docs/concepts/lidar) | Triage brute force, bots, and impossible travel | ### Access control Source: https://www.authdog.com/docs/secure/access-control.md Two different questions get called access control. First: who is allowed to become a user. Second: what that user may do after sign-in. Authdog answers them in different places. Mixing them up is how a public sign-up form ends up granting admin. ## Who can create an account Open **Authentication > Restrictions** for the environment. **Sign-up mode** is one of three: | Mode | Who gets an account | | --- | --- | | **Public** | Anyone who completes sign-up. | | **Restricted** | Nobody through the sign-up form. Access is invite, a user you create, or enterprise SSO. | | **Waitlist** | People can join, and you approve them before they have an account. See [Set up a waitlist](/docs/secure/waitlist). | **Enable user registration** on **Users > Settings** is a separate switch. Off means only existing users can sign in, even if the mode is Public. **Require signup for first login** stops a first Google (or other OAuth) sign-in from creating the user by itself. The browser lands on the hosted signup page instead. Leave registration on when you use that switch. The full sequence is in [Users](/docs/console/users#require-signup-for-first-login). ## Which emails qualify On the same Restrictions tab: - **Allowlist**: only these domains may sign in. This is the right default when the app is for one customer's employees. Add the domains before you enable it. An allowlist with no domains cannot be saved. - **Blocklist**: these domains may not sign in. - **Block email subaddresses**: `+`, `=`, and `#` aliases are allowed once per base address. After `ada+one@example.com` is used, `ada+two@example.com` is rejected for sign-up and for adding to an existing account. - **Block sign-ups that use disposable email addresses**: known throwaway domains are rejected. The same email rules are enforced on email/password registration and on waitlist join. Test an address you expect to fail before you announce the restriction. A typo in an allowlist locks out the customer you meant to admit. Enterprise SSO does not replace the allowlist. Domain routing on the connection picks the identity provider. The allowlist still decides whether that email's domain is welcome. See [Enterprise SSO](/docs/sso). ## What a signed-in user can do A session is not a permission. After the identity check, the environment uses exactly one authorization model: - **RBAC** when seats and roles match the product. - **ABAC** when the rule is attributes, written in Rego and evaluated at the edge. - **FGA** when access is a relationship on a specific object. Pick one and stay with it. [Authorization](/docs/concepts/authorization) compares them. Changing the model later rewrites every check in your app. Invitations (expiry defaults to 7 days, under **Authentication > Features**) are how a restricted environment still grows. They do not bypass authorization. An invited user still needs a role, a policy, or a relationship before a protected API should succeed. ## Related | Read | To learn how to | | --- | --- | | [Authorization](/docs/concepts/authorization) | Choose RBAC, ABAC, or FGA | | [Set up a waitlist](/docs/secure/waitlist) | Approve accounts instead of opening sign-up | | [Users](/docs/console/users) | Registration switches and the waitlist queue | | [Authentication](/docs/console/authentication) | Restrictions, methods, and features | ### Session security Source: https://www.authdog.com/docs/secure/session-security.md When a user signs in, Authdog issues a signed JWT. Your app checks that token on later requests. Session security is how long that token stays valid, how many devices may hold one, and how you take it back. The model is in [Sessions and tokens](/docs/concepts/sessions-tokens). This page is the settings. ## Lifetime Two clocks, and they are not the same setting: - **Token lifetime** (**Authentication > Sessions**) is how long an access token stays valid after it is issued. The range is 0–100,000 seconds. The default is 3,600 seconds (1 hour). Shorten it when a leaked token must die quickly. Lengthen it only for clients that cannot refresh often. A lifetime of 0 is not a session. Leave a window your app can refresh inside. - **Session timeout (minutes)** (**Users > Settings**, in Registration & sign-in) is idle invalidation. A user who walks away loses the session even if the token lifetime has not elapsed. **Allowed clock skew** on the Sessions tab (default 60 seconds, max 600) is tolerance between your clock and Authdog's. It is not extra lifetime. Raising it to hide a bad NTP setup also accepts tokens that are already expired by that many seconds. New values apply to tokens issued after you save. Tokens already in the wild keep the lifetime they were born with. ## How many sessions **Allow concurrent sessions** lets one user stay signed in on more than one device. Turn it off when the product is a single active device: a new sign-in should replace the previous one. Concurrent sessions do not weaken the signature. They widen the set of tokens you may need to revoke. ## Cookie and bearer The same JWT arrives two ways: - `authdog-session` on the browser: `HttpOnly`, `SameSite=Lax`, and `Secure` in production. - `Authorization: Bearer` when the client cannot store that cookie. Treat the bearer like a password. It is readable by whatever holds it. The cookie is not readable by page scripts. [Web security](/docs/secure/web-security) covers the difference. ## Revocation Sign-out clears the cookie and ends that session. A stolen bearer is still a valid signature until it expires, unless something else fails the next check. These end outstanding sessions on the next `userinfo` call: - Suspend or delete the [user](/docs/users). - Rotate or revoke the environment signing key. Follow [Rotate your API keys](/docs/secure/rotate-keys) so you don't reject tokens that are still legitimately in flight. - An inactive user is refused at sign-in. The browser goes to the account-disabled page. Your API should call `userinfo` (the SDK does this) when the decision matters. A local signature check alone cannot see a suspension that happened after the token was issued. ## Related | Read | To learn how to | | --- | --- | | [Sessions and tokens](/docs/concepts/sessions-tokens) | Claims, refresh, and how validation works | | [Sessions](/docs/console/sessions) | Token lifetime, clock skew, and signing keys | | [Rotate your API keys](/docs/secure/rotate-keys) | Swap a signing key without downtime | | [Backend requests](/docs/backend) | Validate the session on your server | ### Machine authentication Source: https://www.authdog.com/docs/secure/machine-authentication.md A job, a CI run, or an agent is not a user. If you put a person's session cookie or personal token in that process, you cannot revoke the workload without locking the person out, and a prompt or a log can leak a human credential. Give the workload its own credential. Pick one. Don't stack two for the same process. | Use this | When | | --- | --- | | **M2M application** | The project is the workload. No human users. OAuth 2.0 `client_credentials`. | | **Agents entry** | A named agent calls APIs or MCP tools inside a project that also has users. You need verify, revoke, and allowed tools. | | **Service account** | Organization-owned automation through the REST API, not the Agents module. | | **Personal access token** | One person's script on their own machine. It dies with that person. | The full create-and-call steps are in [Machine identity](/docs/machine-identity). [AgentSec](/docs/agentsec) is the longer story for agents that both sign in and call tools. ## What the token is An M2M or agent client exchanges its client id and secret at the environment token endpoint and receives a short-lived bearer. There is no refresh token. The process repeats the exchange before `expires_in`. The resource server validates that bearer the same way it validates a user token, then authorizes the action. A valid machine token names a caller. It does not grant every route. The client secret is shown once at creation. It stays in the process that calls the token endpoint. It does not go in a prompt, a tool description, a graph state, or a frontend bundle. The public key (`pk_...`) is the value that is safe to ship in an app that only verifies tokens. ## When something leaks Revoke the agent in [Agents](/docs/console/agents) when the subject is the OIDC client id. New tokens fail. A JWT that was already issued stays valid until `expires_in` unless the resource server enforces the trust store. A leaked client secret is the same class of incident as a leaked signing key: replace it, using [Rotate your API keys](/docs/secure/rotate-keys), and assume any token minted with the old secret is hostile until it expires. Don't reuse one M2M application across environments or across unrelated jobs. One secret should not be able to mint tokens for everything you run. ## Related | Read | To learn how to | | --- | --- | | [Machine identity](/docs/machine-identity) | Create the client and exchange credentials | | [AgentSec](/docs/agentsec) | Sign agents in and govern tool calls | | [Rotate your API keys](/docs/secure/rotate-keys) | Replace a leaked client secret | | [Authorization](/docs/concepts/authorization) | Decide what that machine may do | ### Web security Source: https://www.authdog.com/docs/secure/web-security.md Most session theft in a browser app is a script reading a token, a form posting from another site, or an authorization code landing on the wrong host. Authdog's defaults close the first two for the hosted cookie. You still own the headers on your own origin, and you still have to check webhook signatures. ## The session cookie After sign-in, Authdog sets `authdog-session`: - **`HttpOnly`**: page JavaScript cannot read it. An XSS bug on your origin can still act as the user while the cookie is sent, but it cannot exfiltrate the token to another host as easily. - **`SameSite=Lax`**: the browser will not attach the cookie to cross-site POSTs. That is the CSRF control for cookie sessions. - **`Secure` in production**: the cookie is not sent over plain HTTP. A bearer token in `Authorization` does not get those protections. Anything that can read your client storage can replay it until expiry. Prefer the cookie for a first-party web app. Use the bearer for mobile, a SPA calling your API from a context that cannot use the cookie, or a service. ## Your app's headers Authdog does not set a Content-Security-Policy on your origin. You do. A CSP that blocks unexpected scripts is what keeps an injected tag from running in the first place. The `HttpOnly` cookie is the backstop if that policy fails. Start from this policy and add the hosts your app actually loads: ``` Content-Security-Policy: default-src 'self'; script-src 'self'; connect-src 'self' https://*.authdog.com; frame-src https://*.authdog.com; img-src 'self' data: https://*.authdog.com; style-src 'self' 'unsafe-inline' ``` `connect-src` is the userinfo and token calls. `frame-src` is the hosted sign-in iframe, if you embed it. Drop `unsafe-inline` from `style-src` when your CSS does not need it. Add a CAPTCHA host here only if the challenge runs on your origin. Hosted Authdog pages send their own headers. Also send your pages over HTTPS only. The `Secure` cookie flag does not upgrade a page you served as HTTP. ## Redirects An OAuth redirect is a capability. If an attacker can choose the URI, they receive the code. Redirect URIs are checked against your verified domains. The match is exact, including scheme, port, and trailing slash. `https://app.example.com/callback` does not match `https://app.example.com/callback/`. A mismatch fails as `redirect_uri_mismatch` before the user sees a sign-in form. Set them per environment under **Redirects**. `dev` can point at localhost. `production` should not. See [Redirects](/docs/console/redirects). ## Hosted pages **Trusted domain reminder** (**Authentication > Features**) shows "Only sign in if you recognize this address" with the identity host on hosted sign-in and sign-up. Turn it on when users might be phished toward a lookalike host. It does not scan the user's mail. It only labels the page they are actually on. Custom domains serve those pages on your hostname. The reminder should show the host you expect users to recognize. See [Custom domains](/docs/custom-domains). ## Webhooks Events you subscribe to are easy to forge if you trust the JSON alone. Authdog signs the body with HMAC in `X-Authdog-Signature: t=...,v1=...` and retries delivery. Verify the signature and reject a timestamp that is too old before you create a user, revoke a seat, or send mail. The steps are in [Events and webhooks](/docs/events-webhooks). ## Related | Read | To learn how to | | --- | --- | | [Session security](/docs/secure/session-security) | Lifetime, idle timeout, and revocation | | [Redirects](/docs/console/redirects) | Callback, sign-out, and error URIs | | [Events and webhooks](/docs/events-webhooks) | Verify `X-Authdog-Signature` | | [Custom domains](/docs/custom-domains) | Serve hosted flows on your host | ### MFA account recovery Source: https://www.authdog.com/docs/secure/mfa-recovery.md A user who loses their authenticator cannot finish sign-in, and they should not be able to remove that factor with the factor they no longer have. Recovery is a support action. You verify who they are some other way, then you reset MFA in the console. Enrollment, factors, and the hosted challenge are in [Multi-factor authentication](/docs/mfa). This page is the reset. ## Reset a user 1. Confirm their identity outside the lost factor. A ticket, a video call, or an identity provider your company already trusts. The console reset does not do this check for you. 2. Open the [console](https://console.authdog.com) and select the environment the user belongs to. A reset in `dev` does nothing for `production`. 3. Open the user and disable MFA. 4. Ask them to enroll a new authenticator before they need to sign in from a new device. 5. Read [audit logs](/docs/audit-logs) for the disable action and for sign-ins you don't recognize. Disabling MFA deactivates the active TOTP secret and the backup codes that were issued with it. Treat the reset as privileged. Limit who can open the user record, and keep the approval next to the audit event. ## What the user can and cannot do Backup codes are returned when the user enrolls TOTP. Store them in a password manager at that moment. The hosted MFA screen accepts the 6-digit authenticator code or an 8-digit backup code. A backup code works once. Authdog marks it used and still opens the session. There is no public REST endpoint for enrollment or reset. Management operations the console uses are internal. Build the support process around the console, not around calling those operations from your app. If **Enforce MFA for all users** is on, the user must enroll again at the next sign-in. If MFA is only available and not enforced, a reset leaves them with a password-only account until they enroll. Decide which of those you want before you disable the factor. An unenforced account after a reset is a gap. ## After a suspicious reset A reset that the user did not ask for is an account takeover in progress. Suspend the user, review `MFA_SIGNIN_FAILURE` and later successful sign-ins, and rotate anything that user could reach. [Session security](/docs/secure/session-security) covers revocation. [Stop MFA fatigue](/docs/recipes/stop-mfa-fatigue) covers push bombing, which is a different failure: the user still has the factor, and someone is wearing them down. TOTP is not phishing-resistant. A site that proxies the code can pass this challenge. Recovery does not fix that. Fewer people who can disable MFA does. ## Related | Read | To learn how to | | --- | --- | | [Multi-factor authentication](/docs/mfa) | Factors, enrollment, and the hosted challenge | | [Audit logs](/docs/audit-logs) | Evidence of the reset and of later sign-ins | | [Users](/docs/console/users) | The user record you edit | | [Stop MFA fatigue](/docs/recipes/stop-mfa-fatigue) | Cap repeated MFA prompts | ### Set up a waitlist Source: https://www.authdog.com/docs/secure/waitlist.md A waitlist lets people ask for an account without creating one. You approve the row, then they get an invitation. Until you do, they are not a user and they have no session. Use it when sign-up should exist as a request, not as access. For a closed customer, [restricted mode](/docs/secure/access-control) is stricter: there is no public join form at all. ## Turn it on 1. Open the [console](https://console.authdog.com) and select the environment. 2. Go to **Authentication > Restrictions**. 3. Set **Sign-up mode** to **Waitlist**. You can also turn **Waitlist mode** on from **Users > Settings**. Either switch sends new sign-ups to the queue. Public mode and waitlist mode should not both be the story you tell users. Confirm the Restrictions mode after you save. Email rules still apply. An allowlist, a blocklist, disposable-email blocking, and the subaddress limit reject a join before it becomes a row. See [Access control](/docs/secure/access-control). ## Approve people Open **Users > Waitlist**. The table shows Email, Status, Source, Joined at, and Invited at. Rows appear only while sign-up is in waitlist mode. Approve an entry to create the user and send the invitation. Invitation expiry is the period under **Authentication > Features** (default 7 days). An approval that sits past that window needs a new invite. `USER_JOINED_WAITLIST` fires when someone joins. You can forward it with [events](/docs/events-webhooks) if another system should hear about the request. Approval is still a console action. The event is not the approval. ## Stop the queue from being the attack A waitlist without a challenge is a free inbox for scripts. They don't get a session, but they fill the table and your mail provider. On **Authentication > Add-ons**, enable reCAPTCHA or Turnstile for the `waitlist` action. An invisible challenge is enough to start. The walkthrough is [Prevent signup abuse](/docs/recipes/prevent-signup-abuse) and [Bot protection](/docs/bot-protection). Review the queue before you bulk-approve. A burst of similar addresses is the script, not demand. ## Related | Read | To learn how to | | --- | --- | | [Access control](/docs/secure/access-control) | Public, restricted, and waitlist modes | | [Users](/docs/console/users) | The waitlist table and registration switches | | [Bot protection](/docs/bot-protection) | Challenge the waitlist form | | [Prevent signup abuse](/docs/recipes/prevent-signup-abuse) | CAPTCHA plus a signup-abuse monitor | ### Geo blocking Source: https://www.authdog.com/docs/secure/geo-blocking.md Geo blocking rejects sign-in and sign-up when the request comes from a country you have marked. It is a country list, not a fraud score. A user on a VPN in an allowed country still gets through. A user at home in a blocked country does not. ## Block a country 1. Open the [console](https://console.authdog.com) and select the environment. 2. Go to **Authentication > Policies > Countries**. 3. Click countries on the map. The selection is amber. 4. Open **Review & confirm** to add them as blocked. Blocked countries turn red. 5. Click a red country to remove it. **Clear all** drops the list. Sign-in and sign-up from a blocked country fail with "Sign-in is not available from your country." An empty list blocks nobody. Saving a list enables the policy. The country comes from the edge (`cf-ipcountry`). **Unknown networks** covers Cloudflare `XX` (unknown) and `T1` (Tor). They are not countries, so a pin on the map does not match them. Set **Unknown networks** on the same tab: | Setting | What happens | | --- | --- | | **Allow** | Those requests continue. This is the default. | | **Challenge when a CAPTCHA is on** | The request continues only after the environment CAPTCHA accepts it. With no CAPTCHA configured, the request is denied. | | **Deny** | Sign-in and sign-up fail with "Sign-in is not available from this network." | A hosting provider or VPN in an allowed country still looks like that country. Pair this list with [bot detection](/docs/secure/attack-protection) when the exit node is the problem. ## What it does not do - It does not sign out a session that was issued before you blocked the country. Shorten [token lifetime](/docs/secure/session-security) if a block must take effect quickly for people already signed in. - It does not replace an email allowlist. A contractor in an allowed country with a personal domain is an [access control](/docs/secure/access-control) problem. - It does not follow the user after they connect through an exit node somewhere else. Pair it with [device risk](/docs/secure/attack-protection) if impossible travel matters. Test from a network you expect to block before you rely on the list. A wrong click on a large country locks out everyone there, including you, if you are deploying from that country. ## Related | Read | To learn how to | | --- | --- | | [Attack protection](/docs/secure/attack-protection) | Bots, lockout, and device risk | | [Access control](/docs/secure/access-control) | Who may create an account | | [Session security](/docs/secure/session-security) | How fast an existing session expires | | [Authentication](/docs/console/authentication) | Where the Policies tab sits | ### Rotate your API keys Source: https://www.authdog.com/docs/secure/rotate-keys.md Authdog has two kinds of key material, and you rotate them for different reasons. The **public key** (`pk_...`) only identifies the environment. It is safe in a frontend or a mobile app. Rotating it is not a security response. The **signing keys** inside the environment are what make a session signature valid. The **client secret** on an OIDC client is what lets a machine mint tokens. Those two are the ones you rotate when something leaks, or on a schedule before it does. ## Rotate a signing key Verification and signing are separate. The JWKS endpoint publishes every public key that is not revoked. Only keys marked **In use** sign new tokens. That is why you can introduce a key before you cut over. In **Authentication > Sessions**: 1. Click **New JSON Web Key**. It is created **Inactive**: published for verification, not yet signing. The default algorithm is RS256. 2. Wait until verifiers have refreshed their cached JWKS. SDKs that call `userinfo` pick up the new key on that path. Anything that verifies from a long-lived JWKS cache needs the cache window to pass. 3. Click **Use Key** on the new key so it signs new tokens. 4. Leave the previous key inactive until every token it signed has expired. That is at least one token lifetime. See [Session security](/docs/secure/session-security). 5. Click **Revoke** on the old key. Revoke is permanent. A revoked key never signs again. Tokens it already signed can still be verified from JWKS until they expire, which is why step 4 exists. Revoking first rejects sessions that are still honest. Keep at least one key **In use**. The full table, algorithms, and claims preview are in [Sessions](/docs/console/sessions). Each environment has its own keys. Rotate `production` on its own. A new key in `dev` does not replace the production signer. ## Replace a client secret The client secret is shown once, when you create the OpenID client, the M2M application, or the agent identity. Store it in a secret manager or in [Vault](/docs/vault) before you close the dialog. You cannot read it back from the console later. If it leaks: 1. Create a new client (or a new M2M application) for that workload and copy the new secret into the process that calls the token endpoint. 2. Deploy that process so it mints with the new secret. 3. Remove the old client so it cannot mint again. 4. Treat bearers the old secret already minted as valid until `expires_in`. There is no refresh token to cancel. Short lifetimes make this window small. [Machine authentication](/docs/secure/machine-authentication) is the model. Don't put the new secret in the same ticket, chat, or CI log that exposed the old one. ## After either rotation Confirm a fresh sign-in and a fresh client-credentials exchange in the environment you changed. Then look at [audit logs](/docs/audit-logs) for the key or client change, and for token use you don't recognize. A rotation that nobody asked for is an incident, not maintenance. ## Related | Read | To learn how to | | --- | --- | | [Sessions](/docs/console/sessions) | Key states, algorithms, and token lifetime | | [Machine authentication](/docs/secure/machine-authentication) | Which workload gets a client secret | | [Vault](/docs/vault) | Store the secret after you copy it | | [Sessions and tokens](/docs/concepts/sessions-tokens) | What the public key is for | ### Legal compliance Source: https://www.authdog.com/docs/secure/legal-compliance.md Compliance work splits in two. Your users need to see *your* terms and privacy policy when they sign in. You need a record of what Authdog did with their identity, and a way to request Authdog's own terms, privacy policy, and data processing agreement. ## What your users see Hosted sign-in can link to your legal pages. Open **Redirects > Compliance** and set the terms of service URL, the privacy policy URL, and any consent copy the flow should show. Those URLs are yours. Authdog does not write your privacy policy. Point them at the pages your counsel already published, on a host you control. Set them per environment if `dev` should not send people to the production policy, or point every environment at the same production URLs if the policy is one document. **Allow user to delete their account** (**Users > Settings**) exposes account deletion in the product UI. Turn it on when your policy says people can delete themselves. Deletion is not a substitute for an export or for a retention schedule you run in your own database. ## What you can show an auditor Identity events are already recorded: sign-ins, role changes, provisioning updates, MFA resets, impersonation grants. Read them in [Audit logs](/docs/audit-logs) and forward the same trail to your SIEM with [events and webhooks](/docs/events-webhooks). Verify the webhook signature before that system treats the body as evidence. Each environment has its own user store and keys. A customer or a stage that must stay apart gets its own environment, not a filter on a shared user table. See [Multi-tenancy](/docs/concepts/multi-tenancy). Impersonation and MFA reset are privileged. If your policy requires a reason and an approval, keep that approval beside the audit row. The product records the action. It does not collect the ticket number unless you put it in the process around the click. ## Authdog as a processor Authdog's terms, privacy policy, and subprocessors are public: - [Terms](/terms) - [Privacy](/privacy) - [Subprocessors](/subprocessors) Request a data processing agreement through [support](/docs/console/support). The subprocessors page is the list those agreements refer to. ## Related | Read | To learn how to | | --- | --- | | [Redirects](/docs/console/redirects) | Compliance URLs on hosted pages | | [Audit logs](/docs/audit-logs) | The identity events you can export | | [Events and webhooks](/docs/events-webhooks) | Copy that trail into your SIEM | | [Multi-tenancy](/docs/concepts/multi-tenancy) | Keep environments and user stores apart | ## Product ### Admin portal Source: https://www.authdog.com/docs/admin-portal.md The Admin Portal gives an administrator a short-lived link to a scoped setup task for a specific [organization](/docs/concepts/organizations) (account boundary) in a tenant and environment. Use it for org-scoped SSO, directory sync, or domain verification without giving that administrator access to your Authdog console. This is different from the [Account Portal](/docs/account-portal). The **Admin Portal** is for organization administrators configuring org-scoped setup. The **Account Portal** is for end users signing in and managing their own profile, credentials, and sessions. ## Choose a setup intent Generate a link for one organization and one task: - `sso`: open enterprise SSO setup. This is the default when `intent` is omitted. - `dsync`: open directory-sync setup. - `domain_verification`: open domain-verification setup. Keep each invitation focused. Generate another link when the administrator needs to perform a different task. ## Generate a link from your backend Call the authenticated REST API from trusted server code: ```http POST /v1/tenants/{tenantId}/environments/{environmentId}/portal/generate-link Authorization: Bearer Content-Type: application/json { "organizationId": "org_123", "intent": "sso" } ``` A successful response includes `link`, `intent`, and `expiresAt`. The organization ID is required. Tenant and environment come from the path, so verify all three identifiers belong to the customer context before making the request. Do not call this endpoint from browser code. Authentication to the endpoint and organization selection are privileged operations. ## Send and use the link 1. Authenticate the customer administrator in your application. 2. Check that they may administer the selected organization. 3. Generate the link immediately before use. 4. Redirect them to `link`, or deliver it through a secure channel. 5. After setup, verify the resulting SSO, directory-sync, or domain state before treating onboarding as complete. Links are signed by the selected environment and expire after **15 minutes**. They open an Authdog-hosted `/admin-portal` flow and do not require an Authdog dashboard login. ## Security guidance - Treat the URL as a temporary bearer credential. Anyone who obtains it may enter its scoped setup flow until expiry. - Never log the full URL or return its token separately to analytics, error reporting, or support tools. - Authorize organization membership and admin rights in your backend before generating a link. - Generate links on demand; do not precompute or store batches. - Use the correct environment. Links are environment-scoped and signed with that environment's active key. - Do not use an Admin Portal link as proof that setup succeeded. Read or test resulting configuration separately. Link generation fails when the environment has no active signing key. Unsupported intents and missing `organizationId` are rejected. ## Operational notes - Expired link: generate a new one; links are intentionally short-lived. - Wrong task: generate a new link with the intended `intent`. - Wrong customer or environment: discard the link and regenerate it with corrected identifiers. - Production rollout: exercise each intent in a non-production environment first, then use production tenant, environment, and organization IDs. ## Related - [Account Portal](/docs/account-portal): end-user authentication and account management - [SSO](/docs/sso): configure and operate enterprise connections - [Organizations](/docs/concepts/organizations): organization and membership model - [Provisioning](/docs/concepts/provisioning): directory sync and lifecycle - [Custom domains](/docs/custom-domains): identity-flow domain setup - [Security](/docs/security): key and token security model ### Widgets Source: https://www.authdog.com/docs/widgets.md Widgets let your application embed an Authdog-managed surface while your backend controls which user or organization it can act for. Integration starts by minting a short-lived widget token; do not expose a long-lived API credential to the browser. Widgets are not the hosted [Account Portal](/docs/account-portal). Account Portal sends an end user to Authdog-hosted account screens. A widget is initialized inside your application with a narrowly scoped token. ## Plan the widget context Before issuing a token, decide: - Which environment owns the data. - Which `userId` or `organizationId` the widget should act for. At least one is required. - Which widget capabilities it needs. Example requested scope names include `users-management` and `organization-switcher`. Authdog namespaces requested values as `widgets:`. If you omit scopes, the token receives `widgets:default`. ## Mint a token on your backend Call the authenticated REST API from server code: ```http POST /v1/tenants/{tenantId}/environments/{environmentId}/widgets/token Authorization: Bearer Content-Type: application/json { "organizationId": "org_123", "userId": "user_456", "scopes": ["organization-switcher"] } ``` The response contains `token`, `expiresAt`, and normalized `scopes`. Widget tokens expire after **10 minutes**. Token claims identify the token as a widget token (`typ: widget`, audience `widgets`) and bind it to the selected environment. They are signed with that environment's active signing key. ## Initialize and refresh 1. Authenticate the user in your application. 2. Authorize their access to the requested user or organization. 3. Ask your backend for a widget token. 4. Pass only that short-lived token to your supported widget integration. 5. Mint a replacement before or after expiry when the widget remains open. Do not copy a public widget initialization URL or package name from an unrelated example. Use the initializer supplied by the specific Authdog widget integration you are installing; token generation is the common server-side contract documented here. ## Security guidance - Never mint tokens directly in browser code. The REST call requires a privileged bearer token. - Derive `userId`, `organizationId`, and scopes from authenticated server-side state, not unchecked request fields. - Grant only scopes required by the mounted widget. - Do not use a widget token as an application session. Its token type and audience are intentionally separate. - Do not persist tokens in local storage or logs. Keep them in memory and replace them on expiry. - Never reuse a token across environments or organizations. Token creation fails when neither `userId` nor `organizationId` is present, when authentication fails, or when the environment has no active signing key. ## Operational notes - Refresh on expiry rather than increasing token lifetime; lifetime is fixed by the issuer. - Re-mint when organization, user, or workspace context changes. - Inspect returned `scopes` when debugging. Authdog adds the `widgets:` namespace. - A successful mint proves only that token issuance worked. Test widget behavior with the intended identity and organization. ## Related - [Components](/docs/components): authentication UI mounted in your application - [Account Portal](/docs/account-portal): hosted end-user account screens - [Organizations](/docs/concepts/organizations): organization context and membership - [Authorization](/docs/concepts/authorization): server-side access decisions - [Security](/docs/security): environment signing keys and token handling ### Machine identity Source: https://www.authdog.com/docs/machine-identity.md Pick one non-human identity. Do not stack them for the same workload. | Use this | When | Where | |----------|------|-------| | **M2M application** | The whole project *is* a backend, job, or CI workload. No human users in that project. | [Projects](/docs/console/projects) → **Machine-to-machine** | | **Agents entry** | A named AI agent or workload calls APIs or MCP tools *inside* an existing project. You need verify, revoke, allowed tools, and activity. The console module is self-serve (`agents`). | [Agents](/docs/console/agents) | | **Service account** | Organization-owned automation managed through the Authdog REST API, not the console Agents module. | `GET/POST/DELETE /v1/service-accounts` | | **Personal access token** | One person's local script or developer tool. Lifecycle stays attached to that person. | `GET/POST /v1/personal-access-tokens` | Agents are not users. Never put a human cookie or personal token in an unattended runtime. For the full agent security story — signing agents into apps plus governing what they call — see [AgentSec](/docs/agentsec). ## Machine-to-machine application Use an M2M application when a workload must authenticate without a person: backend-to-backend calls, jobs, or CI. Authdog registers a confidential OIDC client for the environment and uses OAuth 2.0 `client_credentials`. No browser session. ### Create an M2M application In the [Authdog console](https://console.authdog.com): 1. Create a new project or application. 2. Choose **Machine-to-machine** as its type. 3. Enter a descriptive name and, optionally, what the workload will access. 4. Create the application. 5. Copy the generated client ID and client secret before closing the dialog. Creation provisions a default environment and registers a client authorized only for `client_credentials`, using `client_secret_basic`. The client secret is shown once. Use a separate M2M application per workload and environment so one compromise does not rotate unrelated automation. ### Discover the token endpoint Read `token_endpoint` from the environment's OIDC metadata. Authdog's token path is `/oauth2/token`. Discovery keeps working with a [custom domain](/docs/custom-domains). ### Exchange credentials for a token ```bash curl -X POST "$TOKEN_ENDPOINT" \ -u "$AUTHDOG_CLIENT_ID:$AUTHDOG_CLIENT_SECRET" \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "grant_type=client_credentials" ``` Successful responses contain `access_token`, `token_type` (`Bearer`), `expires_in`, and scope `client_credentials`. `expires_in` comes from the registered client's access-token lifetime. No refresh token — repeat the exchange before expiry. ### Call a protected service ```http Authorization: Bearer ``` The receiving service must validate the token, then enforce authorization. A valid machine token identifies a caller; it does not grant every action. If application creation succeeds but credential generation fails, create or inspect the client under **Authentication > Clients**. ## Agents (named workloads) When the caller is an AI agent (or a workload you want to verify, scope, and revoke independently of the project type), register it in [Agents](/docs/console/agents). The console module is self-serve. **Create new agent identity** provisions a confidential OIDC client the same way M2M does, then writes a trust-store entry whose subject is that client ID. **Register existing identity** attaches an existing client ID, SPIFFE ID, or URI. Revoke in Agents is the issuer kill switch when the subject is the OIDC `client_id`: **new** tokens fail. SPIFFE or URI subjects stay in the registry for AuthZEN; they do not stop token mint. Outstanding JWTs remain valid until `expires_in` unless the resource server runs trust-store **enforce**. Pin the tool manifest if the agent talks to MCP — the hash is reviewable; it is not a live rug-pull detector. Public REST still lives under the trust-store path: ``` /v1/tenants/{tenantId}/environments/{environmentId}/mcp/trust-store ``` List, create, verify, revoke, and manage keys there. Console GraphQL (`mcpTrustStoreEntries` and related mutations) is what the Agents module calls. ## Service accounts Service accounts represent organization-owned automation with explicit scopes. They are a separate REST resource, not rows in Agents. - `GET /v1/service-accounts` - `GET /v1/service-accounts/{id}` - `POST /v1/service-accounts` - `DELETE /v1/service-accounts/{id}` Create with a descriptive `name`, optional `description`, and least-privilege `scopes`. The create response includes `clientId` and `clientSecret`. Capture the secret once. Deleting the account is the revocation path. ## Personal access tokens A PAT delegates the current user's access to scripts and developer tooling. Do not use it for shared services. - `GET /v1/personal-access-tokens` - `POST /v1/personal-access-tokens` - `POST /v1/personal-access-tokens/{id}/revoke` Create accepts `name`, optional `expiresAt`, and optional `scopes`. Only create returns `fullToken`. Set an expiry and revoke when the task ends or the owner leaves. ## Security guidance - Store client secrets in a secret manager, never source control, browser code, command history, or logs. - Keep token exchange in trusted workloads. - One client (or Agents entry) per workload. Least privilege on scopes and tools. - Cache access tokens only in memory, only until shortly before `expires_in`. Add jitter when many instances refresh. - On suspected exposure, revoke or replace credentials. Do not wait for leaked access tokens to expire. - Record client ID and workload name in audit context. Redact secrets and complete tokens. - Test `dev` and `prod` independently. Clients and signing context are environment-scoped. The token endpoint rejects inactive or unknown clients, incorrect authentication methods, bad secrets, clients not authorized for `client_credentials`, and revoked machine identities. ## Related - [Agents](/docs/console/agents): register, verify, and revoke named agent identities - [Agent frameworks](/docs/agent-frameworks): present that bearer on the framework hook - [MCP servers](/docs/console/mcp): playground and MCP project type - [Backend requests](/docs/backend#service-to-service-m2m): accepting machine tokens - [Sessions and tokens](/docs/concepts/sessions-tokens#service-to-service-tokens): token model - [Authorization](/docs/concepts/authorization): enforcing access after authentication - [Deployments](/docs/deployments): environment isolation - [Security](/docs/security): key and credential guidance ### AgentSec Source: https://www.authdog.com/docs/agentsec.md AgentSec is Authdog's security layer for AI agents: authenticate agents the way you authenticate people, and govern what they can call the way you govern workloads. One owner, one identity, one revoke switch per agent. Product page: [/product/agentsec](/product/agentsec). Most teams today hand agents the same static API keys humans use. Those keys can't tell you who is calling, can't be scoped to specific tools, live forever, and share one blast radius across every agent. AgentSec replaces that with two connected halves: | Half | Problem it solves | Where it lives | |------|-------------------|----------------| | **Agent authentication** | An agent signs **into your app** with its own identity, through the standard browser-based OIDC code flow — no shared credentials, revocable in one click | [Agent Sign-In](/docs/agent-sign-in) | | **Agent access control** | An agent **calls your APIs and MCP servers** with a registered identity: verified subject, allowed tools, kill switch, and per-agent activity | [Agents module](/docs/console/agents) | ## Authenticate agents into your app [Agent Sign-In](/docs/agent-sign-in) gives an agent what a person gets from your login page: an identity it owns, a browser-based sign-in loop your app already knows how to consume, and an owner who can cut it off. - Your app configures two values — its `https` origin as the `client_id` and a same-site redirect URI. No registration, no client secret. - The agent proves possession of a P-256 key: kept in its browser for interactive use, or a key file its own runtime signs with for headless operation. - The agent gets an OIDC identity: synthetic address, `sub`, `email`, `name`, `actor_type: agent` — standard claims your existing token validation already handles. - Approvals are remembered (180 days, sliding) so repeat sign-ins are zero-interaction, and honest revocation stops new sign-ins immediately while existing tokens die within minutes. ## Govern what agents call The [Agents module](/docs/console/agents) registers every non-human caller in your environment's trust store and gates what it can reach: - **Register and verify** the caller — a provisioned OIDC client, an existing client ID, a SPIFFE ID, or a URI. - **Scope allowed tools** with globs and allowed scopes; `mcp_rug_pull` detection fires when a pinned MCP manifest drifts from the live tool list. - **Revoke** is an issuer-side kill switch: new tokens fail immediately. - **Audit** every tool invocation, denial, and trust violation per agent in the Activity tab — not one undifferentiated "API key" in your logs. For agents that act on a user's behalf, delegation uses RFC 8693 token exchange with an explicit `act` chain — never account impersonation. ## Shared guarantees Both halves run on the same rules: | Guarantee | What it means | |-----------|---------------| | Owner-bound | Every agent identity has an accountable human owner in your directory; recreating an identity mints a new `sub` | | Per-environment | An agent registered in `dev` is not registered in `prod`; scoping never leaks across environments | | Revocable | One click stops new sign-ins and new tokens; existing tokens live at most their documented minutes | | Auditable | Authentication and tool activity land in the same environment audit trail | | No shared credentials | Each agent holds its own key; a leaked key compromises one agent, not the fleet | ## Where to start | If you… | Read | |---------|------| | Build an app that agents should sign in to | [Agent Sign-In](/docs/agent-sign-in) | | Run APIs or MCP servers that agents call | [Agents module](/docs/console/agents) | | Are choosing between M2M, agents, service accounts, and PATs | [Machine identity](/docs/machine-identity) | | Want a first agent running end to end | [Agents quickstart](/docs/quickstarts/agents) | | Are wiring that identity into an agent framework | [Agent frameworks](/docs/agent-frameworks) | The Agents module is self-serve. ### Agent Sign-In Source: https://www.authdog.com/docs/agent-sign-in.md Agent Sign-In lets an AI agent sign in to your application the way a person does — through the browser-based OAuth/OIDC authorization code flow — with an identity its owner controls. Your app adds two config values. The agent enrolls once. After that, the agent signs in by itself, and the owner can revoke it with one click. Agents here sign **into your app**. If you instead need to verify agents that call your API directly, use [machine identity](/docs/machine-identity). To attach that caller inside A2A, Eve, MCP, LangGraph, or the OpenAI Agents SDK, follow [Agent frameworks](/docs/agent-frameworks). For the complete picture, see [AgentSec](/docs/agentsec). ## How it works Five things, no more: | Thing | What it is | |-------|------------| | **Agent identity** | A synthetic address like `agent-support@acme.com` on a tenant domain you have verified, owned by a directory user | | **Sign-in key** | The public half of a P-256 keypair generated in the agent's browser. The private half never leaves that browser | | **Open client** | Your app's `client_id` is simply its `https` origin. No registration, no secret, no client row | | **Registered client** | A confidential OIDC client with `client_id` + secret (console **Agents → Sign-In → Registered relying party**, or `registerOidcClient` with `agentSignIn: true`). Up to 10 redirect URIs. May request `owner_profile`, `owner_email`, and `owner_sub` for accountable-human claims. | | **Remembered approval** | The first sign-in asks the agent (a human approving on its behalf) to trust your app; approval is remembered and slides 180 days | | **Honest revocation** | The owner can suspend or revoke the agent, or revoke a single key, at any time — with exactly the effects documented below | ## Where everything lives Every environment is its own OIDC issuer: | Piece | Value | |-------|-------| | Issuer (`iss`) | `https://id.authdog.com/oidc/{environmentId}` | | Discovery document | `https://id.authdog.com/oidc/{environmentId}/.well-known/openid-configuration` | | JWKS (public keys) | `https://id.authdog.com/oidc/{environmentId}/.well-known/jwks.json` | Find `{environmentId}` in the console under your environment's settings. If you mapped a vanity domain onto the environment, it serves the same paths — the issuer string is whatever the discovery document says it is. The discovery document is the source of truth for endpoints (`authorization_endpoint`, `token_endpoint`, `userinfo_endpoint`, `jwks_uri`) and for the signing algorithm (`id_token_signing_alg_values_supported` — the environment's active key, ES256 or RS256). Validate tokens against the JWKS by `kid` and refetch the JWKS when you see an unknown `kid`; keys rotate. ## Your app: open client (default) Point any OAuth/OIDC library at your Authdog environment: 1. `client_id` — your app's **https origin**, e.g. `https://app.example.com`. That's it. Nothing to register. 2. `redirect_uri` — any https URL **on that origin (or a subdomain)**, e.g. `https://app.example.com/callback`. Two rules are enforced server-side: - **PKCE with `code_challenge_method=S256` is mandatory.** Open clients have no secret; PKCE is the transaction integrity. The challenge is `base64url(SHA256(verifier))`; the verifier is a 43–128 character random string, as in RFC 7636. - **Scopes are capped at `openid email profile`.** Owner scopes (`owner_*`) are rejected for open clients. ## Registered client (owner claims) When you need `owner_sub`, `owner_name`, or `owner_email` on the agent token, register a confidential client in the console (**Agents → Sign-In**). Use the issued `client_id` and `client_secret` with PKCE. Redirect URIs must match the registered list exactly (up to 10). Allowed scopes include `owner_profile`, `owner_email`, and `owner_sub`. Open https-origin clients still cannot request owner scopes. Local development: `http` and `localhost` client_ids are rejected, so test through a tunnel or a staging domain with real https. ### Start the sign-in ``` GET https://id.authdog.com/oidc/{environmentId}/authorize? client_id=https%3A%2F%2Fapp.example.com &redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback &response_type=code &scope=openid%20email%20profile &state= &nonce= &code_challenge= &code_challenge_method=S256 ``` `state` and `nonce` are optional but recommended; any OIDC library generates and checks both for you. On success you are redirected to your `redirect_uri` with a single-use `code` (valid 60 seconds) and your `state`. On failure the redirect carries `error` and `error_description` instead. ### Exchange the code ```bash curl -X POST "$TOKEN_ENDPOINT" \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "grant_type=authorization_code" \ --data-urlencode "client_id=https://app.example.com" \ --data-urlencode "code=$CODE" \ --data-urlencode "redirect_uri=https://app.example.com/callback" \ --data-urlencode "code_verifier=$VERIFIER" ``` Response: ```json { "access_token": "…", "token_type": "Bearer", "expires_in": 600, "id_token": "…", "scope": "openid email profile" } ``` There is **no refresh token** — by design. A refresh token is a standing credential; agents re-run the authorization code flow instead, which is zero-interaction once the approval is remembered. When the access token expires, run `authorize` again. Errors you can actually hit: | `error` | Where it surfaces | Cause | |---------|-------------------|-------| | `invalid_request` | authorize redirect | Missing PKCE S256, non-https `client_id`, cross-site `redirect_uri` | | `invalid_scope` | authorize redirect | Scope outside the `openid email profile` ceiling | | `invalid_grant` | token endpoint response | Code expired (60 s), already used, `redirect_uri` mismatch, or `code_verifier` does not match the challenge | ### Validate the id_token The `id_token` and the access token are both signed JWTs. Verify the signature against the JWKS (`{issuer}/.well-known/jwks.json`, key picked by the token's `kid` header), using the algorithm advertised in discovery. Then check `iss` (exact issuer string above), `aud` (your exact `client_id`), `exp`, and the `nonce` you sent. The access token carries the same `iss`/`aud` plus `typ: "agent_at"` — accept it at your own API or pass it to userinfo; it is not a credential for anyone else. One consistency rule: `iss` is the host the sign-in ran against. If you use a vanity domain, run discovery, authorize, and validation all against that same host. A decoded id_token looks like: ```json { "sub": "9vK2mX...", "actor_type": "agent", "email": "agent-support@acme.com", "email_verified": true, "name": "Support agent", "preferred_username": "agent-support_acme-com", "iss": "https://id.authdog.com/oidc/env_01J...", "aud": "https://app.example.com", "iat": 1791278000, "exp": 1791278600, "nonce": "2c1e4d...", "jti": "018f...", "scope": "openid email profile" } ``` ### Call userinfo ```bash curl "$USERINFO_ENDPOINT" -H "Authorization: Bearer $ACCESS_TOKEN" ``` (`userinfo_endpoint` comes from discovery, like the others.) The issuer re-checks the agent's status and the signing key's status on **every** userinfo call. A suspended agent, a revoked agent, or a revoked key fails the call immediately with `invalid_token` — and because tokens live at most 10 minutes, every token everywhere stops working within that window. ## The agent side ### Enroll once The agent's owner creates the agent identity in the [console](/docs/console/agents) and opens the generated enrollment link in the agent's browser (the browser profile the agent actually runs in). That page offers two storage options for the same key model: - **Generate key in this browser** — generates an ECDSA P-256 keypair locally, marks the private half non-exportable and never transmitted, keeps it in the browser, and sends only the public half plus a key id (`kid`) to Authdog. - **Generate key file (headless agents)** — for agents that sign in from their own runtime (no browser). The private half is written to an `agent-key.pem` file only you control; move it to the agent's runtime. Store it like a password. The key id is shown for the sign-in command. Keys live **30 days** from activation. Enroll a new key before the old one expires to avoid a sign-in gap. Up to **5 active keys** per agent are kept — rotation means: enroll the new key, confirm sign-in works, revoke the old one. The agent's subject (`sub`) never changes during rotation. ### Sign in When your app redirects to `authorize`, the issuer opens a waiting page in the agent's browser: - **Headless agents** — the page shows a `RUN THIS WITH YOUR AGENT` command. The agent's runtime signs the page's one-time auth token with its key file and POSTs the proof: ```bash # Values from the waiting page (export so the node child sees them): export AUTH_TOKEN= export AUTHDOG_KID= # Sign the token with the P-256 key file. The issuer expects the raw # 64-byte r||s signature (WebCrypto / IEEE P1363 form), base64url — # not the DER form openssl emits by default. Node does this directly: SIGNATURE=$(node -e ' const crypto = require("crypto"); const fs = require("fs"); const sig = crypto.sign("sha256", Buffer.from(process.env.AUTH_TOKEN), { key: fs.readFileSync("./agent-key.pem"), dsaEncoding: "ieee-p1363", }); process.stdout.write(sig.toString("base64url")); ') curl -X POST "https://id.authdog.com/oidc/{environmentId}/agents/wait" \ --data-urlencode "auth_token=$AUTH_TOKEN" \ --data-urlencode "kid=$AUTHDOG_KID" \ --data-urlencode "signature=$SIGNATURE" ``` JSON **approve API** (same proof, for automation): `POST /oidc/{environmentId}/agents/approve` with `{ "auth_token", "kid", "signature", "accept_disclosure": true }`. Form POST to `…/agents/wait` remains valid for the curl snippet on the page. **Authdog CLI** (recommended): ```bash authdog agent doctor authdog doctor --agent-sign-in # same key/issuer checks inside global doctor authdog agent rp-init --name "My agent app" --redirect-uri https://app.example/callback authdog agent wait --poll --auth-token "$AUTH_TOKEN" authdog agent prove --accept-disclosure ``` Uses `AUTHDOG_KEY`, `AUTHDOG_KID`, `AUTH_TOKEN`, `IDENTITY_HOST`, and `ENVIRONMENT_ID` (or a linked `.authdog/project.json` for issuer + env id). `rp-init` requires `authdog login` and calls management GraphQL (`AUTHDOG_MANAGEMENT_GRAPHQL`, default `https://mgt.authdog.com/graphql`); it prints `client_secret` once and writes `.authdog/agent-rp.json` without the secret. Prove requires `--accept-disclosure` after the operator has read the waiting-page disclosure. Staging checklist: `specs/ops/agent-sign-in-live-verify.md` in the platform repo. Legacy Bun scripts (same contract): ```bash bun services/openid-server/scripts/agent-signin-doctor.ts bun services/openid-server/scripts/agent-signin-prove.ts ``` The page shows what your app will receive, polls, and the moment the proof lands the browser is sent back to your `redirect_uri` with the code. The transaction expires in **5 minutes** — if no proof lands in that window, no redirect fires and your app simply never hears back; start `authorize` again. The command is safe to re-run against a fresh page. Proving also remembers the approval for your app, so later headless sign-ins stay one command. - **Browser agents** — "Sign in manually" opens the browser-key path: the agent proves possession of its stored key by signing a short-lived challenge, gets a 30-day session cookie, and is sent straight back to your app. The first sign-in for a given app also shows a one-time approval page. Both paths are automatic on every later sign-in — the whole loop is zero-interaction from your app's point of view. ## Tokens and claims The `id_token` is a signed JWT with a **10-minute lifetime** (600 seconds), signed with the environment's active key and pinned by `kid`. `aud` is your exact `client_id` (the origin), `iss` is the environment issuer, and the `nonce` you sent is echoed back. | Claim | Scope | Value | |-------|-------|-------| | `sub` | always | The agent's 43-character opaque subject. Stable while the agent exists; **a deleted and recreated agent gets a new `sub`** | | `actor_type` | always | The literal string `agent` | | `email` | `email` | The agent's synthetic address, e.g. `agent-support@acme.com` | | `email_verified` | `email` | `true` — the address is synthetic on a domain the tenant verified | | `name` | `profile` | The agent's display name | | `preferred_username` | `profile` | Derived from the address: local part + `_` + domain with dots as dashes (`agent-support_acme-com`) | | `jti` | always | Unique token id | | `scope` | always | The granted scope set, space-separated | Claims are **present or absent, never null**: if a scope was not granted, its claims are simply omitted from the token and the userinfo response. There are no `owner_*` claims in this release. An empty `scope` parameter falls back to `openid email`. Any enrolled agent can sign in to any open client — that is what makes the model spread. Your app decides which agents it trusts: key policy off `actor_type`, `sub`, or the address domain (e.g. only accept `*@your-customer.com`). Per-app allowlists arrive with registered clients in a later release. ## Lifetimes Every number here is pinned by the issuer's test suite. | Value | Lifetime | |-------|----------| | Waiting-page auth token | 5 minutes, one proof | | Authorization code | 60 seconds, single use | | Access token / id_token | 10 minutes (600 seconds) | | Sign-in challenge | 5 minutes | | Agent session cookie | 30 days | | Sign-in key | 30 days from activation | | Remembered approval | 180 days, sliding (refreshed at each sign-in) | ## Revocation semantics Revocation is honest — each action does exactly this and nothing else: | Action | Immediate effect | Tokens already issued | Sessions your app created | |--------|------------------|-----------------------|--------------------------| | **Revoke a sign-in key** | That key can no longer prove sign-in; new sign-ins with it stop | userinfo fails immediately (live re-check); the token itself never outlives 10 minutes | Not affected — they belong to your app | | **Suspend the agent** | All sign-ins blocked | userinfo fails immediately; tokens never outlive 10 minutes | Not affected | | **Revoke the agent** | Same as suspend, and the agent is marked revoked permanently | userinfo fails immediately; tokens never outlive 10 minutes | Not affected | | **Delete the agent** | Everything above, plus the identity is gone | userinfo fails immediately | Not affected | Third-party app sessions created from an agent sign-in are the app's sessions — Authdog revokes the Authdog side (sign-ins and tokens), never your app's session records. ## Security notes - `client_id` values that are not https origins (http, localhost, URLs with paths) are rejected before anything else happens. - Redirect URIs must be https and on the client origin's host or a subdomain of it. - Codes are stored hashed and are single-use; replaying one returns `invalid_grant`. - The agent endpoints are rate-limited per environment and per IP alongside the rest of the OIDC surface. - Approving an app is remembered **per scope set and per redirect URI**: if your app later requests different scopes or a different callback, the approval page is shown again. ### Agent frameworks Source: https://www.authdog.com/docs/agent-frameworks.md Authdog is the identity provider. The framework keeps its own auth hook. Register an agent, mint a short-lived bearer with `client_credentials`, and verify that bearer at the hook before the agent, tool, or graph runs. Each hook is grouped under [AI](/docs/ai). The group has pages for the auth boundary, machine credentials, tool checks, and what you put in the prompt. Runnable copies live in [authdog/agents-samples](https://github.com/authdog/agents-samples). Copy one folder. Each folder is its own project. ## Which path | You need | Open | | --- | --- | | An agent signs into your app through the browser | [Agent Sign-In](/docs/agent-sign-in) | | An agent calls your API, an MCP server, or another agent | The framework pages below | | An MCP client signs a person in through hosted OAuth | [MCP authentication](/docs/mcp-authentication) | | An agent acts as a person | [Acting for a person](#acting-for-a-person) | ## Where the hook is | Framework | Hook | Role in the sample | | --- | --- | --- | | [A2A](/docs/agent-frameworks/a2a) | Agent card security scheme, then `require_auth` on the JSON-RPC mount | Server admits the caller. The client sends the bearer on HTTP. | | [Eve](/docs/agent-frameworks/eve) | Channel `auth` walk: `oidc({ issuer, audiences })` | Session routes verify the bearer JWT. | | [MCP](/docs/agent-frameworks/mcp) | `token_verifier` on the MCP server | `whoami` runs only after userinfo accepts the token. | | [LangGraph](/docs/agent-frameworks/langgraph) | `require_auth` on `POST /invoke` | The graph runs only after that dependency returns a user. | | [OpenAI Agents SDK](/docs/agent-frameworks/openai-agents) | Tool `RunContext` plus `require_auth` on the API the tool calls | The tool presents the token. The route decides. | | [CrewAI](/docs/agent-frameworks/crewai) | `EXECUTION_START` raises `HookAborted` | The crew does not start when userinfo rejects the bearer. Sample planned. | | [Google ADK](/docs/agent-frameworks/adk) | `before_agent_callback` | Returning content skips the agent. Sample planned. | | [Pydantic AI](/docs/agent-frameworks/pydantic-ai) | `RunContext` deps, then `require_auth` on your API | The tool presents the token. The route decides. Sample planned. | | [Claude Agent SDK](/docs/agent-frameworks/claude-agent) | `PreToolUse` returns `permissionDecision: deny` | The hook blocks the tool. Sample planned. | | [Mastra](/docs/agent-frameworks/mastra) | `server.auth.authenticateToken` | API routes require a userinfo-accepted bearer. Sample planned. | | [LlamaIndex Workflows](/docs/agent-frameworks/llamaindex) | `require_auth` on the route that calls `workflow.run` | The first step stops when the subject is missing. Sample planned. | | [Semantic Kernel](/docs/agent-frameworks/semantic-kernel) | `FUNCTION_INVOCATION` filter skips `next` | The kernel function does not run. Sample planned. | | [Vercel AI SDK](/docs/agent-frameworks/ai-sdk) | `toolsContext` on the tool, then `require_auth` on your API | The tool presents the token. The route decides. Sample planned. | | [Strands Agents](/docs/agent-frameworks/strands) | `BeforeToolCallEvent.cancel_tool` | A cancelled call does not run the tool. Sample planned. | | [LangChain](/docs/agent-frameworks/langchain) | `wrap_tool_call` does not call the handler | The middleware stops the tool. Sample planned. | Rows marked "Sample planned" describe the hook. The folder is not in the repository yet, and the page does not pin a package version. `subject_if_accepted` and `subjectIfAccepted` in those snippets are the sample's userinfo helper. They are not framework APIs. They return the subject, or nothing when the bearer is missing or rejected. ## Register the caller In the console, open **Agents → Create new agent identity**. Copy the client id and client secret. The secret is shown once. The protected process takes the environment **public key** (`pk_...`) from Project settings. That key selects the identity host. It does not mint tokens. Keep the client secret in the process that calls the token endpoint. Put the access token on the HTTP request. Leave both out of prompts, graph state, and logs. The Agents module is self-serve. If the caller is a workload rather than a named agent, an [M2M application](/docs/machine-identity) issues the same `client_credentials` grant. Keep a development token off your production API. ## Mint the bearer Read `token_endpoint` from the environment discovery document. Authdog's path is `/oauth2/token`. ```bash curl -X POST "$TOKEN_ENDPOINT" \ -u "$AUTHDOG_CLIENT_ID:$AUTHDOG_CLIENT_SECRET" \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "grant_type=client_credentials" ``` The response carries `access_token`, `token_type` (`Bearer`), and `expires_in`. There is no refresh token. Repeat the exchange before expiry. Send it as `Authorization: Bearer `. ## What verification returns `require_auth` and `fetch_user_data` both check the bearer with the environment userinfo endpoint. The public key chooses that host. A missing bearer, a userinfo rejection, or a payload with no subject is `401`. An accepted payload looks like the samples' stand-in: ```json {"meta": {"code": 200}, "user": {"id": "agent_1"}} ``` The samples read `user.id`, then `user.sub`, and return that string. Where the framework has a scope field, they record `agent:invoke`. | Check | Result | | --- | --- | | Missing or rejected bearer | `401` | | Accepted userinfo | Subject from `id`, then `sub` | | Scope field, when the framework has one | `agent:invoke` | | Refresh token | None. Exchange again before `expires_in`. | | `pk_...` | Selects the identity host. Stays on the resource server. | Eve is the exception on this page: it verifies the JWT with discovery instead of calling userinfo. The subject still has to be the one in that token. See [Eve checks the JWT](#eve-checks-the-jwt). ## After the subject is known The samples stop once the subject is known. A `200` from `whoami` means userinfo accepted the bearer. It does not mean the agent may call every tool. The [Agents](/docs/console/agents) registry is the next check on that subject: | Control | Effect | | --- | --- | | Allowed scopes and tools | Globs on the trust-store row. `*` allows every tool. | | Trust-store enforce | The [MCP trust-store sample](https://github.com/authdog/agents-samples/tree/main/trust-store/python) applies that row. The other samples stop at the subject. | | Revoke | New tokens fail when the subject is the OIDC client id. Tokens already issued last until `expires_in` unless the resource server enforces the trust store. | Apply [authorization](/docs/concepts/authorization) after the identity check when the API is yours. ## Acting for a person The framework hook stays the same. Replace the `client_credentials` access token with an [RFC 8693 token](/guides/agent-delegation). `sub` stays the user. `act` names the agent. ```json { "sub": "", "act": { "sub": "client:" } } ``` Put that token in the `Authorization` header, or in the OpenAI Agents [`Caller.token`](/docs/agent-frameworks/openai-agents). Grant token exchange on the agent in [Agents](/docs/console/agents). The [delegation sample](https://github.com/authdog/agents-samples/tree/main/delegation/python) reads a token whose subject is the person. The other samples mint a token whose subject is the agent client. ## One token, three callers Mint once. Present that bearer to each userinfo gate. Run one sample at a time, or bind the ports below so they do not share `8000`. ```bash TOKEN=$(curl -s -X POST "$TOKEN_ENDPOINT" \ -u "$AUTHDOG_CLIENT_ID:$AUTHDOG_CLIENT_SECRET" \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "grant_type=client_credentials" \ | python -c 'import json,sys; print(json.load(sys.stdin)["access_token"])') ``` LangGraph, from `langgraph/python` with `PK_AUTHDOG` set: ```bash uvicorn server:create_app --factory --port 8001 curl -s -X POST http://127.0.0.1:8001/invoke \ -H "Authorization: Bearer $TOKEN" ``` The JSON reply is `{"reply":"subject:"}`. The same call without the header is `401` and the body has no `subject:`. MCP, from `mcp/python` with `PK_AUTHDOG` set. The verifier reads this header. `POST /mcp` without it is `401`. The sample's `whoami` tool returns the subject: ```bash uvicorn server:create_app --factory --port 8002 curl -s -D - -o /dev/null -X POST http://127.0.0.1:8002/mcp \ -H "Authorization: Bearer $TOKEN" \ -H "content-type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"initialize"}' ``` OpenAI Agents, from `openai-agents/python`. The route is the gate. The tool only attaches the header. `OPENAI_API_KEY` is required for `Runner.run`. `pytest` does not need it. ```bash uvicorn tool_api:create_app --factory --port 8003 export WHOAMI_URL=http://127.0.0.1:8003/whoami export AUTHDOG_ACCESS_TOKEN="$TOKEN" ``` ```python context=Caller(token=os.environ["AUTHDOG_ACCESS_TOKEN"]) ``` `GET /whoami` with the bearer returns `{"subject":""}`. ### Eve checks the JWT Eve does not call userinfo. `oidc()` verifies the bearer as a JWT against the issuer discovery document: signature, issuer, expiry, and audience. Agent access tokens for the environment are JWTs. Set `AUTHDOG_ISSUER` to the token's `iss` and `AUTHDOG_AUDIENCE` to its `aud`. The Eve process reads neither the client secret nor `PK_AUTHDOG`. `npx eve dev` listens on port 2000 unless `PORT` is set. ```bash curl -s -D - -o /dev/null -X POST http://127.0.0.1:2000/eve/v1/session \ -H "Authorization: Bearer $TOKEN" ``` A session request with no bearer is `401`. The health route stays public. ## Python gate A2A, MCP, LangGraph, and the OpenAI Agents sample import `require_auth` or `fetch_user_data` from the Python SDK in [authdog/web-sdk](https://github.com/authdog/web-sdk). That package is installed from commit `afad543932541a9e9d3c891421151509d04b49b2` in each sample's `requirements.txt`. `pip install authdog-fastapi` on the public index is a different name and is not the install those samples use. Eve verifies the JWT with Eve's own `oidc()` helper and the issuer discovery document. ## Related | Read | To learn how to | | --- | --- | | [Machine identity](/docs/machine-identity) | Choose M2M, Agents, a service account, or a PAT | | [Agents quickstart](/docs/quickstarts/agents) | Register an agent and mint the first token | | [MCP authentication](/docs/mcp-authentication) | Let an MCP client sign in through the hosted issuer | | [Agent delegation](/guides/agent-delegation) | Keep the user as `sub` when an agent acts for them | ### A2A Source: https://www.authdog.com/docs/agent-frameworks/a2a.md An A2A agent names Authdog on its public agent card, then admits message calls only after `require_auth` accepts the bearer. The client exchanges `client_credentials` out of band and sends the token on the HTTP request. The JSON-RPC body stays the message. Sample: [a2a/python](https://github.com/authdog/agents-samples/tree/main/a2a/python) in [authdog/agents-samples](https://github.com/authdog/agents-samples). Python 3.10+. Package `a2a-sdk[http-server]==1.2.3`. Channel auth, machine credentials, tool checks, and instructions for this framework are the other pages under A2A in [AI](/docs/ai/a2a/channel-auth). Channel auth, machine credentials, tool checks, and instructions for this framework are the other pages under A2A in [AI](/docs/ai/a2a/channel-auth). Channel auth, machine credentials, tool checks, and instructions for this framework are the other pages under A2A in [AI](/docs/ai/a2a/channel-auth). ## Two surfaces | Surface | Auth | | --- | --- | | `GET /.well-known/agent-card.json` | Public. Names the token URL and scope `agent:invoke`. | | `POST /a2a/` | `authdog.starlette` `require_auth` before the executor runs. | The card is how a client discovers the scheme. The middleware is the gate. Publishing the card does not protect `SendMessage`. ## Declare the scheme Build an OAuth 2 client-credentials scheme whose token URL is the environment `token_endpoint`. Attach it as security scheme `authdog` with scope `agent:invoke`. ```python flow = ClientCredentialsOAuthFlow(token_url=token_url) flow.scopes["agent:invoke"] = "Call this agent" scheme = SecurityScheme( oauth2_security_scheme=OAuth2SecurityScheme( description="Authdog agent identity (client credentials)", flows=OAuthFlows(client_credentials=flow), ) ) requirement = SecurityRequirement() requirement.schemes["authdog"].list.append("agent:invoke") ``` Pass `security_schemes={"authdog": scheme}` and `security_requirements=[requirement]` on the `AgentCard`. The token URL is public configuration. The client secret never goes on the card. ## Gate the JSON-RPC mount Construct `Authdog(public_key=...)` from `PK_AUTHDOG`. Wrap the `/a2a` routes in middleware that calls `require_auth` and copies the subject onto the request before the A2A handler runs. ```python class _Gate(BaseHTTPMiddleware): async def dispatch(self, request: Request, call_next): try: user = await authdog.require_auth(request) except HTTPException: return JSONResponse( {"error": "Unauthorized"}, status_code=401, headers={"WWW-Authenticate": "Bearer"}, ) request.state.authdog_subject = subject_of(user) return await call_next(request) ``` `subject_of` reads `user["id"]`, then `user["sub"]`. A custom A2A context builder copies `authdog_subject` into `context.call_context.state`. The executor replies with `subject:` and does not look at the raw `Authorization` header. `SendMessage` requires the `A2A-Version: 1.0` header. Without it the SDK reports that 0.3 is not supported, which is a protocol error, separate from the 401. ## Call it ```python response = await http.post( token_url, data={"grant_type": "client_credentials"}, auth=(client_id, client_secret), headers={"Content-Type": "application/x-www-form-urlencoded"}, ) token = response.json()["access_token"] async with httpx.AsyncClient( headers={"Authorization": f"Bearer {token}"}, ) as http: card = await A2ACardResolver(http, base_url).get_agent_card() client = ClientFactory( ClientConfig(httpx_client=http, streaming=False) ).create(card) ``` The same HTTP client fetches the card and sends the message, so the bearer rides on both. Card fetch succeeds without a token. Message calls do not. Mint again when `expires_in` is close. The sample client performs one exchange per run. ## What you should see 1. `GET /.well-known/agent-card.json` with no token returns 200 and includes the token URL and `agent:invoke`. 2. `POST /a2a/` with `A2A-Version: 1.0` and no bearer returns `401` and `{"error":"Unauthorized"}`, plus `WWW-Authenticate: Bearer`. The body does not contain `subject:`. 3. The same call with a token userinfo accepts returns a message whose text is `subject:`. ## Configure | Variable | Process | Purpose | | --- | --- | --- | | `PK_AUTHDOG` | Server | Public key. Selects the identity host. | | `AUTHDOG_TOKEN_URL` | Server and client | Token URL written on the card and used for the exchange. | | `A2A_PUBLIC_URL` | Server | URL advertised on the card for the JSON-RPC binding. | | `AUTHDOG_CLIENT_ID` / `AUTHDOG_CLIENT_SECRET` | Client | Agent credentials. Server does not read them. | | `A2A_BASE_URL` | Client | Origin the client resolves the card from. | > **Caution** > > Install `authdog-fastapi` from the pinned [web-sdk](https://github.com/authdog/web-sdk) commit in the sample `requirements.txt`, with the `starlette` extra. The server imports `authdog.starlette.Authdog`. ## Related | Read | To learn how to | | --- | --- | | [Agent frameworks](/docs/agent-frameworks) | See the same bearer on the other four hooks | | [Eve](/docs/agent-frameworks/eve) | Move the check onto a channel auth walk | | [Machine identity](/docs/machine-identity) | Create the confidential client | ### Eve Source: https://www.authdog.com/docs/agent-frameworks/eve.md Eve route auth takes a list of authenticators on the channel. Put Authdog's issuer and audience in `oidc()` and pass that function as the only entry. Session routes then require a bearer JWT Eve can verify from the issuer's discovery document. Sample: [eve/typescript](https://github.com/authdog/agents-samples/tree/main/eve/typescript). Node.js 20+. Package `eve@0.81.1`. Channel auth, machine credentials, tool checks, and instructions for this framework are the other pages under Eve in [AI](/docs/ai/eve/channel-auth). Channel auth, machine credentials, tool checks, and instructions for this framework are the other pages under Eve in [AI](/docs/ai/eve/channel-auth). Channel auth, machine credentials, tool checks, and instructions for this framework are the other pages under Eve in [AI](/docs/ai/eve/channel-auth). This is inbound route auth: who may open a session. Outbound tool sign-in is a separate Eve concern and stays off this walk. An agent that signs into your product UI uses [Agent Sign-In](/docs/agent-sign-in). ## The walk `agent/channels/authdog-oidc.mjs` reads the environment and returns Eve's OIDC authenticator: ```javascript import { oidc } from "eve/channels/auth"; export function authdogOidc() { const issuer = process.env.AUTHDOG_ISSUER; const audience = process.env.AUTHDOG_AUDIENCE; if (!issuer || !audience) { throw new Error("AUTHDOG_ISSUER and AUTHDOG_AUDIENCE are required"); } return oidc({ issuer, audiences: [audience], }); } ``` `agent/channels/eve.ts` mounts it alone: ```typescript import { eveChannel } from "eve/channels/eve"; import { authdogOidc } from "./authdog-oidc.mjs"; export default eveChannel({ auth: [authdogOidc()], }); ``` `oidc()` verifies the bearer as a JWT against the issuer discovery document (`iss`, signature, expiry, audience). The Eve process holds no client secret. An opaque token fails this check. Mint the JWT from the same issuer, and set `AUTHDOG_AUDIENCE` to the `aud` that token carries. The agent definition (`defineAgent`) stays the model. Identity lives on the channel. ## What you should see `POST /eve/v1/session` with no `Authorization` header returns `401`. Repeat the call with `Authorization: Bearer ` when `iss` matches `AUTHDOG_ISSUER` and `aud` matches `AUTHDOG_AUDIENCE`. Eve accepts the session only after that verification. Eve's health route does not use this walk. A public health check is expected. `npm test` in the sample asserts two things: the channel source lists `authdogOidc()` and does not list `localDev()`, `none()`, or `vercelOidc()`, and a session request with no bearer is `401`. ## Configure | Variable | Purpose | | --- | --- | | `AUTHDOG_ISSUER` | Environment issuer, `https:///oidc/`. | | `AUTHDOG_AUDIENCE` | Audience claim your agent tokens carry. | | `OPENAI_API_KEY` | Only for `npx eve dev` chat. Tests do not call a model. | ```bash npm install export AUTHDOG_ISSUER=https://id.authdog.com/oidc/ export AUTHDOG_AUDIENCE=https://your-agent.example npm test ``` To talk to the agent, export the same variables and run `npx eve dev` from the sample directory. Leave `localDev()`, `none()`, and another vendor's workload identity off this list. A laptop process is not an Authdog subject. ## Related | Read | To learn how to | | --- | --- | | [Agent frameworks](/docs/agent-frameworks) | Mint the bearer the channel verifies | | [A2A](/docs/agent-frameworks/a2a) | Same identity, declared on an agent card | | [MCP](/docs/agent-frameworks/mcp) | Same identity, on an MCP token verifier | ### MCP agents Source: https://www.authdog.com/docs/agent-frameworks/mcp.md An MCP server exposes `token_verifier`. Implement that hook so a missing or rejected Authdog bearer never reaches a tool. The sample's `whoami` tool returns the subject the verifier already stored. Sample: [mcp/python](https://github.com/authdog/agents-samples/tree/main/mcp/python). Python 3.10+. Package `mcp==2.3.0`. Channel auth, machine credentials, tool checks, and instructions for this framework are the other pages under MCP in [AI](/docs/ai/mcp/channel-auth). Channel auth, machine credentials, tool checks, and instructions for this framework are the other pages under MCP in [AI](/docs/ai/mcp/channel-auth). Channel auth, machine credentials, tool checks, and instructions for this framework are the other pages under MCP in [AI](/docs/ai/mcp/channel-auth). [MCP authentication](/docs/mcp-authentication) is the hosted login an MCP client uses to obtain a token. This page is the resource-server check after the client presents one. The sample's caller is an agent `client_credentials` token, the same mint as [A2A](/docs/agent-frameworks/a2a). ## The verifier Parse `PK_AUTHDOG` with `validate_and_parse_public_key`. That payload carries `identity_host` and `environment_id`. `verify_token` calls `fetch_user_data` — the same userinfo check as `require_auth` — and returns an MCP `AccessToken` only when the payload is authenticated and has a subject. ```python class AuthdogTokenVerifier: async def verify_token(self, token: str) -> AccessToken | None: try: data = await fetch_user_data( self._payload.identity_host, self._payload.environment_id, token, ) except httpx.HTTPError: return None if not is_authenticated_user_info(data): return None subject = subject_of(data.get("user")) if not subject: return None return AccessToken( token=token, client_id=subject, scopes=["agent:invoke"], subject=subject, ) ``` `None` refuses the bearer. `subject_of` reads `id`, then `sub`. Pass the verifier on `MCPServer` and point `AuthSettings.issuer_url` at `{identity_host}/oidc/{environment_id}`: ```python server = MCPServer( name="authdog-mcp", token_verifier=AuthdogTokenVerifier(payload), auth=AuthSettings( issuer_url=issuer, resource_server_url=resource_url, required_scopes=["agent:invoke"], validate_token_resource=False, ), ) ``` `validate_token_resource=False` because this verifier checks the token by userinfo. It does not require an `aud` claim equal to the MCP resource URL. Set the audience on the Authdog client when you need that binding. The sample does not add a second JWT parser. ## The tool ```python @server.tool() def whoami() -> str: """Return the Authdog subject of the calling agent.""" token = get_access_token() if token is None or not token.subject: raise PermissionError("Unauthorized") return token.subject ``` `get_access_token()` is the MCP auth context the verifier filled in. The tool reads `subject` from that context. ## What you should see `POST /mcp` with no bearer returns `401`. The `initialize` method does not run as an authenticated session. Repeat with `Authorization: Bearer ` that userinfo accepts, then call `whoami`. The text is the Authdog subject. `pytest` in the sample checks the unauthenticated `401` and that the verifier returns subject `agent_1` for an accepted userinfo response. ## Configure | Variable | Purpose | | --- | --- | | `PK_AUTHDOG` | Public key. The server never sees the client secret. | | `MCP_RESOURCE_URL` | Resource server URL passed to `AuthSettings`. Defaults to `http://127.0.0.1:8000`. | > **Note** > > `@authdog/mcp-sdk` is a separate package, covered in [MCP SDK](/docs/mcp-sdk). This sample does not install it. It uses `fetch_user_data` from the pinned [web-sdk](https://github.com/authdog/web-sdk) Python package plus `mcp==2.3.0`. Install from the sample `requirements.txt`. ## Related | Read | To learn how to | | --- | --- | | [MCP authentication](/docs/mcp-authentication) | OAuth 2.1 metadata and the client login flow | | [MCP SDK](/docs/mcp-sdk) | `AuthdogVerifier` and the runtime guard | | [LangGraph](/docs/agent-frameworks/langgraph) | The same userinfo gate in front of a graph | ### LangGraph Source: https://www.authdog.com/docs/agent-frameworks/langgraph.md LangGraph compiles a graph. It does not authenticate the HTTP caller. Put `require_auth` on the route that invokes the graph, and pass the Authdog subject in as state. The graph reads that state. It does not read the `Authorization` header. Sample: [langgraph/python](https://github.com/authdog/agents-samples/tree/main/langgraph/python). Python 3.10+. Package `langgraph==1.2.16`. Channel auth, machine credentials, tool checks, and instructions for this framework are the other pages under LangGraph in [AI](/docs/ai/langgraph/channel-auth). Channel auth, machine credentials, tool checks, and instructions for this framework are the other pages under LangGraph in [AI](/docs/ai/langgraph/channel-auth). Channel auth, machine credentials, tool checks, and instructions for this framework are the other pages under LangGraph in [AI](/docs/ai/langgraph/channel-auth). ## The route is the hook ```python class State(TypedDict): subject: str reply: str def build_graph(): def respond(state: State) -> dict[str, str]: return {"reply": f"subject:{state['subject']}"} graph = StateGraph(State) graph.add_node("respond", respond) graph.add_edge(START, "respond") graph.add_edge("respond", END) return graph.compile() authdog = Authdog(public_key=public_key) compiled = build_graph() @app.post("/invoke") async def invoke(user=Depends(authdog.require_auth)): result = await compiled.ainvoke( {"subject": subject_of(user), "reply": ""} ) return {"reply": result["reply"]} ``` `Authdog` comes from `authdog.fastapi`. `require_auth` is a FastAPI dependency, so a missing or rejected token never enters `invoke`. `subject_of` reads `id`, then `sub`, from the userinfo user. Call `graph.invoke` or `ainvoke` only from a route that already depends on `require_auth`. A second route that invokes the compiled graph directly skips Authdog. This sample has one route. The caller mints the bearer elsewhere. Use the exchange in [Agent frameworks](/docs/agent-frameworks), or the client in the [A2A sample](/docs/agent-frameworks/a2a). This process does not take `AUTHDOG_CLIENT_SECRET`. ## What you should see `POST /invoke` with no token returns `401`. The body does not contain `subject:`. The same call with `Authorization: Bearer ` that userinfo accepts returns: ```json {"reply": "subject:"} ``` `pytest` checks both: `401` with no token, and `subject:agent_1` when userinfo returns that id. ## Configure | Variable | Purpose | | --- | --- | | `PK_AUTHDOG` | Public key for `Authdog(public_key=...)`. | ```bash python -m venv .venv source .venv/bin/activate python -m pip install -r requirements-dev.txt export PK_AUTHDOG=pk_... uvicorn server:create_app --factory --port 8000 ``` > **Caution** > > Install `authdog-fastapi` from the pinned [web-sdk](https://github.com/authdog/web-sdk) commit in the sample `requirements.txt`, with the `fastapi` extra. ## Related | Read | To learn how to | | --- | --- | | [OpenAI Agents SDK](/docs/agent-frameworks/openai-agents) | The caller that presents this token from a tool | | [MCP](/docs/agent-frameworks/mcp) | The same userinfo check behind `token_verifier` | | [Backend requests](/docs/backend) | `require_auth` on an API that is not a graph | ### OpenAI Agents SDK Source: https://www.authdog.com/docs/agent-frameworks/openai-agents.md The OpenAI Agents SDK tool is the caller. It reads an access token from run context and sends `Authorization: Bearer` to your API. `require_auth` on that API is the gate. The tool does not decide who the caller is, and it does not invent a subject. Sample: [openai-agents/python](https://github.com/authdog/agents-samples/tree/main/openai-agents/python). Python 3.10+. Package `openai-agents==0.24.0` (`import agents`). Channel auth, machine credentials, tool checks, and instructions for this framework are the other pages under OpenAI Agents SDK in [AI](/docs/ai/openai-agents/channel-auth). Channel auth, machine credentials, tool checks, and instructions for this framework are the other pages under OpenAI Agents SDK in [AI](/docs/ai/openai-agents/channel-auth). Channel auth, machine credentials, tool checks, and instructions for this framework are the other pages under OpenAI Agents SDK in [AI](/docs/ai/openai-agents/channel-auth). ## Hold the token in context ```python @dataclass(frozen=True) class Caller: token: str async def fetch_subject(token: str, client: httpx.AsyncClient, url: str) -> str: headers: dict[str, str] = {} if token: headers["Authorization"] = f"Bearer {token}" response = await client.get(url, headers=headers) response.raise_for_status() return str(response.json()["subject"]) def build_agent(client: httpx.AsyncClient, url: str) -> Agent: @function_tool async def whoami(ctx: RunContextWrapper[Caller]) -> str: """Return the Authdog subject for this caller. Do not invent one.""" return await fetch_subject(ctx.context.token, client, url) return Agent( name="authdog-caller", instructions="Call whoami. Never invent a subject.", tools=[whoami], ) ``` `Caller.token` is the access token from [the client-credentials exchange](/docs/agent-frameworks). The client secret stays in the process that minted it. The model sees the tool result, which is the subject string the API returned. Run it by passing `context=Caller(token=...)` to `Runner.run`. `OPENAI_API_KEY` is required for that run. Proving the tool attaches the header does not require a model: call `fetch_subject` directly. ## The route the tool calls ```python authdog = Authdog(public_key=public_key) @app.get("/whoami") async def whoami(user=Depends(authdog.require_auth)): return {"subject": subject_of(user)} ``` `Authdog` comes from `authdog.fastapi`. A missing or rejected token is `401` from this route, including when the tool sends an empty header because `Caller.token` is empty. To act for a person, keep this route and this tool. Put an [RFC 8693 token](/docs/agent-frameworks#acting-for-a-person) in `Caller.token` so `sub` stays the user and `act` names the agent. The samples mint a `client_credentials` token whose subject is the agent. ## What you should see 1. `GET /whoami` with no token returns `401`. 2. The same call with a bearer userinfo accepts returns `{"subject": ""}`. 3. `Runner.run(..., context=Caller(token=os.environ["AUTHDOG_ACCESS_TOKEN"]))` with instructions to call `whoami` prints that subject as the model output. `pytest` covers the route and `fetch_subject`. It does not call `Runner`. ## Configure | Variable | Process | Purpose | | --- | --- | --- | | `PK_AUTHDOG` | API | Public key for `require_auth`. | | `WHOAMI_URL` | Agent | Absolute URL of `GET /whoami`. | | `AUTHDOG_ACCESS_TOKEN` | Agent | Bearer to place in `Caller`. | | `AUTHDOG_CLIENT_ID` / `AUTHDOG_CLIENT_SECRET` | Minting process | Only if you exchange a token yourself. | | `OPENAI_API_KEY` | Agent | Only for `Runner.run`. | ```bash uvicorn tool_api:create_app --factory --port 8000 ``` > **Caution** > > Install `authdog-fastapi` from the pinned [web-sdk](https://github.com/authdog/web-sdk) commit in the sample `requirements.txt`, with the `fastapi` extra. Do not put the client secret in `Caller` or in the agent instructions. ## Related | Read | To learn how to | | --- | --- | | [LangGraph](/docs/agent-frameworks/langgraph) | Protect the workflow the tool is calling | | [Agent delegation](/guides/agent-delegation) | Exchange a user token when the agent acts for a person | | [Agents quickstart](/docs/quickstarts/agents) | Register the client that mints `AUTHDOG_ACCESS_TOKEN` | ### CrewAI Source: https://www.authdog.com/docs/agent-frameworks/crewai.md CrewAI keeps its own auth hook. Authdog stays the identity provider. The sample attaches an agent access token there and refuses the run when that token is missing or userinfo rejects it. Sample: [crewai/python](https://github.com/authdog/agents-samples/tree/main/crewai/python). Python 3.10+. Package `crewai==1.15.28`. Channel auth, machine credentials, tool checks, and instructions for this framework are the other pages under CrewAI in [AI](/docs/ai/crewai/channel-auth). ## The hook ```python from crewai.hooks import HookAborted, InterceptionPoint, on @on(InterceptionPoint.EXECUTION_START) def require_authdog(ctx) -> None: token = ctx.payload.pop("authdog_token", None) subject = subject_if_accepted(token) if subject is None: raise HookAborted(reason="unauthorized", source="authdog") ctx.payload["subject"] = subject ``` Register the hook before `kickoff`. `ctx.payload` is the crew inputs. `authdog_token` is removed before tasks render, so the model does not see the bearer. `subject` is the id userinfo returned. `HookAborted` stops the run. The tool does not run. This is agent identity: `client_credentials`, then this hook. It is not [Agent Sign-In](/docs/agent-sign-in). A browser sign-in does not replace the hook. ## What you should see Kickoff with no token raises `HookAborted`. No tool result contains a subject. Kickoff with a bearer userinfo accepts stores that subject and continues. `whoami` returns it. `pytest` in `crewai/python` checks both. It does not call a model. ## Configure | Name | Purpose | | --- | --- | | `authdog_token` on the kickoff inputs | Access token. The hook removes it before tasks render. The client secret is not an input. | The shared exchange is in [Agent frameworks](/docs/agent-frameworks#mint-the-bearer). There is no refresh token. Repeat it before `expires_in`. > **Caution** > > Do not commit the client secret, and do not put the access token in the prompt. The public key selects the identity host. It does not mint tokens. ## Related | Read | To learn how to | | --- | --- | | [Agent frameworks](/docs/agent-frameworks) | Mint the bearer this hook checks | | [Google ADK](/docs/agent-frameworks/adk) | The next hook | | [Custom channel auth for CrewAI](/docs/ai/crewai/channel-auth) | The same check, as the auth boundary | ### Google ADK Source: https://www.authdog.com/docs/agent-frameworks/adk.md Google ADK keeps its own auth hook. Authdog stays the identity provider. The sample attaches an agent access token there and refuses the run when that token is missing or userinfo rejects it. Sample: [adk/python](https://github.com/authdog/agents-samples/tree/main/adk/python). Python 3.10+. Package `google-adk==2.12.0`. Channel auth, machine credentials, tool checks, and instructions for this framework are the other pages under Google ADK in [AI](/docs/ai/adk/channel-auth). ## The hook ```python def before_agent_callback(callback_context): token = callback_context.state.get("authdog_token") subject = subject_if_accepted(token) callback_context.state["authdog_token"] = None if subject is None: return types.Content( role="model", parts=[types.Part(text="unauthorized")], ) callback_context.state["subject"] = subject return None ``` The parameter name is `callback_context`. ADK passes callback arguments by keyword. Returning `types.Content` skips the agent body. Returning `None` lets it run. The token lives in callback state, which the host set. It is not the user message. Clear it before the model runs. `AuthCredential` on a tool is outbound tool auth. It is not this check, and it is not a place for the agent client secret. This is agent identity: `client_credentials`, then this hook. It is not [Agent Sign-In](/docs/agent-sign-in). A browser sign-in does not replace the hook. ## What you should see A callback with no token, or a token userinfo rejects, returns content whose text is `unauthorized`. The agent body does not run. A callback with a bearer userinfo accepts stores the subject and returns `None`. `pytest` in `adk/python` checks both. It does not call a model. ## Configure | Name | Purpose | | --- | --- | | Callback state `authdog_token` | Access token, set by the host before the run. Cleared before the model sees state. | The shared exchange is in [Agent frameworks](/docs/agent-frameworks#mint-the-bearer). There is no refresh token. Repeat it before `expires_in`. > **Caution** > > Do not commit the client secret, and do not put the access token in the prompt. The public key selects the identity host. It does not mint tokens. ## Related | Read | To learn how to | | --- | --- | | [Agent frameworks](/docs/agent-frameworks) | Mint the bearer this hook checks | | [CrewAI](/docs/agent-frameworks/crewai) | The previous hook | | [Pydantic AI](/docs/agent-frameworks/pydantic-ai) | The next hook | | [Custom channel auth for Google ADK](/docs/ai/adk/channel-auth) | The same check, as the auth boundary | ### Pydantic AI Source: https://www.authdog.com/docs/agent-frameworks/pydantic-ai.md Pydantic AI keeps its own auth hook. Authdog stays the identity provider. The sample attaches an agent access token there and refuses the run when that token is missing or userinfo rejects it. Sample: [pydantic-ai/python](https://github.com/authdog/agents-samples/tree/main/pydantic-ai/python). Python 3.10+. Package `pydantic-ai==2.59.0`. Channel auth, machine credentials, tool checks, and instructions for this framework are the other pages under Pydantic AI in [AI](/docs/ai/pydantic-ai/channel-auth). ## The hook ```python @dataclass(frozen=True) class Caller: token: str agent = Agent("test", deps_type=Caller) @agent.tool async def whoami(ctx: RunContext[Caller]) -> str: """Return the Authdog subject for this caller. Do not invent one.""" return await fetch_subject(ctx.deps.token) ``` `Caller.token` is the access token. The client secret is not a field on `Caller`. `fetch_subject` sends `Authorization: Bearer` to an API whose route depends on `require_auth`. A missing or rejected token does not return a subject. Tests call that helper and the route. They do not call the model. This is agent identity: `client_credentials`, then this hook. It is not [Agent Sign-In](/docs/agent-sign-in). A browser sign-in does not replace the hook. ## What you should see `GET /whoami` with no token is `401`. The helper, given an empty token, does not return a subject. The same call with a bearer userinfo accepts returns `{"subject":""}`. The tool result is that string. `pytest` in `pydantic-ai/python` checks both. It does not call a model. ## Configure | Name | Purpose | | --- | --- | | `PK_AUTHDOG` | Public key on the API process. | | `Caller.token` | Access token on the run. Not the client secret. | The shared exchange is in [Agent frameworks](/docs/agent-frameworks#mint-the-bearer). There is no refresh token. Repeat it before `expires_in`. > **Caution** > > Do not commit the client secret, and do not put the access token in the prompt. The public key selects the identity host. It does not mint tokens. ## Related | Read | To learn how to | | --- | --- | | [Agent frameworks](/docs/agent-frameworks) | Mint the bearer this hook checks | | [Google ADK](/docs/agent-frameworks/adk) | The previous hook | | [Claude Agent SDK](/docs/agent-frameworks/claude-agent) | The next hook | | [Custom channel auth for Pydantic AI](/docs/ai/pydantic-ai/channel-auth) | The same check, as the auth boundary | ### Claude Agent SDK Source: https://www.authdog.com/docs/agent-frameworks/claude-agent.md Claude Agent SDK keeps its own auth hook. Authdog stays the identity provider. The sample attaches an agent access token there and refuses the run when that token is missing or userinfo rejects it. Sample: [claude-agent/python](https://github.com/authdog/agents-samples/tree/main/claude-agent/python). Python 3.10+. Package `claude-agent-sdk==0.2.172`. Channel auth, machine credentials, tool checks, and instructions for this framework are the other pages under Claude Agent SDK in [AI](/docs/ai/claude-agent/channel-auth). ## The hook ```python async def require_authdog(input_data, tool_use_id, context): token = token_for_this_run() if subject_if_accepted(token) is None: return { "hookSpecificOutput": { "hookEventName": input_data["hook_event_name"], "permissionDecision": "deny", "permissionDecisionReason": "unauthorized", } } return {} options = ClaudeAgentOptions( hooks={"PreToolUse": [HookMatcher(hooks=[require_authdog])]} ) ``` There is no matcher, so the hook runs for every tool. `permissionDecision: deny` stops that call. An empty object lets the normal permission flow continue. The token comes from the host (`token_for_this_run`), not from `input_data["tool_input"]`. The model does not get to supply it. Tests call the hook. They do not need `ANTHROPIC_API_KEY`. This is agent identity: `client_credentials`, then this hook. It is not [Agent Sign-In](/docs/agent-sign-in). A browser sign-in does not replace the hook. ## What you should see The hook returns `permissionDecision` `deny` when the token is missing or userinfo rejects it. The tool does not run. The hook returns `{}` when userinfo accepts the bearer. A `whoami` tool may then return that subject. `pytest` in `claude-agent/python` checks both. It does not call a model. ## Configure | Name | Purpose | | --- | --- | | Host-held access token | What the hook sends to userinfo. Not a tool argument. | The shared exchange is in [Agent frameworks](/docs/agent-frameworks#mint-the-bearer). There is no refresh token. Repeat it before `expires_in`. > **Caution** > > Do not commit the client secret, and do not put the access token in the prompt. The public key selects the identity host. It does not mint tokens. ## Related | Read | To learn how to | | --- | --- | | [Agent frameworks](/docs/agent-frameworks) | Mint the bearer this hook checks | | [Pydantic AI](/docs/agent-frameworks/pydantic-ai) | The previous hook | | [Mastra](/docs/agent-frameworks/mastra) | The next hook | | [Custom channel auth for Claude Agent SDK](/docs/ai/claude-agent/channel-auth) | The same check, as the auth boundary | ### Mastra Source: https://www.authdog.com/docs/agent-frameworks/mastra.md Mastra keeps its own auth hook. Authdog stays the identity provider. The sample attaches an agent access token there and refuses the run when that token is missing or userinfo rejects it. Sample: [mastra/typescript](https://github.com/authdog/agents-samples/tree/main/mastra/typescript). Node.js. Package `@mastra/core@1.79.0`. Channel auth, machine credentials, tool checks, and instructions for this framework are the other pages under Mastra in [AI](/docs/ai/mastra/channel-auth). ## The hook ```typescript export const mastra = new Mastra({ server: { auth: { authenticateToken: async (token) => { const subject = await subjectIfAccepted(token) if (!subject) return null return { id: subject } }, }, }, }) ``` Mastra routes stay public when `server.auth` is unset. With `authenticateToken` set, a route that does not opt out requires a user. Returning no user is unauthorized, and the response is `401`. `whoami` returns `id`. A public route would have to set `requiresAuth: false`. This sample does not. Use the result the installed `@mastra/core` already treats as "no user" (`null`, or a rejected promise if that version requires it). This is agent identity: `client_credentials`, then this hook. It is not [Agent Sign-In](/docs/agent-sign-in). A browser sign-in does not replace the hook. ## What you should see A request with no bearer is `401`. A bearer userinfo rejects is `401`. The body has no subject. A bearer userinfo accepts reaches `whoami`, which returns that subject. `npm test` in `mastra/typescript` checks both. It does not call a model. ## Configure | Name | Purpose | | --- | --- | | `PK_AUTHDOG` | Public key on the Mastra server. Selects the identity host. | | `Authorization` | Access token the caller presents. | The shared exchange is in [Agent frameworks](/docs/agent-frameworks#mint-the-bearer). There is no refresh token. Repeat it before `expires_in`. > **Caution** > > Do not commit the client secret, and do not put the access token in the prompt. The public key selects the identity host. It does not mint tokens. ## Related | Read | To learn how to | | --- | --- | | [Agent frameworks](/docs/agent-frameworks) | Mint the bearer this hook checks | | [Claude Agent SDK](/docs/agent-frameworks/claude-agent) | The previous hook | | [LlamaIndex Workflows](/docs/agent-frameworks/llamaindex) | The next hook | | [Custom channel auth for Mastra](/docs/ai/mastra/channel-auth) | The same check, as the auth boundary | ### LlamaIndex Workflows Source: https://www.authdog.com/docs/agent-frameworks/llamaindex.md LlamaIndex Workflows keeps its own auth hook. Authdog stays the identity provider. The sample attaches an agent access token there and refuses the run when that token is missing or userinfo rejects it. Sample: [llamaindex/python](https://github.com/authdog/agents-samples/tree/main/llamaindex/python). Python 3.10+. Package `llama-index-workflows==2.26.1`. Channel auth, machine credentials, tool checks, and instructions for this framework are the other pages under LlamaIndex Workflows in [AI](/docs/ai/llamaindex/channel-auth). ## The hook ```python class WhoamiFlow(Workflow): @step async def start(self, ev: StartEvent) -> StopEvent: subject = getattr(ev, "subject", None) if not subject: return StopEvent(result="unauthorized") return StopEvent(result=f"subject:{subject}") @app.post("/invoke") async def invoke(user=Depends(authdog.require_auth)): result = await WhoamiFlow().run(subject=subject_of(user)) return {"reply": result} ``` The route copies the subject from userinfo, then starts the workflow. The bearer stays on the request. A run with no subject returns `unauthorized` and does not continue to a later step. Call `run` only from this route. This is agent identity: `client_credentials`, then this hook. It is not [Agent Sign-In](/docs/agent-sign-in). A browser sign-in does not replace the hook. ## What you should see `POST /invoke` with no token is `401`. `workflow.run()` with no subject returns `unauthorized` and does not contain `subject:`. The same POST with a bearer userinfo accepts returns `{"reply":"subject:"}`. `pytest` in `llamaindex/python` checks both. It does not call a model. ## Configure | Name | Purpose | | --- | --- | | `PK_AUTHDOG` | Public key on the route. The workflow process does not take the client secret. | The shared exchange is in [Agent frameworks](/docs/agent-frameworks#mint-the-bearer). There is no refresh token. Repeat it before `expires_in`. > **Caution** > > Do not commit the client secret, and do not put the access token in the prompt. The public key selects the identity host. It does not mint tokens. ## Related | Read | To learn how to | | --- | --- | | [Agent frameworks](/docs/agent-frameworks) | Mint the bearer this hook checks | | [Mastra](/docs/agent-frameworks/mastra) | The previous hook | | [Semantic Kernel](/docs/agent-frameworks/semantic-kernel) | The next hook | | [Custom channel auth for LlamaIndex Workflows](/docs/ai/llamaindex/channel-auth) | The same check, as the auth boundary | ### Semantic Kernel Source: https://www.authdog.com/docs/agent-frameworks/semantic-kernel.md Semantic Kernel keeps its own auth hook. Authdog stays the identity provider. The sample attaches an agent access token there and refuses the run when that token is missing or userinfo rejects it. Sample: [semantic-kernel/python](https://github.com/authdog/agents-samples/tree/main/semantic-kernel/python). Python 3.10+. Package `semantic-kernel==1.45.0`. Channel auth, machine credentials, tool checks, and instructions for this framework are the other pages under Semantic Kernel in [AI](/docs/ai/semantic-kernel/channel-auth). ## The hook ```python async def require_authdog(context: FunctionInvocationContext, next): token = context.arguments.get("authdog_token") subject = subject_if_accepted(token) if subject is None: return context.arguments["subject"] = subject del context.arguments["authdog_token"] await next(context) kernel.add_filter(FilterTypes.FUNCTION_INVOCATION, require_authdog) ``` Not calling `next` means the kernel function does not run. The host puts `authdog_token` on the arguments. The filter deletes it before `next`, so the function sees `subject` and not the bearer. Tests invoke the function through the kernel. They do not call a chat model. This is agent identity: `client_credentials`, then this hook. It is not [Agent Sign-In](/docs/agent-sign-in). A browser sign-in does not replace the hook. ## What you should see Invoking `whoami` with no token does not enter the function. The result is not a subject. Invoking `whoami` with a bearer userinfo accepts runs the function. The result is that subject. `pytest` in `semantic-kernel/python` checks both. It does not call a model. ## Configure | Name | Purpose | | --- | --- | | `authdog_token` argument | Access token the host sets. The filter removes it before the function runs. | The shared exchange is in [Agent frameworks](/docs/agent-frameworks#mint-the-bearer). There is no refresh token. Repeat it before `expires_in`. > **Caution** > > Do not commit the client secret, and do not put the access token in the prompt. The public key selects the identity host. It does not mint tokens. ## Related | Read | To learn how to | | --- | --- | | [Agent frameworks](/docs/agent-frameworks) | Mint the bearer this hook checks | | [LlamaIndex Workflows](/docs/agent-frameworks/llamaindex) | The previous hook | | [Vercel AI SDK](/docs/agent-frameworks/ai-sdk) | The next hook | | [Custom channel auth for Semantic Kernel](/docs/ai/semantic-kernel/channel-auth) | The same check, as the auth boundary | ### Vercel AI SDK Source: https://www.authdog.com/docs/agent-frameworks/ai-sdk.md Vercel AI SDK keeps its own auth hook. Authdog stays the identity provider. The sample attaches an agent access token there and refuses the run when that token is missing or userinfo rejects it. Sample: [ai-sdk/typescript](https://github.com/authdog/agents-samples/tree/main/ai-sdk/typescript). Node.js. Package `ai@7.0.148`. Channel auth, machine credentials, tool checks, and instructions for this framework are the other pages under Vercel AI SDK in [AI](/docs/ai/ai-sdk/channel-auth). ## The hook ```typescript const whoami = tool({ description: "Return the Authdog subject. Do not invent one.", inputSchema: z.object({}), contextSchema: z.object({ token: z.string() }), execute: async (_input, { context }) => fetchSubject(context.token), }) ``` The caller passes the access token in `toolsContext.whoami.token`. `fetchSubject` sends it as `Authorization: Bearer` to a route that depends on `require_auth`. The model sees the tool result, which is the subject string. Tests call `execute` and the route. They do not call `generate`. This is agent identity: `client_credentials`, then this hook. It is not [Agent Sign-In](/docs/agent-sign-in). A browser sign-in does not replace the hook. ## What you should see The route with no token is `401`. `execute` with an empty token does not return a subject. The route with a bearer userinfo accepts returns `{ subject: "" }`. That string is the tool result. `npm test` in `ai-sdk/typescript` checks both. It does not call a model. ## Configure | Name | Purpose | | --- | --- | | `toolsContext.whoami.token` | Access token for this tool only. | | `PK_AUTHDOG` | Public key on the API route. | The shared exchange is in [Agent frameworks](/docs/agent-frameworks#mint-the-bearer). There is no refresh token. Repeat it before `expires_in`. > **Caution** > > Do not commit the client secret, and do not put the access token in the prompt. The public key selects the identity host. It does not mint tokens. ## Related | Read | To learn how to | | --- | --- | | [Agent frameworks](/docs/agent-frameworks) | Mint the bearer this hook checks | | [Semantic Kernel](/docs/agent-frameworks/semantic-kernel) | The previous hook | | [Strands Agents](/docs/agent-frameworks/strands) | The next hook | | [Custom channel auth for Vercel AI SDK](/docs/ai/ai-sdk/channel-auth) | The same check, as the auth boundary | ### Strands Agents Source: https://www.authdog.com/docs/agent-frameworks/strands.md Strands Agents keeps its own auth hook. Authdog stays the identity provider. The sample attaches an agent access token there and refuses the run when that token is missing or userinfo rejects it. Sample: [strands/python](https://github.com/authdog/agents-samples/tree/main/strands/python). Python 3.10+. Package `strands-agents==1.62.0`. Channel auth, machine credentials, tool checks, and instructions for this framework are the other pages under Strands Agents in [AI](/docs/ai/strands/channel-auth). ## The hook ```python class AuthdogHook(HookProvider): def register_hooks(self, registry: HookRegistry) -> None: registry.add_callback(BeforeToolCallEvent, self.require) def require(self, event: BeforeToolCallEvent) -> None: token = event.invocation_state.get("authdog_token") subject = subject_if_accepted(token) if subject is None: event.cancel_tool = "unauthorized" return event.invocation_state["subject"] = subject event.invocation_state.pop("authdog_token", None) ``` Pass the hook to `Agent(hooks=[AuthdogHook()])`. The host sets `authdog_token` on invocation state when it calls the agent. The model does not. Setting `cancel_tool` blocks the tool and returns that message as an error tool result. On accept, the hook stores the subject and removes the token before the tool runs. Tests call `require`. They do not call a model. This is agent identity: `client_credentials`, then this hook. It is not [Agent Sign-In](/docs/agent-sign-in). A browser sign-in does not replace the hook. ## What you should see A before-tool event with no token sets `cancel_tool` to `unauthorized`. The tool does not run. A before-tool event with a bearer userinfo accepts leaves `cancel_tool` unset and stores the subject. `whoami` returns it. `pytest` in `strands/python` checks both. It does not call a model. ## Configure | Name | Purpose | | --- | --- | | Invocation state `authdog_token` | Access token the host passes. Removed before the tool runs. | The shared exchange is in [Agent frameworks](/docs/agent-frameworks#mint-the-bearer). There is no refresh token. Repeat it before `expires_in`. > **Caution** > > Do not commit the client secret, and do not put the access token in the prompt. The public key selects the identity host. It does not mint tokens. ## Related | Read | To learn how to | | --- | --- | | [Agent frameworks](/docs/agent-frameworks) | Mint the bearer this hook checks | | [Vercel AI SDK](/docs/agent-frameworks/ai-sdk) | The previous hook | | [LangChain](/docs/agent-frameworks/langchain) | The next hook | | [Custom channel auth for Strands Agents](/docs/ai/strands/channel-auth) | The same check, as the auth boundary | ### LangChain Source: https://www.authdog.com/docs/agent-frameworks/langchain.md LangChain keeps its own auth hook. Authdog stays the identity provider. The sample attaches an agent access token there and refuses the run when that token is missing or userinfo rejects it. Sample: [langchain/python](https://github.com/authdog/agents-samples/tree/main/langchain/python). Python 3.10+. Package `langchain==1.4.7`. Channel auth, machine credentials, tool checks, and instructions for this framework are the other pages under LangChain in [AI](/docs/ai/langchain/channel-auth). ## The hook ```python @wrap_tool_call def require_authdog(request, handler): token = request.runtime.context.get("authdog_token") subject = subject_if_accepted(token) if subject is None: return ToolMessage( content="unauthorized", tool_call_id=request.tool_call["id"], status="error", ) return handler(request) ``` Pass the middleware to `create_agent(..., middleware=[require_authdog])`. The token is on runtime context, which the host set. It is not `request.tool_call["args"]`. Skipping `handler` means the tool does not run. Tests call the middleware with a stand-in request. They do not call a model. Read the token from the runtime field the pinned `langchain` actually exposes. The current docs name that `request.runtime.context`. This is agent identity: `client_credentials`, then this hook. It is not [Agent Sign-In](/docs/agent-sign-in). A browser sign-in does not replace the hook. ## What you should see A tool call with no token returns a tool message `unauthorized` and does not call the handler. A tool call with a bearer userinfo accepts calls the handler. `whoami` returns that subject. `pytest` in `langchain/python` checks both. It does not call a model. ## Configure | Name | Purpose | | --- | --- | | Runtime context `authdog_token` | Access token the host sets. Not a tool argument. | The shared exchange is in [Agent frameworks](/docs/agent-frameworks#mint-the-bearer). There is no refresh token. Repeat it before `expires_in`. > **Caution** > > Do not commit the client secret, and do not put the access token in the prompt. The public key selects the identity host. It does not mint tokens. ## Related | Read | To learn how to | | --- | --- | | [Agent frameworks](/docs/agent-frameworks) | Mint the bearer this hook checks | | [Strands Agents](/docs/agent-frameworks/strands) | The previous hook | | [Custom channel auth for LangChain](/docs/ai/langchain/channel-auth) | The same check, as the auth boundary | ### MCP authentication Source: https://www.authdog.com/docs/mcp-authentication.md MCP clients discover your authorization server through OAuth 2.1 metadata and RFC 9728 protected-resource documents. Authdog hosts the issuer per environment so you can protect MCP tool servers without building login UI. ## Flow 1. The MCP client calls your resource without a token. 2. The resource responds with **401** and `WWW-Authenticate` pointing at protected-resource metadata. 3. The client loads metadata, starts authorize with **PKCE (S256)**, and optionally sends a **resource** indicator (RFC 8707). 4. After consent, the client exchanges the code at the token endpoint and calls tools with the access token. ```text Client --(no token)--> MCP resource MCP resource --401 + resource_metadata--> Client Client --authorize + PKCE--> Authdog (hosted consent) Client --token--> Authdog Client --Bearer token--> MCP resource ``` ## Environment settings In the console, open **MCP Auth → Settings** for your MCP project environment: - **Enable MCP Auth** turns on default-resource and strict-resource behavior for that issuer. - **Default resource** is applied when clients omit the `resource` parameter. - **Strict resource mode** rejects authorize and token requests that omit `resource` when no default is configured. Dynamic client registration and client-ID metadata (CIMD) toggles are stored per environment. When **MCP Auth** is enabled, the issuer registration endpoint honors **DCR** and **CIMD** toggles; CIMD clients use an `https` metadata URL as `client_id`. ## Resource server integration Use `@authdog/mcp-sdk` from the Authdog monorepo (`packages/mcp-sdk`). The `AuthdogMcpAuthProvider` class wraps token validation for Workers or Node MCP servers: ```typescript import { AuthdogMcpAuthProvider } from "@authdog/mcp-sdk" const auth = new AuthdogMcpAuthProvider({ env: { AUTHDOG_OIDC_ISSUER: process.env.AUTHDOG_OIDC_ISSUER!, MCP_PROTECTED_RESOURCE: process.env.MCP_PROTECTED_RESOURCE!, MCP_REQUIRED_SCOPES: "mcp:invoke", }, }) const result = await auth.validateAccessToken(request.headers.get("authorization")) ``` Need a verifier in another language? See [MCP SDK](/docs/mcp-sdk) for Node.js, Python, Go, Rust, C#, Java, Kotlin, Swift, and Dart. See [MCP security](/guides/mcp-security) and the [console MCP module](/docs/console/mcp). ## Enterprise SSO and MCP OAuth MCP OAuth uses the **same environment issuer** as hosted sign-in and enterprise connections. There is no separate MCP-only SAML or OIDC stack: when **Enterprise SSO** is enabled for an environment, users signing in to MCP clients through Authdog follow the same IdP routing, MFA, and session policies as the account portal. SCIM or HRIS **deprovisioning** (or deactivating a user in the console) revokes Connected Apps consents and invalidates outstanding OIDC access and refresh tokens for that user in the environment, so MCP clients cannot refresh after directory removal. ## Audit and SIEM Enable **`MCP_TELEMETRY=true`** on your MCP server (or hosted gateway) with an environment API secret (`adenv_`). The SDK sends runtime events to `POST https://api.authdog.com/v1/mcp/events`. They appear in **Audit** under category `mcp_runtime` and in **Agents → Activity** for MCP projects. When AuthZEN or the trust store denies a tool, the runtime records **`MCP_TOOL_DENIED`** with `decision: deny` and a reason code (for example `tool_not_allowed`). Subscribe to `MCP_TOOL_DENIED` and related types in **Notifications → Channels** after you confirm them in **Audit → Event Types**. Authdog indexes events for console search; **long-term retention is your SIEM or webhook destination**. See [Audit logs](/guides/audit-logs) and [SIEM security events](/guides/siem-security-events). Payload fields use snake_case inside `event_payload` (`tool_name`, `decision`, `reason`, `server_id`, …); raw tool arguments are never stored. For how OAuth-only MCP compares to trust store, AuthZEN, and audit, see [MCP OAuth vs platform stack](/docs/mcp-oauth-vs-platform). ## Related - [MCP SDK](/docs/mcp-sdk) - [Machine identity](/docs/machine-identity) - [Agent sign-in](/docs/agent-sign-in) - [Authentication](/docs/authentication) ### MCP SDK Source: https://www.authdog.com/docs/mcp-sdk.md Authdog ships two MCP server SDKs. They solve different problems and are not interchangeable, so pick by what your MCP server needs to enforce. | | Token verifier SDKs | MCP runtime guard | |---|---|---| | Source | [authdog/mcp-sdk](https://github.com/authdog/mcp-sdk) | `packages/mcp-sdk` in the Authdog monorepo | | Languages | Node.js, Python, Go, Rust, C#, Java, Kotlin, Swift, Dart | TypeScript (Workers or Node) | | Checks | Is this bearer token valid, who is the caller, and which scopes it carries (Node.js) | Token against JWKS, trust store, per-tool AuthZEN decision, audit telemetry | | Calls | Your environment's JWKS (Node.js, local verification) or `GET /v1/userinfo` | JWKS, trust store, AuthZEN, and `POST /v1/mcp/events` | | Availability | Node.js: `npm install @authdog/mcp-sdk`. Other languages: source-available, see each guide | Private, not on npm; install from source | ## Token verifier SDKs An MCP client obtains an Authdog access token, for example through [MCP authentication](/docs/mcp-authentication), and sends it as a bearer token. `AuthdogVerifier` calls the environment's `GET /v1/userinfo` with that token and returns an `AuthInfo`. The Node.js verifier can instead verify a JWT locally against your environment's JWKS when you set `issuer`, which also fills in scopes and expiry from the token. The list below describes the userinfo path that every language shares: - `subject`, `username`, and `email` for the authenticated user - `expiresAt`, derived from the remaining session lifetime - `scopes`, currently always empty because userinfo does not return scopes yet - `token` and the full `user` object A `401` from Authdog raises an authentication error your transport should turn into an HTTP 401. Any other failure raises a separate API error, so you can tell a rejected token from an unreachable Authdog. Pick your language: | Language | Guide | Package | |----------|-------|---------| | Node.js / TypeScript | [Node.js](/docs/mcp-sdk/node) | `@authdog/mcp-sdk` | | Python | [Python](/docs/mcp-sdk/python) | `authdog-mcp` | | Go | [Go](/docs/mcp-sdk/go) | `github.com/authdog/mcp-sdk/go` | | Rust | [Rust](/docs/mcp-sdk/rust) | `authdog-mcp` | | C# | [C#](/docs/mcp-sdk/csharp) | `Authdog.Mcp.Sdk` | | Java | [Java](/docs/mcp-sdk/java) | `com.authdog:authdog-mcp-sdk` | | Kotlin | [Kotlin](/docs/mcp-sdk/kotlin) | `com.authdog:authdog-mcp-sdk` | | Swift | [Swift](/docs/mcp-sdk/swift) | `AuthdogMCP` | | Dart | [Dart](/docs/mcp-sdk/dart) | `authdog_mcp` | > **Note** > > The Node.js package is on npm as `@authdog/mcp-sdk`. The other languages are source-available in [authdog/mcp-sdk](https://github.com/authdog/mcp-sdk), and each guide says how to install it. The runtime guard below shares the npm name but is a different, private package: `npm install @authdog/mcp-sdk` gives you the verifier. ## MCP runtime guard The TypeScript runtime SDK runs inside your MCP worker and layers four independent controls over each tool call. Adopt them one at a time: 1. **Authenticate**: verify the caller's access token against your environment's JWKS, with RFC 9728 protected-resource metadata. 2. **Trust store**: require the calling identity to be a verified non-human identity within its allowed scopes. 3. **Authorize**: ask the AuthZEN decision point whether this identity may invoke this specific tool. 4. **Telemetry**: emit invoked, denied, and failed events for [audit and SIEM](/docs/mcp-authentication). Trust store and authorization default to non-breaking modes (`off` and `monitor`). Move each to `enforce` after you have watched the decisions in the console. The guard is private and is not on npm, so install it from a clone of the monorepo. Use the verifier above if you only need to authenticate callers. ```ts import { authenticateRequest, checkTrustStore, createAuthdogTelemetry, withAuthdogToolGuard, } from "@authdog/mcp-sdk" const auth = await authenticateRequest(request, env) if (!auth.ok) return new Response(auth.message, { status: auth.status }) const trust = await checkTrustStore(auth.claims, env) if (!trust.ok) return new Response(trust.message, { status: trust.status }) const telemetry = createAuthdogTelemetry(env) const readFile = withAuthdogToolGuard("my-server", "read_file", env, handler, { telemetry, }) ``` Configure it with these variables on your worker: | Variable | Purpose | |----------|---------| | `AUTHDOG_OIDC_ISSUER` | Environment OIDC issuer; JWKS is derived from it | | `AUTHDOG_OIDC_AUDIENCE` | Audience your tokens are minted for (your MCP server URL) | | `MCP_REQUIRED_SCOPES` | Space-separated scopes required on every call | | `AUTHDOG_API_BASE_URL` | Public API base, used by trust store, authorize, and telemetry | | `AUTHDOG_ENV_API_SECRET` | Environment API secret (`adenv_...`) | | `MCP_TRUST_STORE_MODE` | `off` (default), `monitor`, or `enforce` | | `MCP_AUTHZ_MODE` | `off`, `monitor` (default), or `enforce` | | `MCP_TELEMETRY` | `true` to emit tool-call events | `AUTHDOG_DEV_MODE=true` allows `http://localhost` issuers for local development. Never set it in production. To detect a server changing its tools after registration, report a tool manifest hash with `computeToolManifestHash` and pin it on the trust-store entry. ## Related - [MCP authentication](/docs/mcp-authentication) - [MCP OAuth vs platform](/docs/mcp-oauth-vs-platform) - [Console MCP module](/docs/console/mcp) - [Machine identity](/docs/machine-identity) ### MCP OAuth vs platform Source: https://www.authdog.com/docs/mcp-oauth-vs-platform.md Many vendors stop at **OAuth for MCP**: metadata, consent, and a bearer token. That proves a client got scopes; it does not prove a specific **tool** should run, that the caller is a **verified agent**, or that you can **audit and revoke** machine access like human sessions. Authdog’s MCP Auth platform keeps OAuth 2.1 and RFC 9728 as the front door, then adds the controls operators expect in production. ## Comparison | Capability | OAuth-only MCP connect | Authdog MCP Auth platform | |------------|------------------------|---------------------------| | Protected-resource metadata (RFC 9728) | Yes | Yes — per environment issuer | | PKCE authorize + token | Yes | Yes — hosted consent, DCR/CIMD options | | Scope strings on token | Yes | Yes — catalog + admin-only consent flags | | **Per-tool** allow/deny after token | No — app must build | Yes — AuthZEN `MCP_TOOL` policies | | Verified agent / trust store | No | Yes — [Agents](/docs/console/agents) + enforce mode | | Runtime audit (`MCP_TOOL_DENIED`, …) | Rarely | Yes — `@authdog/mcp-sdk` telemetry → Audit/SIEM | | User revoke from Connected Apps | Varies | Yes — consent + token revocation | | Enterprise SSO on same issuer | Varies | Yes — same environment IdP as hosted auth | | SCIM deprovision → revoke grants | Varies | Yes — refresh blocked after deactivation | | Hosted edge gateway | Varies | Optional — `{label}.mcp.authdog.com` (024) | | Management MCP for operators | N/A | `https://api.authdog.com/mcp` (control plane) | ## When OAuth alone is enough Use a minimal OAuth layer if you only need “signed-in user can hit any tool the server exposes” and you trust your server code to enforce everything else. That fits early prototypes and internal tools with no agent fleet. ## When you want the full stack Choose Authdog MCP Auth when: - Non-human **agents** call tools and need verification, pinning, and rug-pull hooks. - Security wants **deny with evidence** (`MCP_TOOL_DENIED` in audit/SIEM), not silent 403s in logs. - Compliance wants **revocation** tied to directory lifecycle (SCIM, deactivate user). - Product teams want **scope + policy** separation: OAuth for consent, AuthZEN for tools. ## Get started 1. Create an **MCP server** project and enable **MCP Auth → Settings**. 2. Integrate `@authdog/mcp-sdk` with trust store + AuthZEN enforce ([MCP security](/guides/mcp-security)). 3. Forward `mcp_runtime` events to your SIEM ([MCP authentication → Audit & SIEM](/docs/mcp-authentication#audit-and-siem)). ## Related - [MCP authentication](/docs/mcp-authentication) - [Machine identity](/docs/machine-identity) - [Authorization (AuthZEN)](/docs/authzen) ### Custom domains Source: https://www.authdog.com/docs/custom-domains.md Use a custom identity-flow domain such as `auth.example.com` so hosted sign-in and other identity flows use your brand's hostname instead of the default Authdog hostname. Configuration is environment-specific. ## Verify the parent domain First prove control of the parent domain at tenant level: 1. In the [Authdog console](https://console.authdog.com), open tenant **Settings > Domains**. 2. Add the apex domain, such as `example.com`. 3. Copy the displayed validation subdomain and validation token. 4. Create the requested DNS `TXT` record with that exact name and value. 5. After DNS propagation, choose **Retry DNS Verification**. Authdog queries the displayed validation name for a TXT value exactly matching the token. Keep the record until the console marks the domain **Verified**. Verification belongs to the tenant. A verified apex domain can then back an environment hostname at the apex or one subdomain level, such as `example.com` or `auth.example.com`. ## Configure the environment hostname 1. Select the target project and environment. 2. Open **Domains**. 3. Enter **Identity Flows URI** as a hostname only, for example `auth.example.com`. 4. Save it. 5. Under **Vanity domains**, add the same hostname (Sync Domain will also register it if missing). 6. At your DNS provider, create a `CNAME` from that hostname to the regional edge target shown by the console (for example `nbg1-dns.authdog.com`). Use grey-cloud / DNS-only, do not proxy through Cloudflare orange-cloud. 7. In **Verified Domains**, select **Sync Domain**. This verifies DNS against the vanity edge, binds the hostname for routing, and probes HTTPS so the edge can issue a certificate. 8. Wait until status shows **Synchronized** (`ready` or `active`). 9. Test sign-in and callback behavior on the custom hostname before sending production traffic. DNS changes may take time to propagate. Synchronization requires a verified matching tenant domain and a CNAME already pointing at the regional vanity edge. ## Hostname rules Enter only a hostname: - No `http://` or `https://` - No path, query, fragment, or port - No `localhost` - ASCII hostname labels only - Maximum one subdomain level, such as `auth.example.com` The hostname can be at most 253 characters, with each label at most 63 characters. ## Security guidance - Verify domain ownership before binding an environment. - Keep DNS and Authdog ownership aligned. Remove the Authdog vanity hostname before deleting or repointing its DNS record. - Use separate hostnames for development, staging, and production. - Do not point an unverified customer-controlled hostname at your production identity environment. - Recheck OAuth and SSO provider allowlists after hostname changes. Their redirect URI must match the callback URI shown by the Authdog connection form. - Treat certificate or DNS failure as an authentication outage; keep DNS changes controlled and reviewed. ## Operational notes - **Not yet in verified domains:** verify the parent apex in tenant settings, then retry Sync Domain. - **Domain is not pointing to the edge host:** check the CNAME name and target (must match the regional edge), wait for propagation, and sync again. - **Vanity status Pending / Failed:** fix DNS, refresh under Vanity domains, then Sync Domain again. - **Synchronized but HTTPS fails:** open the hostname once after sync; certificate issuance can finish on the first request. - **Provider callback fails:** copy the current redirect URI from **Authentication > Providers** into the upstream provider's allowlist. - **Moving a hostname:** register and sync the replacement first. DNS and certificate propagation can make immediate cutovers unreliable. Saving Identity Flows URI updates environment configuration. **Sync Domain** is the step that binds the verified hostname to the Kubernetes vanity edge for TLS and routing. ## Related - [Deployments](/docs/deployments): environment-specific configuration - [Account Portal](/docs/account-portal): hosted identity screens - [SSO](/docs/sso): callback and provider setup - [Authentication](/docs/concepts/authentication): identity-flow model - [Security](/docs/security): session and key security ### Email providers Source: https://www.authdog.com/docs/email-providers.md Each environment can deliver transactional authentication email through Authdog's managed provider or one connected provider of your own. Only one provider is active at a time. The managed provider is used until you connect and activate another. ## Choose a provider Supported email delivery choices: - **Authdog managed**: no credentials or setup. - **Custom SMTP**: host, port, username, password, from address, and optional implicit TLS. - **Resend**: API key and from address. - **SendGrid**: API key and from address. - **Amazon SES**: access key ID, secret access key, region, and from address. - **Postmark**: server token, from address, and optional message stream. Provider configuration belongs to one environment. Configure development first, then repeat with production-specific credentials and sender domains. ## Connect a provider In the [Authdog console](https://console.authdog.com): 1. Select the project and environment. 2. Open **Notifications > Providers**. 3. Select the **Email** channel. 4. Choose a provider and select **Connect**. 5. Enter its required credentials and from address. 6. Save. A newly connected provider becomes active. 7. Select **Test**, enter a recipient, and confirm delivery. When managing an existing provider, secret fields are masked and never loaded back into the browser. Leave a secret field blank to keep its stored value; enter a new value to replace it. Connect several providers, but only one sends for the environment. Use **Set active** to switch, or **Use managed** to return to Authdog-managed delivery. ## SMTP transport Custom SMTP supports: - Port `587` with STARTTLS upgrade. - Port `465` with **Use TLS/SSL** enabled for implicit TLS. - `AUTH LOGIN` when both username and password are present. Use the exact submission host and credentials supplied by your relay. Do not use an unencrypted SMTP endpoint. ## Prepare the sender domain Before activation: 1. Verify the from address or domain with your provider. 2. Publish provider-required SPF and DKIM records. 3. Add a DMARC policy appropriate for your rollout. 4. Confirm the provider account is out of sandbox or test mode where applicable. 5. Send a test to a mailbox outside your own domain and inspect authentication results. Activating a provider in Authdog does not verify sender-domain ownership at that provider. Provider-side rejection still causes delivery failure. ## Security guidance - Create least-privilege sending credentials for Authdog; do not reuse account-owner or general AWS credentials. - Store provider secrets only through the console. They are encrypted server-side and are not returned by provider queries. - Rotate credentials in a non-production environment first, then replace production values and run a test. - Restrict SES credentials to required email actions and region. - Use a dedicated transactional sender domain to isolate reputation from employee mail. - Disconnect credentials no longer in use. ## Operational notes - Test before switching active provider and again after DNS or credential changes. - Keep Authdog managed delivery available as a deliberate rollback option. - A connected provider is not necessarily active; check its status badge. - Postmark defaults to the `outbound` message stream when no stream is configured. - SMTP defaults to port `587` when no usable port reaches dispatch, but the console requires a port during setup. - If delivery fails, check provider suppression lists, account limits, sender verification, region, credentials, and provider logs. - Disconnecting removes that environment's provider configuration. Switch active delivery first when planning a controlled change. Email provider setup affects email only. SMS providers shown in the same console area are a separate channel and are not configured by this page. ## Related - [Authentication](/docs/concepts/authentication): flows that send authentication email - [Deployments](/docs/deployments): environment isolation - [Custom domains](/docs/custom-domains): branded identity hostnames - [Security](/docs/security): secret and session security - [Testing](/docs/testing): test-environment guidance ### Enterprise SSO Source: https://www.authdog.com/docs/sso.md Enterprise single sign-on lets users authenticate through their organization's identity provider (IdP). Authdog supports SAML 2.0 and OpenID Connect (OIDC), then routes users to an active connection by configured email domain. The current provider catalog includes first-class connectors for Okta, JumpCloud, and Ping Identity over SAML, and for Microsoft Entra ID and BeyondTrust over OIDC. Generic **SAML 2.0** and **OpenID Connect** connectors are also available. French education and research identity federations are also first-class: - **ÉduConnect** — OIDC provider for French primary and secondary students and parents signing into school portals (ENT). - **FranceConnect** — OIDC provider that lets parents authenticate through the French government's central identity aggregator. - **ARENA / AAF** — SAML provider for teachers, administrators, and education staff managed by regional academies. - **RENATER** — SAML provider for the *Fédération Éducation-Recherche*, giving students and researchers access across higher-education institutions. Institutional IdPs under RENATER or global federations such as eduGAIN can use the generic **SAML 2.0** connector. eduroam is a network access service rather than a web SSO provider, so it is not listed as an Authdog connection. ## Create a connection In the [Authdog console](https://console.authdog.com/dashboard/authentication?tab=providers&category=enterprise): 1. Select the target project and environment. 2. Open **Authentication > Providers**. The **Enterprise** filter is pre-selected by this link. 3. Choose the vendor or generic protocol connector. 4. Copy Authdog's displayed service-provider values into the IdP. 5. Enter the IdP values in Authdog. 6. Add the email domains that should discover this connection. 7. Save, then use **Test** before inviting users. Enterprise providers can have multiple connections in one environment. Give each a clear connection name and non-overlapping discovery domains; the name identifies the connection in the **Configured** list. Each first-class vendor connector links to that vendor's own setup guide from the connection form. Follow it for the IdP-side steps; the Authdog-side values are always displayed in the form. ## Configure SAML Authdog displays: - SP-initiated sign-in URL - ACS / Reply URL - SP Entity ID / audience - Downloadable SP metadata XML Configure those exact values in the IdP. Then provide Authdog with: - IdP SSO URL - IdP X.509 signing certificate - Connection name Import IdP metadata from a URL or paste metadata XML. Import fills available SSO URL, certificate, entity ID, and logout URL fields; it runs only when you select **Fetch** or **Parse & autofill**. Optional controls include logout, login hints, signed requests, protocol binding, and request template. Prefer RSA-SHA256 and SHA-256 when request signing is required; SHA-1 options exist only for compatibility with older IdPs. ## Configure OIDC Authdog displays the redirect / callback URL to register at the IdP. For an external provider, enter: - Connection name - OIDC discovery URL - Client ID - Client secret The current first-class OIDC vendors use this external-provider configuration. Authdog also supports an internal mode that selects an OIDC client already registered in the environment. Optional request controls: - **Scopes**: space-separated scopes requested from the IdP. Defaults to `openid profile email`; add IdP-specific scopes only when a claim you need requires them. - **Prompt**: forces the IdP to re-prompt (`login`, `consent`, `select_account`, or `none`). Leave on the IdP default unless you need an account picker or forced re-authentication. Use the exact discovery document URL and callback URL. A base issuer URL that does not serve the expected discovery document will not configure the connection correctly. ## Route users by email domain In **Email domains (for SSO discovery)**, enter one or more domains separated by commas, for example: ```text acme.com, eu.acme.com ``` An entry matches the exact email domain and its subdomains. For example, `acme.com` also matches `eu.acme.com`. Only active enterprise connections participate in discovery. Avoid overlapping entries across connections. Resolution uses the first active matching connection returned for the environment, so overlaps can route users unpredictably. ## Test before rollout 1. Use the connection's **Test** action. 2. Test SP-initiated sign-in. 3. Confirm the IdP sends the expected stable subject and email claims. 4. Exercise MFA and conditional-access rules at the IdP. 5. Test logout only if you enabled it. 6. Test through the [Account Portal](/docs/account-portal) or your embedded sign-in UI with a discovery-domain address. 7. Repeat in production with production URLs and credentials. ## Security guidance - Pin SAML trust to the IdP certificate supplied through metadata or secure administrator exchange. - Track SAML certificate expiry and rotate before the IdP removes the old certificate. - Use HTTPS discovery, SSO, logout, and callback URLs. - Store OIDC client secrets only in Authdog and the IdP; never browser code. - Keep request signing on SHA-256 algorithms unless an IdP strictly requires legacy SHA-1. - Verify organization administrators before allowing self-service setup through the [Admin Portal](/docs/admin-portal). - Keep a recovery sign-in path while testing policy changes so a bad IdP configuration does not lock out operators. ## Operational notes - Changing a [custom domain](/docs/custom-domains) can change the callback URL. Update the IdP allowlist with the URL currently shown in the connection form. - Metadata import does not automatically refresh later. Review IdP metadata and certificates during rotations. - Removing a connection schedules it for deletion; verify another sign-in method works first. - Missing SAML SSO URL or certificate is rejected. OIDC connections require client credentials and a discovery/issuer URL. - Copy connection ID from **Configured** when support or audit work needs an unambiguous identifier. ## Related - [Admin Portal](/docs/admin-portal): customer-admin self-service setup links - [Account Portal](/docs/account-portal): end-user hosted sign-in - [Authentication](/docs/concepts/authentication): connection and session model - [Organizations](/docs/concepts/organizations): customer organization model - [Provisioning](/docs/concepts/provisioning): SCIM and directory lifecycle - [Custom domains](/docs/custom-domains): callback hostname setup - [Security](/docs/security): session and federation security ### Okta Source: https://www.authdog.com/docs/sso/okta.md Okta connects to Authdog over SAML 2.0. Okta stays the system of record for your workforce; Authdog fronts your application and consumes the assertion. ## Copy the Authdog SP details In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Okta**, and click **Enable**. The form shows the service-provider values to paste into Okta: | Authdog field | Where it goes in Okta | | --- | --- | | **Sign-in URL (SP-initiated)** | Optional, for IdP- vs SP-initiated flows | | **ACS / Reply URL** | **Single sign-on URL** in the SAML app | | **SP Entity ID (Audience)** | **Audience URI (SP Entity ID)** | | **SP metadata XML** | Download and import where Okta accepts metadata | Each environment has its own connection id, so its own ACS URL. ## Create the SAML app in Okta 1. In the Okta admin dashboard, open **Applications > Applications** and click **Create App Integration**. 2. Choose **SAML 2.0**. 3. Set the **Single sign-on URL** to the ACS URL from Authdog and the **Audience URI** to the SP Entity ID. 4. Map attributes: `email`, `given_name`/`firstName`, `family_name`/`lastName`. Authdog reads standard defaults; custom names can be mapped in the connection's **IdP attribute mapping** section. 5. Finish the wizard, then open the **Sign On** tab and copy the **Identity Provider Single Sign-On URL** and the **X.509 signing certificate**. The metadata URL under **Metadata details** works too. ## Configure Authdog Fastest path: paste the Okta **metadata URL** into **Import from IdP metadata URL** and click **Fetch**. Authdog fills the SSO URL, certificate, and entity ID for you. | Field | Value | | --- | --- | | **IdP SSO URL** | Identity Provider Single Sign-On URL from Okta | | **IdP X.509 certificate** | The signing certificate PEM from the Sign On tab | | **Email domains** | Domains routed to this connection, e.g. `acme.com` | Save, then toggle the connection **active**. ## Test the connection 1. On the saved connection, click **Test connection**. Authdog checks required fields, the SSO URL, and that the certificate parses as X.509. With a metadata URL set it also re-fetches the metadata and flags drift (`metadata_mismatch`, `certificate_mismatch`). Each fault comes back with a named code and a fix. 2. Then run a real login: open the **Sign-in URL**, complete the Okta prompt, and confirm the user appears under **Users** with an Okta identity linked. ## Rotate the certificate Okta signing certificates rotate on a schedule you control. To roll without a support ticket: 1. If the connection has a metadata URL, click **Rotate certificate** — Authdog re-fetches the metadata and stores the new certificate. 2. Otherwise paste the new PEM from Okta into the certificate field and save. Every rotation writes a `sso_connection_certificate_rotated` event to the environment audit log with the actor and the source. The old certificate is not readable afterward. ## Troubleshooting | Symptom | Cause | | --- | --- | | `invalid_url` fault | SSO URL is not a valid https URL | | `invalid_certificate` fault | The PEM is truncated or includes the certificate chain bag — paste only the signing cert | | `certificate_mismatch` fault | Okta rotated its cert; run **Rotate certificate** | | User looped back to sign-in | Email domain not listed on the connection | | Works in one environment only | Each environment has its own ACS URL to register in Okta | ## Related | Read | To learn how to | | --- | --- | | [Enterprise SSO](/docs/sso) | Understand the SSO model and discovery | | [SCIM provisioning](/docs/concepts/provisioning) | Sync Okta users and groups over SCIM | | [Audit logs](/docs/audit-logs) | Find the rotation and sign-in events | ### Microsoft Entra ID Source: https://www.authdog.com/docs/sso/entra-id.md Microsoft Entra ID (formerly Azure AD) connects to Authdog over OpenID Connect. Entra stays the system of record for your workforce; Authdog fronts your application and consumes the ID token. ## Copy the redirect URL In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Microsoft Entra ID**, and click **Enable**. Copy the **Redirect / Callback URL**: ```url https://identity.authdog.com/api/v1/callback/ ``` Each environment has its own connection id, so its own redirect URL. ## Register the application in Entra 1. In the Entra admin center, open **Identity > Applications > App registrations** and click **New registration**. 2. Set a name, keep **Accounts in this organizational directory only**, and add a **Web** redirect URI with the Authdog callback URL. 3. Under **Certificates & secrets**, create a client secret and copy its value — it is shown once. 4. Copy the **Application (client) ID** from the Overview page. 5. Note the **OpenID Connect metadata document** URL, e.g. `https://login.microsoftonline.com//v2.0/.well-known/openid-configuration`. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | Application (client) ID | | **Client secret** | The secret value from Certificates & secrets | | **Discovery URL** | The OpenID Connect metadata document URL (the `/.well-known/openid-configuration` suffix is optional) | | **Email domains** | Domains routed to this connection, e.g. `acme.com` | Save, then toggle the connection **active**. ## Test the connection 1. On the saved connection, click **Test connection**. Authdog validates the required fields, then fetches the discovery document and confirms it carries `authorization_endpoint`, `token_endpoint`, and `jwks_uri`. Failures come back with named codes — `unreachable` when the document cannot be fetched, `missing_endpoint` when it is the wrong document. 2. Then run a real login: open the **Login URL**, complete the Entra prompt, and confirm the user appears under **Users** with an Entra identity linked. ## Rotate the secret Entra client secrets expire. When one does: 1. Create a new secret under **Certificates & secrets**. 2. Paste it into the connection's **Client secret** field and save. No downtime: the old secret keeps working until Entra expires it. Secret rotations are recorded in the environment audit log. ## Troubleshooting | Symptom | Cause | | --- | --- | | `unreachable` fault on discovery | Wrong tenant id in the discovery URL, or the app registration was deleted | | `missing_endpoint` fault | The URL points at a v1 endpoint or a non-OIDC document — use the v2.0 metadata URL | | `AADSTS700016` | The client ID does not exist in this tenant | | `AADSTS50011` | The redirect URI is not registered on the app registration | | User looped back to sign-in | Email domain not listed on the connection | ## Related | Read | To learn how to | | --- | --- | | [Enterprise SSO](/docs/sso) | Understand the SSO model and discovery | | [SCIM provisioning](/docs/concepts/provisioning) | Sync Entra users and groups over SCIM | | [Okta](/docs/sso/okta) | Connect Okta over SAML instead | ### JumpCloud Source: https://www.authdog.com/docs/sso/jumpcloud.md JumpCloud connects to Authdog over SAML 2.0. JumpCloud stays the system of record for your workforce; Authdog fronts your application and consumes the assertion. ## Copy the Authdog SP details In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **JumpCloud**, and click **Enable**. The form shows the service-provider values to paste into JumpCloud: | Authdog field | Where it goes in JumpCloud | | --- | --- | | **ACS / Reply URL** | **ACS URL** on the SSO connector | | **SP Entity ID (Audience)** | **IdP Entity ID** / **SP Entity ID** pair | | **SP metadata XML** | Download and upload where the connector accepts metadata | Each environment has its own connection id, so its own ACS URL. ## Create the SSO connector in JumpCloud 1. In the JumpCloud admin portal, open **SSO Applications** and click **Add New Application**, then **Custom SAML App**. 2. Set the **IdP Entity ID** and **SP Entity ID** to the SP Entity ID from Authdog, and the **ACS URL** to the ACS URL. 3. Map attributes: `email`, `firstname`, `lastname`. Authdog reads standard defaults; custom names can be mapped in the connection's **IdP attribute mapping** section. 4. Activate the connector, then copy the **IdP URL** (the SSO endpoint) and download the **signing certificate**. The connector's metadata URL works too. ## Configure Authdog Fastest path: paste the JumpCloud **metadata URL** into **Import from IdP metadata URL** and click **Fetch**. Authdog fills the SSO URL, certificate, and entity ID for you. | Field | Value | | --- | --- | | **IdP SSO URL** | The connector's IdP URL | | **IdP X.509 certificate** | The signing certificate PEM | | **Email domains** | Domains routed to this connection, e.g. `acme.com` | Save, then toggle the connection **active**. ## Test the connection 1. On the saved connection, click **Test connection**. Authdog checks required fields, the SSO URL, and that the certificate parses as X.509. With a metadata URL set it also re-fetches the metadata and flags drift (`metadata_mismatch`, `certificate_mismatch`). Each fault comes back with a named code and a fix. 2. Then run a real login: open the **Sign-in URL**, complete the JumpCloud prompt, and confirm the user appears under **Users** with a JumpCloud identity linked. ## Rotate the certificate 1. If the connection has a metadata URL, click **Rotate certificate** — Authdog re-fetches the metadata and stores the new certificate. 2. Otherwise paste the new PEM from JumpCloud into the certificate field and save. Every rotation writes a `sso_connection_certificate_rotated` event to the environment audit log with the actor and the source. The old certificate is not readable afterward. ## Troubleshooting | Symptom | Cause | | --- | --- | | `invalid_url` fault | SSO URL is not a valid https URL | | `invalid_certificate` fault | The PEM is truncated — export the certificate again and paste it whole | | `certificate_mismatch` fault | JumpCloud rotated its cert; run **Rotate certificate** | | Signature validation failure | The connector signs the response only — set it to sign the assertion, or both | | Works in one environment only | Each environment has its own ACS URL to register in JumpCloud | ## Related | Read | To learn how to | | --- | --- | | [Enterprise SSO](/docs/sso) | Understand the SSO model and discovery | | [SCIM provisioning](/docs/concepts/provisioning) | Sync JumpCloud users and groups over SCIM | | [Audit logs](/docs/audit-logs) | Find the rotation and sign-in events | ### PingIdentity Source: https://www.authdog.com/docs/sso/ping-identity.md PingIdentity (PingOne, PingFederate) connects to Authdog over SAML 2.0. Ping stays the system of record for your workforce; Authdog fronts your application and consumes the assertion. ## Copy the Authdog SP details In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **PingIdentity**, and click **Enable**. The form shows the service-provider values to paste into Ping: | Authdog field | Where it goes in Ping | | --- | --- | | **ACS / Reply URL** | **ACS URL** on the SAML application | | **SP Entity ID (Audience)** | **Entity ID** | | **SP metadata XML** | Download and import where Ping accepts SP metadata | Each environment has its own connection id, so its own ACS URL. ## Create the SAML application in Ping 1. In the PingOne admin console (or PingFederate), create a **SAML application** / SP connection. 2. Set the **ACS URL** to the ACS URL from Authdog and the **Entity ID** to the SP Entity ID. 3. Map attributes: `email`, `givenName`, `surname`. Authdog reads standard defaults; custom names can be mapped in the connection's **IdP attribute mapping** section. 4. Enable the application, then copy the **SSO URL** (the IdP single sign-on endpoint) and download the **signing certificate**. Ping's SAML metadata URL works too. ## Configure Authdog Fastest path: paste the Ping **metadata URL** into **Import from IdP metadata URL** and click **Fetch**. Authdog fills the SSO URL, certificate, and entity ID for you. | Field | Value | | --- | --- | | **IdP SSO URL** | The Ping SSO endpoint | | **IdP X.509 certificate** | The signing certificate PEM | | **Email domains** | Domains routed to this connection, e.g. `acme.com` | Save, then toggle the connection **active**. ## Test the connection 1. On the saved connection, click **Test connection**. Authdog checks required fields, the SSO URL, and that the certificate parses as X.509. With a metadata URL set it also re-fetches the metadata and flags drift (`metadata_mismatch`, `certificate_mismatch`). Each fault comes back with a named code and a fix. 2. Then run a real login: open the **Sign-in URL**, complete the Ping prompt, and confirm the user appears under **Users** with a Ping identity linked. ## Rotate the certificate 1. If the connection has a metadata URL, click **Rotate certificate** — Authdog re-fetches the metadata and stores the new certificate. 2. Otherwise paste the new PEM from Ping into the certificate field and save. Every rotation writes a `sso_connection_certificate_rotated` event to the environment audit log with the actor and the source. The old certificate is not readable afterward. ## Troubleshooting | Symptom | Cause | | --- | --- | | `invalid_url` fault | SSO URL is not a valid https URL | | `invalid_certificate` fault | The PEM includes the whole chain — paste only the signing cert | | `certificate_mismatch` fault | Ping rotated its cert; run **Rotate certificate** | | `metadata_mismatch` fault | The application was recreated in Ping with a new endpoint — re-import the metadata | | Works in one environment only | Each environment has its own ACS URL to register in Ping | ## Related | Read | To learn how to | | --- | --- | | [Enterprise SSO](/docs/sso) | Understand the SSO model and discovery | | [SCIM provisioning](/docs/concepts/provisioning) | Sync Ping users and groups over SCIM | | [Audit logs](/docs/audit-logs) | Find the rotation and sign-in events | ### Passkeys Source: https://www.authdog.com/docs/passkeys.md Passkeys use WebAuthn public-key credentials instead of a shared password. A user's authenticator keeps the private key; Authdog stores credential metadata and the public material needed to verify a sign-in. ## Current availability Passkey registration and sign-in are available in Authdog's hosted identity flow when **Passkeys** is enabled for the environment. Registration requires an authenticated user session. Authentication uses discoverable credentials and local user verification. Authdog supports ES256 and RS256 credential keys. Registration requests no attestation, so Authdog verifies the WebAuthn ceremony without collecting device-identifying attestation. ## How the flow works Passkeys use two ceremonies: 1. **Registration**: Authdog creates a short-lived challenge scoped to an environment and user. Browser calls `navigator.credentials.create()`, and Authdog verifies the returned registration response before storing the credential. 2. **Authentication**: Authdog creates another challenge. Browser calls `navigator.credentials.get()`, and Authdog verifies challenge, relying-party origin, authenticator data, signature, user verification, and signature counter before creating a session. Credentials and challenges are isolated by Authdog environment. The relying-party ID comes from the identity host, so passkeys are bound to that site rather than portable across unrelated domains. ## Plan your user experience Keep account recovery independent of a single device: - Offer another verified sign-in or recovery path. - Let users register more than one authenticator. - Show credential-management controls only inside an authenticated account session. - Require recent authentication before deleting the last credential. - Explain that synced passkeys may follow a user's platform account, while device-bound authenticators may not. Passkeys can satisfy possession and local user verification, but they do not remove the need for server-side session validation. Continue validating Authdog sessions on every protected backend request. ## Security notes Authdog verifies challenge, exact origin, relying-party ID hash, user-presence and user-verification flags, credential signature, credential ownership, expiration, one-time challenge consumption, and authenticator signature counters. Registration and credential management require a validated session. ## Operations Before rollout: - Test platform authenticators, roaming security keys, and synced passkeys. - Test cancellation, unavailable authenticators, lost devices, and recovery. - Keep at least one non-passkey sign-in method enabled during migration. - Monitor authentication failures in [Audit logs](/docs/audit-logs). - Separate development and production credentials by using different Authdog environments. ## Related - [Authentication](/docs/authentication) - [MFA](/docs/mfa) - [Sessions and tokens](/docs/concepts/sessions-tokens) - [Security](/docs/security) ### Multi-factor authentication Source: https://www.authdog.com/docs/mfa.md Multi-factor authentication asks for a second check after the first sign-in succeeds. You turn it on per environment. Each user enrolls their own factors. ## Supported factors - **Authenticator app (TOTP)**: six-digit, time-based one-time passwords. Enrollment uses an `otpauth://` configuration (algorithm SHA-1, 6 digits, 30-second period, ±1 period verification window). - **SMS**: a six-digit code delivered to the user's phone. Requires an SMS delivery provider configured for the environment (Notifications → SMS Providers; Twilio, Vonage, MessageBird, or Amazon SNS). - **Push**: an approve/deny prompt against an enrolled device. - **WebAuthn security keys / passkeys**: see the passkeys documentation. Any verified factor satisfies the MFA check, and a user can enroll more than one. Email one-time codes from Magic Auth are a primary sign-in method, not a second factor. ### SMS providers SMS-based MFA needs an active SMS provider for the environment. Configure one under **Notifications → SMS Providers**: supply the vendor credentials (kept encrypted at rest) and send a test message to verify delivery. Exactly one provider is active at a time; when none is active, SMS enrollment and challenges are unavailable. ## Enable MFA for an environment 1. Open the [Authdog console](https://console.authdog.com). 2. Select the project and environment. 3. Open authentication settings and enable **Multi-factor authentication**. 4. Enroll a test user before enforcing MFA across production users. The environment setting controls whether the sign-in flow applies the MFA gate. The user must also have at least one verified factor (TOTP, SMS, or push). Keep development and production settings separate; each Authdog environment has isolated configuration and users. MFA configuration and enrollment currently use the Authdog console and Authdog-hosted account flows. Management GraphQL operations visible in console traffic are internal APIs and are not supported for external integrations. No public REST endpoint for MFA enrollment is documented at this time. ## Enroll a user Enrollment follows a prepare-then-verify sequence: 1. Authdog generates an inactive TOTP secret and ten backup codes. 2. The user scans the configuration with an authenticator app. 3. The user enters the current six-digit code. 4. Authdog verifies the code and activates the TOTP secret and backup codes. Do not consider enrollment complete until verification succeeds. Store the displayed backup codes in a secure password manager; treat each as an authentication secret. The hosted MFA screen accepts either the current authenticator code or one unused 8-digit backup code. A used backup code cannot be reused. ## Sign-in behavior After successful primary authentication, Authdog can defer issuing the normal access and refresh cookies. It sets a short-lived MFA-pending state and redirects the user to the hosted MFA screen. The user enters an authenticator code; only successful verification completes sign-in and issues session cookies. The pending state is bound to the environment and contains the original continuation details. A missing, expired, or wrong-environment state sends the user back to sign-in. Failed codes generate authentication failure activity; successful completion generates MFA sign-in activity. Your application should not call the hosted `/api/v1/mfa/verify` route directly. It relies on Authdog-managed pending cookies and is part of the hosted identity flow, not a public management API. ## Reset MFA for a user If a user loses authenticator access: 1. Verify their identity through a support process outside the compromised factor. 2. In the console, open the user in the correct environment. 3. Disable the user's MFA. 4. Have the user enroll a new authenticator. 5. Review [audit logs](/docs/audit-logs) for unexpected sign-ins or administrative changes. Disabling MFA deactivates the active TOTP secret and its associated backup codes. Treat a reset as a privileged account-recovery action; limit who can perform it and retain evidence of approval. ## Security and operations - Never log TOTP secrets, QR payloads, current codes, or backup codes. - Require TLS and use the Authdog-hosted flow so pending state stays in protected cookies. - Keep server clocks synchronized; TOTP depends on time. - Alert on repeated `MFA_SIGNIN_FAILURE` activity. - Test user lockout and administrator reset procedures. - Do not describe MFA as phishing-resistant. TOTP codes can be relayed by phishing sites. - Validate the final Authdog session on the backend; the MFA screen alone is not authorization. ## Related - [Authentication](/docs/authentication) - [Passkeys](/docs/passkeys) - [Users](/docs/users) - [Audit logs](/docs/audit-logs) - [Security](/docs/security) ### Vault Source: https://www.authdog.com/docs/vault.md Authdog Vault stores environment-scoped configuration values and secret material. Use it for credentials consumed by Authdog-managed workflows or workloads explicitly integrated with your Authdog environment. ## Access model Manage secrets from the console (**Secrets > Vault** in the [Authdog console](https://console.authdog.com)) or from the public Vault API. ### Public API The Vault API is environment-scoped and authenticated with an environment API secret (`adenv_…`), the same credential family used by the [AuthZEN](/docs/authzen) and [FGA](/docs/fga) APIs. The environment is resolved from the token, so no tenant or environment id appears in the path. | Operation | Endpoint | Required scope | | --- | --- | --- | | List (redacted) | `GET /vault/v1/secrets` | `environment:read` | | Reveal one | `GET /vault/v1/secrets/{name}` | `environment:read` | | Create / update | `PUT /vault/v1/secrets/{name}` | `environment:update` | | Delete | `DELETE /vault/v1/secrets/{name}` | `environment:update` | ```bash # Store a secret curl -X PUT https://api.authdog.com/vault/v1/secrets/STRIPE_KEY \ -H "Authorization: Bearer $AUTHDOG_ENV_API_SECRET" \ -H "Content-Type: application/json" \ -d '{ "value": "sk_live_…", "description": "billing" }' # Read it back (records a READ access-log entry) curl https://api.authdog.com/vault/v1/secrets/STRIPE_KEY \ -H "Authorization: Bearer $AUTHDOG_ENV_API_SECRET" # → { "name": "STRIPE_KEY", "value": "sk_live_…", ... } ``` List responses redact `value`; only a single-secret read returns it, and every read is recorded in the secret access log. Vault is separate from **API access** secrets: - **Vault entries** hold your values, such as provider credentials, JSON, YAML, PEM, or opaque files. - **Environment API secrets** authenticate backend calls and carry scopes such as `environment:read` or `environment:update`. Do not paste an environment API secret into Vault as a substitute for proper workload secret storage. ## Create a secret 1. Select the correct project and environment. 2. Choose **Add new**. 3. Enter a unique name. Use `/` separators, such as `production/database/password`, for console folder organization. 4. Enter text or select a file. 5. Add an optional description and expiration date. 6. Keep **Encrypt variable** enabled for sensitive values. 7. Save and verify the intended entry appears in the selected environment. Text mode preserves multi-line UTF-8. File mode Base64-encodes bytes before storage; consumers must Base64-decode the value to recover the original file. Names must be unique within an environment. ## Encryption behavior Encrypted entries use AES-256-GCM authenticated encryption. The service refuses encrypted writes when the required server encryption key is missing; it does not fall back to a built-in default key. Ciphertext uses a versioned storage format so the implementation can evolve. Authorized console reads currently decrypt and return the secret value for editing. "Encrypted" means protected at rest, not write-only. Anyone with sufficient Authdog console access to read Vault can receive plaintext. Apply least privilege to console membership and protect administrator sessions with MFA. The console also exposes environment KMS controls: - **AES-256-GCM** keys can be generated and selected as the default. The raw key is shown once on creation for offline backup; the platform keeps a wrapped copy for server-side use. - **RSA-OAEP-4096** keypairs can be generated. The private key is wrapped at rest with the platform master secret and never leaves Authdog; the PEM-encoded public key is returned on creation so encryptors can use it. Encrypt a short payload against the public key with `kmsEncrypt` and recover it server-side with `kmsDecrypt`. - The KMS UI designates a default for new encrypted material and records a key's rotation state. - Disabling a key still in use can make existing ciphertext unavailable. The Vault write path uses service-managed encryption configuration; it does not bind an individual entry to a selected KMS key, and changing the KMS default does not re-encrypt existing entries. ## Rotate a secret A Vault update replaces the value while retaining the entry's identity and metadata. Safe rotation: 1. Create the replacement credential at the upstream provider. 2. Update the Vault value. 3. Verify the dependent workflow. 4. Revoke the old credential upstream. 5. Review audit activity. There is no documented public API for atomic multi-secret rotation or version rollback. Coordinate changes that span several entries, and keep external recovery material where required. ## Expiration and deletion Expiration is metadata shown by the console. Do not assume Authdog automatically rotates the value, revokes the upstream credential, or deletes an expired entry unless your integration explicitly implements that behavior. Deletion is irreversible in the console. Confirm the environment, dependencies, and recovery plan before deleting. ## Operational guidance - Use separate entries in every environment; never reuse production credentials in development. - Prefer narrow credentials with provider-side scopes and expiration. - Keep encryption enabled unless plaintext storage is an explicit, reviewed requirement. - Never store user passwords, TOTP codes, session tokens, or private data without a defined need and retention policy. - Record the owner and purpose in the description. - Rotate after staff or vendor access changes. - Review [Audit logs](/docs/audit-logs) after create, update, or delete operations. ## Related - [API](/docs/api) - [Deployments](/docs/deployments) - [Security](/docs/security) - [Audit logs](/docs/audit-logs) ### OpenTelemetry Source: https://www.authdog.com/docs/opentelemetry.md Authdog accepts OpenTelemetry **OTLP/HTTP JSON** for traces, logs, and metrics. Point an SDK, the OpenTelemetry Collector, or Grafana Alloy at the Authdog API and authenticate with an environment API secret. This page is the contract. A longer walkthrough with Collector YAML lives in [Set up OpenTelemetry with Authdog](/blog/setup-opentelemetry-with-authdog). ## In the console After ingest, open **Observability** in the [Authdog console](https://console.authdog.com). That module is application telemetry — not [audit events](/docs/audit-logs) and not [Lidar Signals](/docs/concepts/lidar). | Tab | What it shows | | --- | --- | | Overview | Counters and a timeline for logs, metrics, and traces in the selected time range | | Logs | Severity, service, body, and `trace_id`. A trace id opens the matching trace | | Metrics | Name, type, unit, value, and service. Pick a name to see the series | | Traces | Span name, service, duration, and status. Click a trace for the parent/child tree | | Settings | OTLP endpoints and a button to mint an environment API secret (`adenv_`) | Empty state on Overview points here and to Vault API keys. Deep-links keep the time range: `?tab=traces&trace=` and `?tab=logs&trace=`. ## Endpoints Pick one base. Exporters append `/v1/traces`, `/v1/logs`, and `/v1/metrics` themselves. | `OTEL_EXPORTER_OTLP_ENDPOINT` | Paths the exporter posts | | --- | --- | | `https://api.authdog.com` | `/v1/traces`, `/v1/logs`, `/v1/metrics` | | `https://api.authdog.com/v1/otel` | `/v1/otel/v1/traces`, `/v1/otel/v1/logs`, `/v1/otel/v1/metrics` | Self-hosted deployments use your API origin. The OpenAPI document at [`/v1/openapi`](https://api.authdog.com/v1/openapi) lists the same operations. This path is **application telemetry**. Audit and security events still use [Events and webhooks](/docs/events-webhooks). ## Authenticate Required environment variables: ``` OTEL_EXPORTER_OTLP_PROTOCOL=http/json OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer adenv_ ``` Mint the secret in the console: **Observability → Settings**, or **Vault → API Keys** with the `otel:ingest` scope. Copy it once. A `dev` secret cannot write into `prod`. Legacy secrets with `environment:read` or `environment:update` still ingest. Tenant and environment come from the secret. Resource attributes such as `service.name` are stored but never used for tenancy. ## Protocol and limits - **JSON only.** `application/x-protobuf` and gRPC return **415**. - Gzip is fine when the runtime decompresses `Content-Encoding`. - Body ≤ 2 MiB. - ≤ 500 records per request. Overflow returns OTLP `partialSuccess`. - Empty `{}` with HTTP 200 is full success. ## Related - [Vault](/docs/vault): environment API secrets (`adenv_`) - [Events and webhooks](/docs/events-webhooks): Authdog lifecycle events - [Lidar](/docs/concepts/lidar): identity SIEM, not application OTLP - [Set up OpenTelemetry with Authdog](/blog/setup-opentelemetry-with-authdog): SDK and Collector examples ### Audit logs Source: https://www.authdog.com/docs/audit-logs.md Authdog audit logs record authentication and administrative activity for investigation, compliance, and operational monitoring. Environment logs cover identity activity; organization logs cover organization-level changes. ## Review logs in the console Open **Audit** in the [Authdog console](https://console.authdog.com), then select the intended project and environment. Filter by time, event category, event type, user, browser, operating system, location, external ID, or event details. Under **Query window**, set an optional 1–365 day clamp for list and export reads (it does not delete stored events). Start with a narrow time range and a known user or event type. Expand only after confirming the relevant timestamp and environment. Environment records can include event type and category, timestamp, tenant, application, environment, organization, provider, external ID, remote IP, user agent, user summary, location, and JSON event payload. Fields are nullable because context varies by event source. Organization records can include actor, authentication method, resource type and ID, previous and new values, remote IP, user agent, and environment context. ## Query environment logs with the REST API Public REST endpoints use `https://api.authdog.com/v1` and Bearer authentication. Keep the API token server-side; see [API](/docs/api) for the general conventions. ```bash curl --get \ "https://api.authdog.com/v1/tenants/$TENANT_ID/environments/$ENVIRONMENT_ID/audit/logs" \ -H "Authorization: Bearer $AUTHDOG_API_TOKEN" \ --data-urlencode "startDateTime=2026-07-01T00:00:00Z" \ --data-urlencode "endDateTime=2026-07-02T00:00:00Z" \ --data-urlencode "eventTypes=SIGNIN_SUCCESS,MFA_SIGNIN_FAILURE" \ --data-urlencode "limit=100" ``` The response contains `logs`, `total`, and optional `meta`. Pagination uses numeric `offset` and `limit`; maximum limit is 500. Supported filters include: - `startDateTime`, `endDateTime`, `from`, `to`, or `relativeTime` - `eventType` or comma-separated `eventTypes` - `eventCategory` or comma-separated `categories` - `searchQuery`, `eventDetailsSearch`, or `externalIdSearch` - comma-separated `userIds`, `browsers`, `operatingSystems`, or `locations` Treat event names as catalog values rather than guessing strings. ## Discover event types Use metadata endpoints to build filters dynamically: ```bash curl \ "https://api.authdog.com/v1/tenants/$TENANT_ID/environments/$ENVIRONMENT_ID/audit/event-types/catalog" \ -H "Authorization: Bearer $AUTHDOG_API_TOKEN" ``` Available routes: - `GET /v1/tenants/{tenantId}/environments/{environmentId}/audit/event-metadata`: observed categories, counts, types, and sample payloads for the selected time range. - `GET /v1/tenants/{tenantId}/environments/{environmentId}/audit/event-types`: grouped event types, including catalog-backed zero-count entries where available. - `GET /v1/tenants/{tenantId}/environments/{environmentId}/audit/event-types/catalog`: centralized type/category catalog. Do not hard-code an exhaustive event list. New catalog entries may appear without breaking the API. ## Record a custom action Register the name in the console first: **Audit**, then **Custom actions**. Registration is per environment. The name then appears in the event-type catalog under `custom` with a count of 0. A name is 2–64 characters, starts with a letter, and uses A–Z, digits, and underscores. Spaces and hyphens become underscores. The API stores the uppercase form, so `invoice paid` and `invoice-paid` are both `INVOICE_PAID`. An optional description is capped at 280 characters. Saving the same name again updates the description. ```bash curl -X POST \ "https://api.authdog.com/v1/events/audit" \ -H "Authorization: Bearer $AUTHDOG_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"action":"INVOICE_PAID","external_id":"inv_1042","payload":{"amount":1280}}' ``` The environment comes from the API credential. The path does not take a tenant or environment id. A name that is not registered for that environment is rejected. A successful response is `{ "event_type": "INVOICE_PAID" }`. `payload` is a JSON object. Authdog sets `origin` to `api` and `channel` to `api.audit_action`, replacing any caller values for those keys. The stored payload cannot exceed 8192 characters. `external_id` is the subject the event is about. Omit it and the log stores `-`. The event category is `custom`. Removing the registration does not delete events already written. If no event was recorded, the name leaves the catalog. ## Query organization logs ```bash curl --get \ "https://api.authdog.com/v1/organizations/$ORGANIZATION_ID/audit/logs" \ -H "Authorization: Bearer $AUTHDOG_API_TOKEN" \ --data-urlencode "categories=organization_management,tenant_management" \ --data-urlencode "limit=100" ``` The organization log endpoint supports time, event type/category, search, and resource type filters. Maximum limit is 500. ## Investigate an incident 1. Record the tenant, environment, user, and UTC time range. 2. Find the initial event and note its event ID, actor, remote IP, provider, and user agent. 3. Correlate neighboring authentication, MFA, user-management, and secret-management events. 4. Preserve raw JSON before taking remediation action. 5. Revoke sessions or credentials as needed. 6. Confirm remediation produced expected follow-up events. Event payloads are source-specific. Parse defensively, preserve unknown fields, and never make authorization decisions from audit data. ## Security and retention Audit responses may contain personal data, IP addresses, and event details. Restrict API scopes, encrypt exports, set destination retention, and avoid copying payloads into tickets without redaction. The Authdog API exposes recorded data, but this page does not promise a fixed retention period or a tamper-evident external archive for the event stream. Export artifacts (the CSV or JSON files produced by an export job) are deleted 30 days after the job is created. If regulation requires immutable long-term retention, export events to controlled SIEM or storage and document the verification process. To record a business action that is not in the built-in catalog, [register a custom action](#record-a-custom-action) and post it with the API credential for that environment. To export a time window on demand, use the events export API (`POST /v1/events/exports`, then download the finished CSV or JSON artifact) or the Export control on the console audit view. Exports contain only the environment the credential belongs to. For continuous retention, stream events to your own platform with a [SIEM channel](/docs/events-webhooks) and manage the lifecycle there. ## Audit trail vs Lidar Audit logs record what happened. They are not threat decisions by themselves. [Lidar](/docs/concepts/lidar) correlates the auth event stream and raises **Signals** when detectors mark activity suspicious. Use audit events to investigate and confirm remediation; use Lidar for identity-aware detections in the console. Forwarding the raw stream to an external platform is [SIEM channel delivery](/docs/events-webhooks), not Lidar. ## Related - [Events and webhooks](/docs/events-webhooks) - [Lidar](/docs/concepts/lidar) - [API](/docs/api) - [Security](/docs/security) - [MFA](/docs/mfa) ### Events & webhooks Source: https://www.authdog.com/docs/events-webhooks.md Authdog exposes environment activity in two complementary ways: - **Events API**: pull immutable, cursor-paginated events derived from the environment's [audit-log](/docs/audit-logs) store. - **Webhooks**: push selected event types to your HTTPS endpoint through an environment notification channel. Use the Events API for backfills and reconciliation. Use webhooks for low-latency reactions. For reliable integrations, use both. ## List events ```bash curl --get \ "https://api.authdog.com/v1/tenants/$TENANT_ID/environments/$ENVIRONMENT_ID/events" \ -H "Authorization: Bearer $AUTHDOG_API_TOKEN" \ --data-urlencode "rangeStart=2026-07-01T00:00:00Z" \ --data-urlencode "events=SIGNIN_SUCCESS,NEW_USER" \ --data-urlencode "limit=100" ``` Each event contains: - `id`: event identifier - `event`: canonical event type - `category`: optional category - `created_at`: event time - `organization_id`: optional organization context - `data`: source payload plus available user, provider, IP, and user-agent context `limit` defaults to 20 and cannot exceed 100. `rangeStart` is inclusive; `rangeEnd` is exclusive. `events` and `categories` accept comma-separated values. The response includes an opaque `list_metadata.after` cursor. Pass it unchanged as the next request's `after` value: ```bash curl --get \ "https://api.authdog.com/v1/tenants/$TENANT_ID/environments/$ENVIRONMENT_ID/events" \ -H "Authorization: Bearer $AUTHDOG_API_TOKEN" \ --data-urlencode "after=$AFTER_CURSOR" \ --data-urlencode "limit=100" ``` Stop when `list_metadata.after` is `null`. Never decode the cursor or construct one yourself. ## Event catalog Browse every event type Authdog emits, inspect an example payload, and generate a subscription. The list below is the built-in catalog; your environment's `events/types` response is authoritative. The same data downloads as JSON. ## Discover the event catalog ```bash curl \ "https://api.authdog.com/v1/tenants/$TENANT_ID/environments/$ENVIRONMENT_ID/events/types" \ -H "Authorization: Bearer $AUTHDOG_API_TOKEN" ``` The catalog returns event/category pairs and categories. Build subscription UI and filters from this response. Do not assume the examples on this page are complete. ## Create a webhook channel Webhooks are generic notification channels scoped to an environment: ```bash curl -X POST \ "https://api.authdog.com/v1/tenants/$TENANT_ID/environments/$ENVIRONMENT_ID/notification-channels" \ -H "Authorization: Bearer $AUTHDOG_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Production identity events", "type": "webhook", "enabled": true, "webhookUrl": "https://example.com/authdog/webhooks", "eventTypes": ["SIGNIN_SUCCESS", "NEW_USER"], "channels": [] }' ``` Channel types are `webhook`, `slack`, `datadog`, `sysdig`, `splunk`, `sumologic`, and `sentinel`. Generic webhook and Slack require `webhookUrl`; the SIEM types have their own required fields (see below). Secret values are not returned by notification-channel list operations; each response instead includes a `*Configured` boolean per secret. Use the notification-channel endpoints to list, create, update, delete, and test channels. Update is `PUT`; omit a stored secret (`webhookUrl`, `datadogApiKey`, `sysdigApiToken`, `splunkToken`, `sumoUrl`, or `sentinelSharedKey`) to preserve it. ## Stream events to a SIEM The SIEM channel types forward the same event stream to your log platform. A SIEM channel with an empty `eventTypes` array drains **all** events; list event types to filter. SIEM forwarding is not [Lidar](/docs/concepts/lidar). Lidar runs detectors inside Authdog and surfaces Signals in the console. SIEM channels ship the audit/event stream to an external tool for your own correlation and retention. - `datadog`: Logs intake. Requires `datadogApiKey`. Optional `datadogSite` (defaults `datadoghq.com`), `datadogService`, `datadogSource`, `datadogTags`. - `sysdig`: Events API. Requires `sysdigApiToken`. Optional `sysdigRegion` (defaults `us1`), `sysdigSource`, `sysdigTags`. - `splunk`: HTTP Event Collector. Requires `splunkToken` and `splunkUrl` (HEC base URL, `host[:port]`). Optional `splunkIndex`, `splunkSourcetype` (defaults `authdog:audit`), `splunkSource`. - `sumologic`: HTTP Source. Requires `sumoUrl`; the collector URL is itself the credential, so treat it as a secret. Optional `sumoSourceCategory`, `sumoSourceName`, `sumoSourceHost`. - `sentinel`: Microsoft Sentinel through the Azure Log Analytics Data Collector API. Requires `sentinelWorkspaceId` (workspace GUID) and `sentinelSharedKey` (workspace primary or secondary key). Optional `sentinelLogType` sets the custom table name (alphanumeric, defaults `AuthdogAudit`; Azure stores records as `_CL`). ```bash curl -X POST \ "https://api.authdog.com/v1/tenants/$TENANT_ID/environments/$ENVIRONMENT_ID/notification-channels" \ -H "Authorization: Bearer $AUTHDOG_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Security events to Sentinel", "type": "sentinel", "enabled": true, "sentinelWorkspaceId": "12345678-abcd-ef01-2345-6789abcdef01", "sentinelSharedKey": "", "sentinelLogType": "AuthdogAudit", "eventTypes": [] }' ``` SIEM deliveries use the platform's native authentication instead of `X-Authdog-Signature`, and they participate in the same delivery records, retry, and redelivery flow described below. Sentinel redelivery re-signs the stored payload with the current shared key. ## Retention and channel health Authdog does not enforce a fixed retention period on the event stream and does not promise a tamper-evident archive. An environment may set a query window of 1–365 days. List and export reads then start at that window; the underlying stream is not deleted. Clearing the window restores an unclamped read. Export artifacts are deleted 30 days after the job is created. If you need long-term or regulated retention, drain events to your own platform with a SIEM channel (continuous) or export a time window on demand (`POST /v1/events/exports`, then download the CSV or JSON artifact) and manage the lifecycle on your side. Each channel's delivery health — last success, last failure, and whether a redelivery is pending or exhausted — is visible on the channel in the console Notifications view, so you can confirm your drain is healthy without operator logs. ## Verify webhook signatures Generic webhook deliveries include: - `X-Authdog-Signature: t=, v1=` - `X-Authdog-Event-Type` - `X-Authdog-Delivery-Id` `v1` is HMAC-SHA256 over the exact bytes of `t + "." + rawBody`. Verification order: 1. Read the raw request body before JSON parsing. 2. Parse the timestamp and `v1` from the signature header. 3. Reject timestamps outside your replay tolerance. 4. Compute HMAC-SHA256 with the endpoint's signing secret. 5. Compare digests using constant-time comparison. 6. Only then parse and process the JSON. Store the delivery ID and make your handler idempotent. Retries and manual redelivery can send the same logical event more than once. The webhook channel list API currently returns the signing secret. Restrict the API token that can call it, never expose the response to a browser, and avoid logging it. ## Delivery and retry behavior Any `2xx` response is success. Failed deliveries are recorded and retried with exponential backoff beginning at 60 seconds, for at most five total attempts. Delivery records include status, attempts, response status, error, last attempt, and next retry time. List delivery records: ```bash curl --get \ "https://api.authdog.com/v1/tenants/$TENANT_ID/environments/$ENVIRONMENT_ID/webhooks/deliveries" \ -H "Authorization: Bearer $AUTHDOG_API_TOKEN" \ --data-urlencode "status=failed" \ --data-urlencode "limit=100" ``` The endpoint supports `channelId`, `status`, `offset`, and `limit` up to 200. A recorded delivery can be manually redelivered: `POST /v1/tenants/{tenantId}/environments/{environmentId}/webhooks/deliveries/{deliveryId}/redeliver` Redelivery re-signs the stored payload. Your endpoint must still deduplicate. ## Rotate the signing secret `POST /v1/tenants/{tenantId}/environments/{environmentId}/webhooks/{channelId}/rotate-secret` returns a new secret. The previous secret stops being valid immediately, so rotation has no dual-secret grace period. Coordinate the deployment: 1. Pause or tolerate failed processing. 2. Rotate the secret. 3. Update the receiver immediately. 4. Send a test delivery. 5. Redeliver failed records after verification succeeds. ## Related - [Audit logs](/docs/audit-logs) - [Lidar](/docs/concepts/lidar) - [API](/docs/api) - [Integrations](/docs/integrations) - [Security](/docs/security) ### Actions Source: https://www.authdog.com/docs/actions.md Actions are synchronous hooks that run *during* an authentication flow. Unlike [events & webhooks](/docs/events-webhooks), which fire after the fact and cannot change the outcome, an action is awaited inline and its response gates the flow: it can **allow**, **deny**, or **enrich** the session with custom token claims. Use actions to enforce rules your backend owns (block disposable email domains, require an allow-list, call a risk engine, or stamp entitlements into the token) without forking Authdog's hosted flows. ## Triggers Each action is bound to one trigger: | Trigger | Fires | Typical use | | --- | --- | --- | | `pre_registration` | Before an account is created | Block domains, enforce invite-only | | `pre_authentication` | Before credentials are verified | Deny known-bad users, geo rules | | `post_authentication` | After a successful sign-in | Risk scoring, step-up decisions | | `pre_token_issuance` | Before a token is signed | Inject custom claims (entitlements, roles) | `post_authentication` and `pre_token_issuance` run for every flow, email/password, social, and enterprise SSO. ## Contract Authdog `POST`s a JSON body to your endpoint and signs it with an `X-Authdog-Signature: t=, v1=` header (the same scheme as webhooks). Verify the signature with the action's signing secret, then answer: ```json { "decision": "allow", "claims": { "plan": "pro" } } ``` - `decision`: `"allow"` or `"deny"` (required). - `message`: optional; surfaced to the user on a deny. - `claims`: optional object merged into the issued token (honored on `pre_token_issuance`). A `deny` short-circuits the flow immediately. Hooks for the same trigger run in rank order. ## Fail mode If your endpoint times out, errors, or returns a malformed body, `failMode` decides the outcome: - `closed` (default): the auth flow is blocked. Choose this for security-critical checks. - `open`: the flow proceeds as if allowed. Choose this for enrichment that must never take down sign-in. `timeoutMs` bounds how long Authdog waits (default 3000ms). ## Public API Actions are managed with your Authdog management API token at `https://api.authdog.com/v1/tenants/{tenantId}/environments/{environmentId}/actions`. | Operation | Endpoint | | --- | --- | | List | `GET /actions` | | Create / update | `POST /actions` | | Test | `POST /actions/test` | | Logs | `GET /actions/executions` | | Delete | `DELETE /actions/{id}` | Create returns the signing secret **once**: ```bash curl -X POST \ https://api.authdog.com/v1/tenants/$TENANT/environments/$ENV/actions \ -H "Authorization: Bearer $AUTHDOG_MANAGEMENT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Block disposable emails", "hookType": "pre_registration", "url": "https://api.acme.com/authdog/hook", "failMode": "closed" }' # → { "action": { ... }, "signingSecret": "whsec_…" } ``` Store the secret securely; it is not retrievable later (only a masked hint is returned by the list endpoint). Rotate it with `{ "id": "…", "regenerateSigningSecret": true }`. ## Console Configure actions under **Actions** in the console. The **Configuration** tab manages hooks and reveals the signing secret on create; the built-in **Test** button fires a sample payload at your URL and shows the decision; the **Logs** tab lists recent invocations with status, HTTP code, and latency. ## Related - [Events & webhooks](/docs/events-webhooks) - [JWT claims](/docs/jwt-claims) - [Bot protection](/docs/bot-protection) - [Audit logs](/docs/audit-logs) ### Fine-grained authorization Source: https://www.authdog.com/docs/fga.md Fine-grained authorization (FGA) models resource access as relationships. Use it when roles alone cannot express rules such as “Alice can edit document 42,” “engineering group can view folder 7,” or “document viewers inherit access from parent folder.” ## Public API Authdog exposes an environment-scoped, Zanzibar-style FGA engine as a public REST API at `https://api.authdog.com/fga/v1`, authenticated with an environment API secret (`adenv_…`), the same credential the [AuthZEN API](/docs/authzen) uses. The environment is resolved from the token, so no tenant or environment id appears in the path. | Operation | Endpoint | | --- | --- | | Check | `POST /fga/v1/check` | | BatchCheck | `POST /fga/v1/batch-check` | | ListObjects | `POST /fga/v1/list-objects` | | Expand | `POST /fga/v1/expand` | | Read | `POST /fga/v1/read` | | Write / Delete | `POST /fga/v1/write` | | List model versions | `GET /fga/v1/models` | | Read a model | `GET /fga/v1/models/{id}` (`latest` for the active model) | | Write a model | `POST /fga/v1/models` | | Read assertions | `GET /fga/v1/assertions/{modelId}` | | Write assertions | `PUT /fga/v1/assertions/{modelId}` | | List indexed objects (early access) | `GET /fga/v1/indexes/{indexId}/objects?user=…` | Objects and subjects use the compact string form `namespace:id` (a subject may carry a relation, `namespace:id#relation`). Example check: ```bash curl https://api.authdog.com/fga/v1/check \ -H "Authorization: Bearer $AUTHDOG_ENV_API_SECRET" \ -H "Content-Type: application/json" \ -d '{ "object": "document:readme", "relation": "viewer", "subject": "user:alice" }' # → { "allowed": true } ``` Add `"trace": true` to a check to also receive the proof path that granted access: ```bash curl https://api.authdog.com/fga/v1/check \ -H "Authorization: Bearer $AUTHDOG_ENV_API_SECRET" \ -H "Content-Type: application/json" \ -d '{ "object": "group:001", "relation": "member", "subject": "user:angela", "trace": true }' # → { "allowed": true, "trace": { "kind": "relation", "label": "group:001#member", "children": [ … ] } } ``` Write tuples with `{ "writes": [...], "deletes": [...] }`, each tuple `{ "object", "relation", "subject" }`. `contextual_tuples` may be supplied on Check/BatchCheck/ListObjects to evaluate against tuples not yet persisted. For coarse role-based access, [RBAC permissions](/docs/permissions) remain the simpler choice. ## Core model A relationship tuple has this conceptual form: ```text document:roadmap#editor@user:alice ``` Parts: - `document`: object namespace - `roadmap`: application-defined object ID - `editor`: relation - `user:alice`: subject The subject can also be a userset: ```text document:roadmap#viewer@group:engineering#member ``` This grants the document `viewer` relation to members of the engineering group. Authdog stores tuples per environment with uniqueness across object, relation, and subject. Deleting an environment removes its FGA tuples. ## Authorization model An environment is one **store**. Each save of the model creates an immutable **version** with a model ID (a ULID). The latest version is the one enforced; earlier versions stay readable by ID. The model is written in an OpenFGA-style language (schema 1.1): ```text model schema 1.1 type user type group relations define member: [user] type folder relations define owner: [user] define viewer: [user, user:*, group#member] or owner type doc relations define parent: [folder] define owner: [user] define blocked: [user] define viewer: [user, user:*, group#member] or viewer from parent define can_read: viewer but not blocked ``` - `[user, user:*, group#member]` lists the subject types a direct tuple may use: a user, everyone of a type (`user:*`), or the members of a group (`group#member`). Writes with any other subject type are rejected. - `or`, `and` and `but not` combine rules. Use parentheses to mix `or` and `and`. - `viewer from parent` follows the `parent` relation and evaluates `viewer` on the related object (inheritance). - Conditions are not supported yet. The same model is stored as JSON (`namespaces`, `relations`, rewrite rules `this`, `computedUserset`, `tupleToUserset`, `union`, `intersection`, `exclusion`, plus `directTypes`). Models saved before schema 1.1 keep working; opening one in the console shows it as DSL, and saving it turns on the subject type checks. A built-in fallback model demonstrates groups, environments, folders, and documents until you save your own. The fallback is an implementation default, not a substitute for explicit production design. ## Assertions Assertions are saved checks that belong to one model version. Each states that a user should, or should not, hold a relation on an object, optionally with contextual tuples. Run them from Developer Mode while you iterate, or write them through `PUT /fga/v1/assertions/{modelId}`, which replaces the whole list. ## Permissions Index (early access) The index precomputes the objects a user can reach through chosen `type#relation` pairs, so a list page needs one lookup instead of one check per object. Writes mark an index stale and the next lookup rebuilds it. Indexes are bounded: users come from existing tuples, wildcard grants are not expanded for unknown users, and very large builds are refused. Access is enabled per organization on request. ## Clients and scopes Applications call the API with an environment API secret. Create one under **Store Settings, Authorized Clients** with these permissions: | Scope | Allows | | --- | --- | | `fga:check` | Check, BatchCheck, ListObjects, Expand | | `fga:read` | Read tuples, models, assertions and indexed objects | | `fga:write` | Write and delete tuples, write assertions | | `fga:model` | Write models | A secret without any `fga:` scope predates scopes and keeps full access. As soon as it carries one `fga:` scope it is limited to exactly those. A missing scope returns `403`. ## Logs Every API and console call is logged and searchable for seven days under **Logs**: timestamp, response status, operation, method, actor IP and client name. Search with `operation:Write status:400 client:"Backend service"`. Free text matches client, IP and operation, not request bodies. ## Design a model 1. List protected object types: `document`, `folder`, `workspace`. 2. Define relations in product language: `owner`, `editor`, `viewer`. 3. Identify direct grants, group grants, and inheritance. 4. Map every sensitive operation to a required object relation. 5. Add tuples when membership or ownership changes. 6. Remove tuples on revocation and resource deletion. 7. Test allowed and denied cases, including inherited access. Prefer a few stable relations. Avoid encoding transient request context into persistent tuples; use contextual relationships only when a supported integration explicitly defines them. ## Runtime decisions The engine supports these conceptual operations: - **Check**: whether a subject holds a relation on an object. - **BatchCheck**: multiple checks in one request. - **ListObjects**: objects in a namespace available to a subject. - **Write/Delete**: mutate tuples. - **Read**: inspect tuples. - **Expand**: flatten a relation to concrete subjects. An unknown namespace or relation is rejected. Check returns a boolean and fails closed when the relation cannot be established. The per-environment model is cached briefly for decision performance; model changes may not be visible instantly across every isolate. ## Enforce in your application Authorization checks must run on the backend for every protected operation. Client-side visibility is only UX. Use stable user and object identifiers, include the correct Authdog environment, and default to deny on timeout or error. For list pages, avoid fetching every object and checking one by one. Use a supported list operation or maintain an application index consistent with the relationship tuples. Always re-check before a mutation because list results can become stale. ## Security and operations - Keep tuple writes behind a trusted backend. - Validate object ownership before writing a delegation tuple. - Treat group membership updates as security-sensitive. - Remove relationships when users, groups, or resources are deleted. - Log model and tuple changes through [Audit logs](/docs/audit-logs). - Test cycles and deep inheritance before rollout. - Separate development and production models by Authdog environment. - Maintain a break-glass process outside the normal relationship graph. FGA answers the relationship question; it does not authenticate the user. Validate the [Authdog session](/docs/concepts/sessions-tokens) first, then evaluate authorization. ## Related - [Authorization concepts](/docs/concepts/authorization) - [AuthZEN API](/docs/authzen) - [Permissions](/docs/permissions) - [Organizations](/docs/concepts/organizations) - [Audit logs](/docs/audit-logs) - [Backend requests](/docs/backend) ### AuthZEN API Source: https://www.authdog.com/docs/authzen.md Authdog answers authorization questions over the OpenID Foundation's [AuthZEN Authorization API 1.0](https://openid.github.io/authzen/). Your application acts as the Policy Enforcement Point (PEP) and calls Authdog as the Policy Decision Point (PDP): send a subject, an action, and a resource; get back permit or deny. Reach for AuthZEN when you want a vendor-neutral wire contract, when a gateway or sidecar in your stack already speaks it, or when you want one decision endpoint no matter which model (RBAC, ABAC, or FGA) the environment enforces. ## Endpoints The PDP is mounted at the API origin root, outside the `/v1` base path, because the specification fixes these paths and interop tooling probes them verbatim. | Endpoint | Purpose | | --- | --- | | `POST /access/v1/evaluation` | One decision for one subject, action, and resource. | | `POST /access/v1/evaluations` | Boxcarred batch of up to 100 evaluations. | | `POST /access/v1/search/subject` | Who can perform this action on this resource? | | `POST /access/v1/search/resource` | What resources can this subject act on? | | `POST /access/v1/search/action` | What actions can this subject perform on this resource? | | `GET /.well-known/authzen-configuration` | Discovery document. Unauthenticated, per section 9.1. | Discovery advertises all five endpoints and honors `X-Forwarded-Host` and `X-Forwarded-Proto`, so the advertised URLs match the origin your PEP actually calls. ```bash curl https://api.authdog.com/.well-known/authzen-configuration ``` ## Authentication Every endpoint except discovery is authenticated with an environment API secret (`adenv_...`) as a bearer token. The token alone identifies the environment whose policies decide the request, so no tenant or environment appears in the path. ```bash curl -X POST "https://api.authdog.com/access/v1/evaluation" \ -H "Authorization: Bearer $AUTHDOG_ENVIRONMENT_SECRET" \ -H "Content-Type: application/json" \ -d '{ "subject": { "type": "user", "id": "usr_123" }, "action": { "name": "can_read" }, "resource": { "type": "document", "id": "roadmap" } }' ``` The response is the spec's decision shape: ```json { "decision": true, "context": { "reason_admin": { "reason_code": "role_grant" }, "reason_user": { "en": "Permitted by an assigned role." } } } ``` The environment secret is a PEP credential, not an end-user token. Keep it server-side; never ship it to a browser or mobile client. ## How decisions are made The PDP dispatches on the environment's permission model, reusing the same engines that enforce access inside Authdog, a decision served over AuthZEN can never disagree with what Authdog enforces internally. - **RBAC**: the action is normalized to a CRUD verb and checked against the subject's role grants, including roles inherited through groups. - **ABAC**: the request is converted into policy input and evaluated against the environment's enabled policies. - **FGA**: the action is mapped onto a relation and checked against the environment's authorization model and relationship tuples. See [Authorization concepts](/docs/concepts/authorization) for how to choose a model, and [Fine-grained authorization](/docs/fga) for the relationship model. ### Action names AuthZEN leaves the action namespace to the PDP. Authdog normalizes action names onto four coarse verbs (`create`, `read`, `update`, `delete`) so PEPs can send whichever dialect they already use: | Sent by the PEP | Normalized verb | | --- | --- | | `create`, `can_create`, `insert`, `add`, `write`, `can_write`, `POST` | `create` | | `read`, `can_read`, `view`, `can_view`, `GET`, `list`, `HEAD`, `search` | `read` | | `update`, `can_update`, `edit`, `can_edit`, `modify`, `PUT`, `PATCH` | `update` | | `delete`, `can_delete`, `remove`, `destroy` | `delete` | An unrecognized action name falls back to `read`, the least privileged verb. That means an unknown action can never widen access, but it also won't grant what you intended, so keep your PEP on a name from the table. ### Subjects Subject types `user`, `identity`, and `subject` resolve against the environment's directory, matched by user id or external id. A subject that cannot be resolved is denied with reason code `subject_not_found`; a resolved but deactivated user is denied with `subject_inactive`. Reason codes are stable and safe to log: `role_grant`, `no_matching_role`, `policy_allow`, `policy_deny`, `no_policies`, `relation_holds`, `relation_missing`, `subject_not_found`, `subject_inactive`, and `evaluation_error`. ## Batching `POST /access/v1/evaluations` evaluates up to 100 items in one request. Top-level `subject`, `action`, `resource`, and `context` act as defaults; each item overrides only what it sets. ```json { "subject": { "type": "user", "id": "usr_123" }, "resource": { "type": "document", "id": "roadmap" }, "evaluations": [ { "action": { "name": "can_read" } }, { "action": { "name": "can_update" } }, { "action": { "name": "can_delete" } } ], "options": { "evaluations_semantic": "deny_on_first_deny" } } ``` Supported semantics are `execute_all` (the default), `deny_on_first_deny`, and `permit_on_first_permit`. When a batch short-circuits, the remaining entries are filled with the short-circuit decision rather than omitted, so the response array always lines up with the request array. A batch shares one environment lookup and memoizes subject and grant reads, so evaluating 50 items costs a handful of database round trips, not 50. ## Search The search endpoints answer the reverse questions, enumeration rather than a yes/no. Results are paginated forward-only: pass the `next_token` from a response's `page` block to fetch the next page. ```bash curl -X POST "https://api.authdog.com/access/v1/search/resource" \ -H "Authorization: Bearer $AUTHDOG_ENVIRONMENT_SECRET" \ -H "Content-Type: application/json" \ -d '{ "subject": { "type": "user", "id": "usr_123" }, "action": { "name": "can_read" }, "resource": { "type": "document" } }' ``` Search evaluates every candidate it enumerates, so it costs far more than a single evaluation. Use it for admin surfaces and access reviews, not on a hot request path. ## Failure behavior The PDP is fail-closed. An unresolvable subject, an unknown permission model, or an internal error produces `decision: false` with a reason, never a partial permit. | Status | Meaning | | --- | --- | | `400` | Malformed JSON or a body that fails spec validation. | | `401` | Missing or invalid environment API secret. | | `502` | The decision point was unreachable or returned no decision. | Errors use one envelope on every endpoint, so a PEP can log a single shape: ```json { "error": { "status": 400, "message": "subject.id is required" } } ``` A `502` means no decision was made. Your PEP must treat it as a deny, not as a permit. ## Related - [Authorization concepts](/docs/concepts/authorization) - [Roles and permissions](/docs/permissions) - [Fine-grained authorization](/docs/fga) - [API reference](/docs/api) ### JWT claims Source: https://www.authdog.com/docs/jwt-claims.md You can add your own claims to the session tokens Authdog issues. Your app then reads roles, groups, or profile attributes straight from the token, without an extra lookup. Mappings belong to one environment. Authdog applies them at sign-in, magic-link verification, MFA elevation, and token refresh. Custom claims are additive. They never replace the standard claims, and a broken mapping never blocks sign-in. ## Configure mappings In the [Authdog console](https://console.authdog.com): 1. Select the project and environment. 2. Open **Authentication > JWT Claims**. 3. Edit the claims document in the JSON editor. 4. Save. The editor validates the document, reports errors inline, and refuses to save an invalid one. The document maps a claim name to a source. Two forms are accepted: ```json { "roles": "roles", "groups": "groups", "tenant": { "source": "static", "value": "acme" }, "dept": { "source": "metadata", "key": "department" }, "legacy_id": { "source": "externalId", "enabled": false } } ``` The string form names the source. Use the object form when you need `value`, `key`, or `enabled`. Setting `"enabled": false` keeps the mapping in the document without putting the claim on the token. That's useful when you're staging a claim, or when you want to turn one off without deleting its definition. Claim names may contain letters, digits, and `_ . : / -`, up to 128 characters. ## Sources | Source | Emits | | --- | --- | | `roles` | Array of role slugs | | `groups` | Array of group slugs | | `email` | Primary email address | | `displayName` | Display name | | `givenName` | Given (first) name | | `familyName` | Family (last) name | | `username` | Username | | `externalId` | Stable external user id | | `metadata` | Value at `key` in the user's metadata | | `static` | The literal in `value` | `roles` covers roles assigned directly, roles inherited through group membership, and roles held via an active time-bound elevated access grant. Slugs are deduplicated and sorted, so the claim stays byte-stable between tokens for an unchanged user. `metadata` requires a `key`. `static` requires a `value`. ## Emission rules - A source that yields nothing is omitted rather than written as `null`. Check for claim presence, not for a null value. - `roles` and `groups` are always emitted when mapped, as `[]` when the user has none. A configured array claim is safe to read without a presence check. - Mappings are applied in a stable order by claim name. ## Reserved claims These claims carry token verification and authorization. If a mapping targets one of them, Authdog drops that mapping instead of applying it: `iss`, `sub`, `aud`, `exp`, `iat`, `nbf`, `jti`, `scp`, `env`, `egat`, `sid`, `externalid` Namespace your own claims (for example `https://acme.com/dept` or `acme_dept`) so a future standard claim can't collide with one of yours. ## Discovery Configured claim names show up in `claims_supported` on the environment's OpenID discovery document, so a relying party can see which claims to expect. ## Failure behavior Claim resolution is best-effort. A broken mapping, or a brief database error, never blocks authentication. Authdog still issues the token, with the standard claims only, and logs the failure. Don't build a security control on the assumption that a custom claim is present. Treat a missing claim as "no grant," and keep the server-side check as the source of truth for anything sensitive. ## Size Claims travel with the token on every request. Large arrays and long metadata values grow the header, and they can trip proxy limits. Map only the few attributes your app actually routes on. Fetch the rest through the [API](/docs/api). ## Related | Read | To learn how to | | --- | --- | | [Sessions and tokens](/docs/concepts/sessions-tokens) | See what the token already contains | | [Roles and permissions](/docs/permissions) | Put roles on the token | | [Backend requests](/docs/backend) | Read the session on your server | | [Users](/docs/users) | See where profile and metadata come from | ### Billing Source: https://www.authdog.com/docs/billing.md Authdog Billing adds a subscription catalog to your environment: define plans and the features they grant, sell them through Stripe, and gate your application on the plan or feature a signed-in user holds. Subscriptions and entitlements live next to your users, so checking a plan is a local read, not a round trip to Stripe. Billing runs on the environment's existing Stripe connection and supports one active subscription per subject. ## Prerequisites Connect Stripe first, under **Authentication > Add-ons** in the [Authdog console](https://console.authdog.com). Billing reuses that connection, its secret key creates Products and Prices, and your checkout page initializes Stripe.js with its publishable key. There is no separate Billing credential. Connect a Stripe test account to your development environment and the live account to production. Plans, features, and subscriptions never cross environments. ## Model | Object | Meaning | | --- | --- | | **Feature** | A gateable capability, identified by a slug such as `advanced_reports`. | | **Plan** | A purchasable bundle of features with a monthly price and an optional annual price. | | **Subscription** | One subject's current plan, its period, and its Stripe status. | A plan targets either a user or an organization (`forResource`), and slugs are unique within an environment. A feature's slug is what your application checks against, so treat it as frozen once it ships, renaming it revokes access for everyone holding it. ## Define plans In the console, open **Billing > Plans** for the selected environment: 1. Add the features you want to gate on. 2. Create a plan: name, slug, description, and whether it is for a user or an organization. 3. Set the currency and the monthly amount, in cents. Add an annual amount only if you sell one, a plan can be month-only. 4. Attach the features the plan grants. 5. Choose whether the plan is publicly visible, whether it is the default, and its sort order in the pricing table. 6. Select **Sync to Stripe**. Syncing creates or refreshes the plan's Stripe Product and its monthly and annual Prices. Stripe Prices are immutable, so changing an amount mints a new Price and leaves the old one in place. Stripe does not allow deleting a Price that has been used, and existing subscribers stay on the Price they bought until they change plans. An unsynced plan has no Stripe Price and cannot be checked out. ## Client endpoints Your application calls these on the Authdog identity origin, `https://identity.authdog.com`, or on your [custom domain](/docs/custom-domains) when one is configured. | Endpoint | Auth | Purpose | | --- | --- | --- | | `GET /billing/:environmentId/plans` | None | Publicly visible plans, their features, and the Stripe publishable key. | | `GET /billing/:environmentId/subscription` | Session | The caller's current subscription. | | `POST /billing/:environmentId/subscriptions` | Session | Start a subscription; returns a client secret for payment confirmation. | | `POST /billing/:environmentId/subscriptions/cancel` | Session | Cancel the caller's subscription. | The plans endpoint is deliberately unauthenticated, a pricing page has to render before sign-in. It returns only plans marked publicly visible, and the publishable key, never a secret. Pass `?for=org` to list organization plans; the default is `user`. Creating a subscription takes a `planId` and an optional `planPeriod` of `month` or `annual` (default `month`). The response carries a `clientSecret` your page confirms with Stripe Elements, so card details never touch Authdog. ```bash curl -X POST "https://identity.authdog.com/billing/$ENVIRONMENT_ID/subscriptions" \ -H "Authorization: Bearer $SESSION_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "planId": "pln_123", "planPeriod": "annual" }' ``` ## Gate your application The React SDK ships the pieces for a pricing page and for gating: | Piece | Role | | --- | --- | | `PricingTable` | Renders plans with a month/annual toggle and a per-plan call to action. | | `CheckoutButton` | Starts checkout for a plan and period. | | `Protect` | Renders its children only when the caller holds the given plan or feature. | | `hasBillingAccess` | Exposes the same check as a plain function, for use outside JSX. | ```tsx import { Protect } from "@authdog/react-elements" } > ``` `Protect` is a pure conditional render over state you already fetched, it makes no network call of its own. And like every client-side gate, it decides what the interface shows, not what the API allows: enforce entitlements again on your server before doing the paid work. ## Staying in sync with Stripe Stripe's webhook is the source of truth for subscription state: it updates the stored status, current period end, and cancellation flag, and refreshes the entitlement cache for the plan's features. Feature slugs double as Stripe entitlement lookup keys, so a feature check reads the same cache that backs entitlement claims. A subscription starts as `incomplete` and turns `active` once payment succeeds. Never grant access on the checkout response alone, wait for the subscription to report an active status. Configure the Stripe webhook for the same account you connected, and verify a full purchase in a test environment before enabling live payments. ## Limits - One active subscription per subject, per environment. - Checkout accepts user plans only. Organization plans can be defined and listed with `?for=org`, but a subscription request for one is rejected. - Amounts are integer cents in the plan's currency. A plan has a single currency. ## Related - [Integrations](/docs/integrations) - [Organizations](/docs/concepts/organizations) - [Users](/docs/users) - [JWT claims](/docs/jwt-claims) ### Bot protection Source: https://www.authdog.com/docs/bot-protection.md Authdog can require a CAPTCHA check on hosted authentication flows, cutting off automated sign-up, credential stuffing, password-reset spam, and one-time-code abuse at the front door. Two add-ons are available, **Google reCAPTCHA** and **Cloudflare Turnstile**. Each is configured per environment, so development and production can run different keys, or no protection at all. Pick one. If both are enabled for the same flow, **reCAPTCHA is used**. Bot protection filters traffic before it authenticates. It complements, but does not replace, [Lidar](/docs/concepts/lidar) detections and rate limiting. ## Protected flows Attach a check to any of these actions. The same names apply to both add-ons. | Action | Flow | Visible widget | Invisible only | | --- | --- | --- | --- | | `signin` | Email and password sign-in | Yes | Yes | | `signup` | Email and password sign-up | Yes | Yes | | `waitlist` | Waitlist registration | Yes | Yes | | `password_reset` | Password reset request | Yes | Yes | | `magic_link` | Magic link request | — | Yes | | `otp_verify` | One-time code verification | — | Yes | | `otp_resend` | One-time code resend | — | Yes | | `mfa_verify` | MFA challenge verification | — | Yes | Selecting no actions protects all of them. An enabled add-on defaults to its broadest coverage. **Visible widget** means the page can render a checkbox or Turnstile challenge: reCAPTCHA **v2**, or Turnstile **managed** / **non-interactive**. **Invisible only** means the page has no room for a checkbox. Those flows need reCAPTCHA **v3**, **Enterprise**, or Turnstile **invisible**. A v2 or managed widget configured only for those actions does nothing; the request proceeds without a token. ## Choose a provider | | Google reCAPTCHA | Cloudflare Turnstile | | --- | --- | --- | | Console add-on | **Authentication > Add-ons > Google reCAPTCHA** | **Authentication > Add-ons > Cloudflare Turnstile** | | Visible challenge | v2 checkbox | Managed or non-interactive widget | | Invisible / all flows | v3 or Enterprise score (`0.0`–`1.0`) | Invisible mode | | Extra knobs | Minimum score (v3 / Enterprise, default `0.5`) | Widget mode must match the Cloudflare widget | | Client loader | `api.js` (v2/v3) or `enterprise.js` (Enterprise) | Turnstile widget script | | Siteverify | `google.com/recaptcha/api/siteverify` | `challenges.cloudflare.com/turnstile/v0/siteverify` | Use **Turnstile** when you want a privacy-friendly challenge without image puzzles. Use **reCAPTCHA v3** or **Enterprise** when you want a numeric score you can tighten over time. Keys created in Google Cloud are Enterprise: pick that version and paste the **legacy secret key** from the Integration tab. Do not enable both providers for the same action unless you intend reCAPTCHA to take over. ## Enable reCAPTCHA Create a reCAPTCHA site in the [Google reCAPTCHA admin console](https://www.google.com/recaptcha/admin) first, and add every domain that serves your Authdog authentication pages, including any [custom domain](/docs/custom-domains). Then, in the [Authdog console](https://console.authdog.com): 1. Select the project and environment. 2. Open **Authentication > Add-ons**. 3. Select **Google reCAPTCHA**. 4. Choose the version: **Enterprise** (Google Cloud keys), **v3** (classic score-based), or **v2** (checkbox challenge). 5. Enter the **site key** and the **secret key**. For Enterprise, use the **legacy secret key** shown on the Integration tab (third-party / siteverify). 6. For Enterprise or v3, set the minimum score to accept. 7. Select which auth flows to protect. 8. Enable the add-on and save. The add-on cannot be enabled without a site key. The site key is public and is rendered on the auth pages; the secret key is encrypted at rest, never returned to the browser, and is only decrypted server-side to verify tokens. **v3** and **Enterprise** score every request from `0.0` (likely a bot) to `1.0` (likely a person) without user interaction. Authdog rejects scores below your minimum. The default is `0.5`; a value outside `0.0`–`1.0` falls back to it. When Google echoes an action name, Authdog requires it to match the flow being attempted, so a token minted on one page cannot be replayed on another. **Enterprise** keys come from Google Cloud (Security > reCAPTCHA). Hosted pages load `enterprise.js` and call `grecaptcha.enterprise.execute`. Server verification still uses the classic `siteverify` endpoint with the **legacy secret key** from the Integration tab — the path Google documents for third-party services. A classic `api.js` loader will not record traffic against an Enterprise key. **v2** shows the "I'm not a robot" checkbox and returns a pass or fail with no score. It only applies to `signin`, `signup`, `waitlist`, and `password_reset`. Start v3 at a permissive minimum, watch how real traffic scores, and tighten from there. Set the bar too high and you reject legitimate users on shared networks and privacy-hardened browsers. ## Enable Turnstile Create a widget in the [Cloudflare Turnstile dashboard](https://dash.cloudflare.com/?to=/:account/turnstile) first, and add every domain that serves your Authdog authentication pages. Then, in the [Authdog console](https://console.authdog.com): 1. Select the project and environment. 2. Open **Authentication > Add-ons**. 3. Select **Cloudflare Turnstile**. 4. Choose the widget mode: **Managed**, **Non-interactive**, or **Invisible**. This must match the widget you created in Cloudflare. 5. Enter the **site key** and the **secret key**. 6. Select which auth flows to protect. 7. Enable the add-on and save. | Widget mode | What the user sees | Which flows | | --- | --- | --- | | **Managed** (recommended) | Adaptive challenge; checkbox only when Cloudflare needs a further check | `signin`, `signup`, `waitlist`, `password_reset` | | **Non-interactive** | Visible widget with a spinner; no click required | Same as managed | | **Invisible** | No widget | Every flow in the table above | A mode mismatch (for example an Invisible site key saved as Managed) makes Cloudflare reject tokens, and every protected attempt then fails closed. When Cloudflare echoes an action name, Authdog requires it to match the flow, same as reCAPTCHA v3. ## Verification behavior Authdog verifies every token server-side against the provider's `siteverify` endpoint, forwarding the client IP when it is known. The check **fails closed** when a token is missing or the provider reports it invalid, when a reCAPTCHA v3 score is under the minimum, when the echoed action does not match, or when the add-on is enabled but the secret key is missing or cannot be decrypted. The user sees a verification error and the attempt does not proceed. The check **fails open** only when the provider `siteverify` request itself errors (network or outage). A Google or Cloudflare outage must not lock every user out of an environment. Verify a new configuration with a real sign-in in a non-production environment: if the secret cannot be decrypted, sign-in is blocked until the wrapping key and add-on secret match. ## Rollout 1. Configure and test in development first. 2. Enable an invisible challenge (reCAPTCHA v3 or Turnstile Invisible) on `signup`, `waitlist`, and `password_reset`, the flows attackers hit first. 3. Add `signin`, then `magic_link` and the one-time-code actions once real traffic looks healthy. 4. Repeat in production with production-specific keys. Keys are per environment and are not copied between them. Confirm the reCAPTCHA site or Turnstile widget lists every domain that serves your auth pages, including any [custom domain](/docs/custom-domains). A domain mismatch makes the provider reject every token, and every protected attempt then fails closed. ## Related - [Recipes](/docs/recipes): goal-shaped walkthroughs that combine this add-on with Lidar. - [Lidar threat detection](/docs/concepts/lidar) - [Multi-factor authentication](/docs/mfa) - [Security](/docs/security) - [Custom domains](/docs/custom-domains) - [Authentication in the console](/docs/console/authentication) ### Account portal Source: https://www.authdog.com/docs/account-portal.md Authdog hosts the sign-in, sign-up, MFA, and account screens, so you can ship [authentication](/docs/concepts/authentication) without building that UI. The portal lives on the Authdog identity host, and you theme it per environment. ## What the portal covers The hosted Account Portal is the set of screens you would otherwise build yourself: - **Sign-in**: email/password, social login, and enterprise SSO, depending on the connections enabled for the environment. - **Sign-up**: self-service registration with whatever verification your environment requires. - **MFA**: enrollment and step-up challenges (e.g. TOTP), presented automatically when a user or policy requires it. - **Account management**: profile, credentials, linked sign-in identities, active sessions, and a **Connected apps** tab where users revoke OAuth clients they previously authorized. - **Account linking**: the **Connected accounts** card on the Profile tab lists every sign-in identity attached to the account and offers one-click **Link** buttons for the environment's enabled social connections. A user invited by email can link their Google (or GitHub, Microsoft, Apple, ...) account and sign in with either method from then on. Because the screens are hosted, they follow the connections and policies on the environment. You don't redeploy your app when you add a social provider or turn on MFA. ## How account linking works 1. The user clicks **Link Google** in the portal. The browser performs a normal OAuth sign-in against that connection with `mode=link` in the OAuth state. 2. On callback, Authdog resolves the **currently signed-in user** from the session cookie. It doesn't issue a new session, and it doesn't create a user. 3. The link is accepted only when the provider returns an email that **matches one of the account's emails** (case-insensitive) and the provider does not report it as unverified. Mismatches, missing emails, and subjects already linked to another user are refused and explained back in the portal. 4. The identity is stored in `users_identities`. From then on, signing in with that provider resolves to the linked user, with the user's original subject (`sub`) preserved in tokens. 5. **Unlink** soft-detaches the identity. It is refused when it is the user's only credential (no password and no other identity). A detached provider account cannot sign the user in again until it is re-linked from the portal. Each link and unlink emits a `USER_IDENTITY_LINKED` / `USER_IDENTITY_UNLINKED` audit event, and the console shows the identities on the user's **Profile** tab (read-only). ## Theming per environment Each environment (dev, staging, production) themes its portal independently: logo, colors, and copy. This means your development portal can look distinct from production while sharing the same flows, and you promote theming along with the rest of your environment config. See [Deployments](/docs/deployments) for how per-environment configuration is promoted. ## How the redirect flow works The portal uses a redirect-based flow: 1. Your app sends the user to the hosted portal on the Authdog identity host. 2. The user authenticates there (sign-in, sign-up, MFA as needed). 3. Authdog redirects the user back to your app, establishing an `authdog-session` cookie for the environment. 4. Your frontend and backend read that session and validate it against the environment's public key (`pk_...`). Your application never handles raw credentials; it only ever sees the resulting session. See [Sessions & tokens](/docs/concepts/sessions-tokens) for how the session is validated on subsequent requests. ## When to use the portal Reach for the Account Portal when you want auth working with the least code and are happy to send users to an Authdog-hosted URL. Choose an alternative when you need auth inside your own layout: - **[Components](/docs/components)**: prebuilt, themeable UI mounted directly in your app, no redirect. - **[Custom flows](/docs/custom-flows)**: fully headless control when neither the portal nor components fit. To get a portal working end to end, follow the [Quickstarts](/docs/quickstarts). ### Component reference Source: https://www.authdog.com/docs/components.md The frontend SDKs include prebuilt, themeable components so you can put account UI inside your own layout. They're the middle ground between the hosted [Account Portal](/docs/account-portal) and fully [custom flows](/docs/custom-flows). ## Available components The React library (`@authdog/react-elements`) has a small set of components for the signed-in parts of an app: | Component | Role | | --- | --- | | `UserProfile` | The signed-in user's profile, emails, and connected accounts. | | `UserDropdown` | The user's avatar menu with manage-account and sign-out actions. | | `Navbar` | An app navbar with built-in auth-aware user slots. | | `TOTPValidator` | The MFA code-entry step for [TOTP challenges](/docs/mfa). | Each component talks to the same environment as your session, so it always reflects the connections and policies configured for that environment. Sign-in and sign-up themselves are driven by the SDK's redirect primitives (`signIn`, `signUp`) into the hosted flow. ## Which SDKs provide them Components ship in the React component library (`@authdog/react-elements`) and pair with the framework SDKs: Next.js, Remix, SvelteKit, TanStack Start, Vue/Nuxt, Angular, Astro, Gatsby, RedwoodJS, and React Native/Expo. See the full list in [Integrations](/docs/integrations) and the SDK-specific [Quickstarts](/docs/quickstarts). ## Theming Components inherit the environment's theme (logo, colors, copy) and can be styled further to match your app. Because theming is per environment, the same component looks right in dev, staging, and production without code changes; see [Deployments](/docs/deployments). ## Mounting a component Components mount like any other React element. In a Next.js app, wrap your tree in the framework SDK's `AuthdogProvider` (which consumes the sign-in callback) and feed components the user from the `useUser` hook; the environment's public key is read from `NEXT_PUBLIC_PK_AUTHDOG`: ```tsx import { AuthdogProvider, useUser } from "@authdog/nextjs-app"; import { UserProfile } from "@authdog/react-elements"; function Account() { const { user } = useUser(); return user ? : null; } export default function App() { return ( ); } ``` Once the user signs in, the SDK establishes the session for the environment, just like the hosted portal does. ## Portal, components, or custom? - Use the **[Account Portal](/docs/account-portal)** when a hosted, redirect-based experience is enough. - Use **components** when you want prebuilt UI inside your own app. - Use **[custom flows](/docs/custom-flows)** when you need full headless control over the UI and edge cases. ### Custom flows Source: https://www.authdog.com/docs/custom-flows.md Custom flows give you headless control of authentication. You call the identity APIs and SDK primitives yourself, and you build the sign-in experience. Use this when the [Account Portal](/docs/account-portal) and [components](/docs/components) can't express the UI or the logic you need. ## When to go headless Most apps are fine with the hosted portal or the embeddable components. Choose a custom flow when those can't meet the requirement: a sign-in UI that has to match a specific design, a multi-step onboarding that isn't the standard one, tight integration with your own state, or a native surface where you want to own every screen. You take on the UI, the error handling, the MFA and step-up prompts, and the correctness of the flow. Authdog still issues and validates the session. The experience around it is yours. ## The primitives A headless flow is built from a few primitives that mirror what the portal and components do internally: - **Start sign-in**: begin authentication for a chosen connection (credentials, social, or SSO) and drive any challenges (including MFA) yourself. - **Handle the callback**: receive the result of the authentication step and exchange it for a session. - **Establish the session**: set the `authdog-session` for the environment so the browser and your backend can use it. - **Sign out**: clear the session. ## Illustrative shape The precise API surface depends on your SDK; with the framework-agnostic browser client it looks like: ```ts import { createAuthdogClient } from "@authdog/javascript"; const authdog = createAuthdogClient({ publicKey: PK_AUTHDOG }); // 1. Kick off sign-in (redirects to the identity flow) authdog.signIn(); // 2. On return, consume the callback and persist the session authdog.handleRedirectCallback(); // ...later authdog.signOut("/"); ``` ## Validating the session Once the `authdog-session` exists, validation is the same no matter how you created it. See [Sessions and tokens](/docs/concepts/sessions-tokens). Your server enforces it on protected routes, as described in [Backend requests](/docs/backend). If you're rebuilding UI the SDK already ships, drop back to [components](/docs/components) for those screens. ### Integrations Source: https://www.authdog.com/docs/integrations.md Authdog integrates with the identity providers, analytics tools, billing systems, and directories you already use. Integrations are configured per environment, so dev, staging, and production can each enable a different set; see [Deployments](/docs/deployments). ## Social login providers Let users sign in with the accounts they already have. Built-in providers include Google, GitHub, Microsoft, and Apple, with more available. Enabling a provider on an environment makes it appear automatically in the [Account portal](/docs/account-portal) and [components](/docs/components), no app redeploy required. ## Enterprise SSO Connect enterprise identity providers so your customers' employees sign in through their own IdP. First-class connectors are available for: - **Okta** - **Microsoft Entra ID** - **JumpCloud** - **Ping Identity** - **BeyondTrust** SSO connections are scoped to an [organization](/docs/concepts/organizations) and resolved per environment. See [Enterprise SSO](/docs/sso) for connection setup and domain-based routing. ## Analytics and CRM Per-environment add-ons forward identity events to your analytics and CRM stack: - **Google Analytics** - **Segment** These let you track sign-ups, sign-ins, and other auth events without wiring the instrumentation yourself. ## Billing (Stripe) The Stripe add-on is bidirectional. Entitlement claims flow into the tokens Authdog issues so your app can gate features on a user's plan, and seat counts stay in sync between Authdog [organizations](/docs/concepts/organizations) and Stripe subscriptions. ## Log streaming (SIEM) Stream security and audit events to the log platform your team already monitors. Notification channels are available for: - **Datadog** - **Sysdig** - **Splunk** (HTTP Event Collector) - **Sumo Logic** (HTTP Source) - **Microsoft Sentinel** (Azure Log Analytics) Channels are configured per environment under **Notifications**, filter by event type (or drain everything), and share the same durable retry and delivery inspection as webhooks. See [Events & webhooks](/docs/events-webhooks) for the per-provider fields and API examples. ## Webhooks and the Events API Subscribe to identity events and react in your own systems: - **Webhooks**: HTTP callbacks signed with an HMAC `X-Authdog-Signature` header and delivered with durable retry, so transient failures don't drop events. See [Testing](/docs/testing) for verifying signatures. - **Events API**: query and consume the same event stream programmatically. ## Directory sync (provisioning) Keep users and groups in sync with an external directory using **SCIM** and **HRIS** connectors. Directory sync lets an IdP or HR system create, update, and deactivate users automatically. See [Provisioning](/docs/concepts/provisioning) for the full model. ### Deployments Source: https://www.authdog.com/docs/deployments.md In Authdog, environments are your deployment stages. A project has dev, staging, and production environments, and each one is fully isolated: its own signing keys, connections, user store, and public key. Promoting auth from development to production means promoting configuration between environments, not migrating infrastructure. See [Multi-tenancy](/docs/concepts/multi-tenancy) for how tenants, projects, and environments nest. ## Environments as stages Each environment maps to a deployment stage: - **dev**: for local and iterative development. - **staging**: for pre-production verification. - **production**: for live traffic. Because environments are isolated, work in dev can never touch production users or keys. ## Per-environment keys and config Every environment has its own signing keys and its own public key (`pk_...`). Your app is configured with the public key for the environment it targets, and it validates sessions against that key. Connections (social, SSO), theming, add-ons, and policies are all set per environment. Keep each environment's public key with that environment's build: the dev key belongs in dev, the production key in production. ## Promoting configuration As a change is ready, promote configuration from dev toward production. This moves settings (connections, theming, add-ons, policies) up the stages so production mirrors what you verified earlier, without hand-copying settings between consoles. Signing keys remain distinct per environment; only configuration is promoted. ## Custom domains Point a custom domain at the identity/portal host so the [Account Portal](/docs/account-portal) and identity endpoints run under your own domain rather than the default Authdog host. Custom domains are configured per environment, so production can use your branded domain while dev uses the default. See [Custom domains](/docs/custom-domains) for verification and DNS setup. ## Edge validation Authdog runs on an edge runtime (Cloudflare Workers), so session validation happens close to your users rather than a single region. This keeps auth checks fast wherever requests originate, whether they hit the hosted portal, components, or your own backend. ## Self-hosting Enterprise customers can run the same platform on-premises in Kubernetes via the Authdog Helm chart, preserving the environment and key isolation model described here. See [Self-hosting](/docs/self-hosting). ## Testing before you promote Verify each stage against its own keys and users before promoting. See [Testing](/docs/testing) for seeding users, validating the `authdog-session`, and keeping the dev public key (never production secrets) in CI. ### Self-hosting Source: https://www.authdog.com/docs/self-hosting.md Authdog runs as a managed service on the edge by default. For regulated workloads, air-gapped environments, or data-residency requirements, Enterprise customers can run Authdog on-premises inside their own Kubernetes cluster via the official **Authdog Helm chart**. The chart packages the same control plane, identity endpoints, console, and worker runtime that power the hosted platform, so feature parity is preserved between cloud and self-hosted. > Self-hosting is available on the Enterprise plan. Contact your CSM or [support](/docs/console/support) to get access to the chart repository and an image pull secret. ## What the chart installs The Helm chart deploys the Authdog platform as a small set of services on your cluster: - **API** — the control plane and public identity endpoints (sign-in, sign-up, sessions, SSO, webhooks, actions). - **Console** — the admin console UI and account portal, served from your cluster. - **Worker runtime** — the edge-style runtime that executes auth flows, actions, and session validation close to traffic. - **Data services** — a relational store for tenants, projects, environments, and users, plus object storage for branding assets and a secrets backend for the [Vault](/docs/vault). - **Migrations job** — a pre-install/pre-upgrade hook that applies D1-style schema migrations to the data store. Each component is a separately scalable Deployment, fronted by a single Ingress, so you can tune resources and replicas per workload. ## Prerequisites Before you install, make sure your cluster meets the chart requirements: - Kubernetes **1.27** or newer. - Helm **3.12** or newer. - A supported container registry reachable from the cluster with the image pull secret Authdog provides. - A PostgreSQL **15+** cluster (managed or in-cluster) for the relational store. The chart does not bundle PostgreSQL by default; bring your own for production. - An S3-compatible object store (S3, R2, MinIO, GCS via S3 API) for branding assets and large payloads. - An Ingress controller (nginx, Traefik, or a service mesh like Istio) with TLS termination. - A DNS record pointing at the Ingress hostname you want Authdog to answer on. For evaluation only, the chart can spin up in-cluster PostgreSQL and MinIO with `--set postgresql.enabled=true,minio.enabled=true`. Do not use these bundled stores in production. ## Add the chart repository ```bash helm repo add authdog https://charts.authdog.com helm repo update ``` Pull the chart and inspect its values before deploying: ```bash helm pull authdog/authdog --untar --version ``` Your CSM will confirm the `` and provide the image pull secret. Create the secret in the target namespace before installing: ```bash kubectl create namespace authdog kubectl -n authdog create secret docker-registry authdog-pull-secret \ --docker-server=registry.authdog.com \ --docker-username= \ --docker-password= \ --docker-email= ``` ## Configure values Copy `values.yaml` from the chart and edit it for your environment. The minimum you must set: ```yaml image: pullSecrets: - name: authdog-pull-secret ingress: enabled: true className: nginx hostname: authdog.example.com tls: - secretName: authdog-tls hosts: - authdog.example.com api: replicaCount: 2 resources: requests: { cpu: 250m, memory: 512Mi } limits: { cpu: 1, memory: 1Gi } console: replicaCount: 2 data: postgres: # Point at your managed Postgres for production. host: pg.example.internal port: 5432 database: authdog existingSecret: authdog-pg secretKey: password objectStore: type: s3 endpoint: https://s3.example.com bucket: authdog-assets region: us-east-1 existingSecret: authdog-s3 accessKeySecretKey: accessKeyId secretKeySecretKey: secretAccessKey vault: kms: # Per-environment KMS keys are required, same as on the hosted platform. # Bring your own key material or let the chart generate it on first install. existingSecret: authdog-kms ``` Secrets (Postgres password, S3 credentials, KMS key material) are referenced by `existingSecret` and never written into `values.yaml`. See [Vault](/docs/vault) for how KMS keys are used per environment. ## Install ```bash helm install authdog authdog/authdog \ --namespace authdog \ --create-namespace \ --version \ --values values.yaml ``` The migrations job runs before the API and console come up. Watch rollout: ```bash kubectl -n authdog rollout status deploy/authdog-api kubectl -n authdog rollout status deploy/authdog-console ``` When both are ready, open `https://authdog.example.com` and complete first-run setup: create the first tenant, project, and environment. From that point, the on-prem console behaves identically to the hosted console. See [Console overview](/docs/console/overview). ## Environments and keys on-prem The on-prem install preserves the [multi-tenancy](/docs/concepts/multi-tenancy) model: tenants contain projects, projects contain dev/staging/production environments, and each environment has its own signing keys, public key, connections, and user store. Promoting configuration between environments works the same way as on the hosted platform. See [Deployments](/docs/deployments). The environment public keys (`pk_...`) your applications validate sessions against are issued by your on-prem install, not by `authdog.com`. Point your SDKs and backend validators at your on-prem hostname. ## Custom domains and ingress On-prem, "custom domains" means additional Ingress hosts or routes you configure for the same install. Each environment can answer on its own hostname (for example `auth.tenant.example.com`) by adding an Ingress rule and pointing the relevant DNS record at the cluster. The chart supports multiple Ingress hosts; see `ingress.hosts` in the chart README. See [Custom domains](/docs/custom-domains) for the conceptual model. ## Upgrading Run `helm upgrade` with the new chart version. The migrations job re-runs as a pre-upgrade hook and is idempotent. Treat schema migrations as forward-only; the chart does not support automatic rollback of the data store. ```bash helm repo update helm upgrade authdog authdog/authdog \ --namespace authdog \ --version \ --values values.yaml ``` Always back up the Postgres database before upgrading. For a managed Postgres, use your provider's snapshot mechanism. ## Backups and disaster recovery - **Postgres**: take regular snapshots and ship WAL to your standby region. The chart does not manage backups for you. - **Object store**: enable bucket versioning on the assets bucket. - **KMS keys**: back up the encrypted key material secret. If `environment_kms_keys` or `encrypted_key_material` is missing after a restore, environments cannot decrypt their vault entries — recover with the `0084_ensure_environment_kms_keys` migration, same as the hosted platform. ## Air-gapped clusters For clusters with no outbound internet access: 1. Pull the chart and all images from `registry.authdog.com` on a connected workstation. 2. Push them into your internal registry. 3. Set `image.registry` in `values.yaml` to your internal registry and `image.pullSecrets` to the corresponding pull secret. 4. Run `helm install` from the local chart tarball (`helm install authdog ./authdog-.tgz`). The install does not phone home. Telemetry is opt-in and disabled by default. ## What is different from the hosted platform - **Edge runtime location**: validation runs in your cluster, not on the global Cloudflare edge. Latency depends on where you place the cluster and your users. - **No Cloudflare-native bindings**: KV, D1, R2, and Workers AI bindings used by some hosted features are replaced by in-cluster equivalents (Postgres, S3, optional local inference). Feature parity is tracked per release; check the chart changelog for any feature flagged as "hosted-only". - **Updates**: you control the upgrade cadence. Security patches ship in chart releases; apply them on your schedule. - **Support**: Enterprise on-prem includes a dedicated CSM and the same SLA commitments as Enterprise cloud. See [Support Center](/docs/console/support). ## Limits and current scope - The Helm chart is GA for the core identity, console, sessions, SSO, provisioning, vault, and audit workloads. - Lidar threat detection runs on the same event stream on-prem, but the managed detector feed updates ship with chart releases rather than continuously. - Cloudflare-specific add-ons (Turnstile, Workers AI) are not available on-prem; configure an equivalent reCAPTCHA challenge under [Bot protection](/docs/bot-protection). For the full list of supported values and advanced configuration (HPA, PodDisruptionBudgets, service mesh, external secrets, OpenTelemetry export), see the chart README shipped with `helm pull`. ### Testing Source: https://www.authdog.com/docs/testing.md Testing an Authdog integration means exercising your app against a real but isolated environment. Use a dedicated dev environment with its own keys and users, and validate the `authdog-session` exactly as production would, without ever touching production secrets. See [Deployments](/docs/deployments) for how environments map to stages. ## Use a dedicated dev environment Point your tests at a dev environment. Because each environment has its own signing keys, connections, and user store, tests can create and mutate users freely without affecting staging or production. The dev environment has its own public key (`pk_...`): that's the only credential your test suite needs. ## Seed users and roles Set up the state each test expects: seed users and assign roles in the target environment. Seeding against the dev environment gives you deterministic fixtures, a known user with a known role, so assertions about access are stable. ## Validate the session Your backend enforces auth by validating the `authdog-session` cookie (or an `Authorization: Bearer ` header) against the environment's public key, the same as in production. Test both the authorized and unauthorized paths: ```ts // Protected route rejects a request with no session const res = await app.request("/api/private"); expect(res.status).toBe(401); // ...and accepts one carrying a valid authdog-session const ok = await app.request("/api/private", { headers: { cookie: `authdog-session=${session}` }, }); expect(ok.status).toBe(200); ``` See [Backend requests](/docs/backend) for how the session is resolved and enforced on the server. ## Test webhooks Webhook deliveries are signed with an HMAC `X-Authdog-Signature` header. In tests, either verify that signature against your endpoint's secret, or mock the delivery with a correctly computed signature so you exercise the same verification path production uses. Because delivery uses durable retry, also confirm your handler is idempotent. ## CI tips - Store the **dev** public key in CI: never production keys or secrets. - Keep test users and roles in the dev environment, isolated from real data. - Run session-validation and webhook-signature tests on every build so auth regressions surface early. ## Recipes ### Recipes Source: https://www.authdog.com/docs/recipes.md Recipes are short, goal-shaped walkthroughs that combine [Bot protection](/docs/bot-protection) and [Lidar](/docs/concepts/lidar) to stop a specific kind of abuse. Each recipe names the threat, the pieces you use, the setup, and how to verify it before relying on it. ## Recipes - [Prevent signup and free-trial abuse](/docs/recipes/prevent-signup-abuse): stop automated account creation and trial farming. - [Stop credential stuffing](/docs/recipes/stop-credential-stuffing): blunt list-testing against your sign-in. - [Stop MFA fatigue](/docs/recipes/stop-mfa-fatigue): cap MFA push bombing against a single user. - [Detect impossible travel](/docs/recipes/detect-impossible-travel): flag sign-ins that cannot be physically real. ## Prerequisites Every recipe assumes you have: - A [project and environment](/docs/concepts/multi-tenancy) you can configure without affecting production. - [Lidar](/docs/console/lidar) on a Team, Pro, or Enterprise plan (or an approved grant) for the environment, where a recipe tunes a monitor. - A [bot-protection](/docs/bot-protection) add-on where a recipe puts a CAPTCHA on a hosted flow: **Google reCAPTCHA** (v3 or v2) or **Cloudflare Turnstile** (managed, non-interactive, or invisible), registered with the domains that serve your auth pages. Recipes fail closed on the bot-protection side and only raise Signals on the Lidar side; neither blocks legitimate users on its own when configured in a non-production environment first. ## Related - [Bot protection](/docs/bot-protection): reCAPTCHA and Turnstile add-on reference. - [Lidar concepts](/docs/concepts/lidar): monitors, detectors, and Signals. - [Lidar in the console](/docs/console/lidar): enable access and triage Signals. - [Security](/docs/security): platform defaults behind these controls. ### Prevent signup abuse Source: https://www.authdog.com/docs/recipes/prevent-signup-abuse.md Stop automated account creation, trial farming, and waitlist flooding at the front door. Attackers spin up disposable mailboxes to claim free trials or exhaust waitlist slots; a CAPTCHA plus a signup-abuse monitor makes this expensive and visible. ## What you use - [Bot protection](/docs/bot-protection) with an **invisible** challenge on `signup` and `waitlist`: reCAPTCHA **v3**, or Cloudflare Turnstile **Invisible**. - The Lidar `repeat_signup` monitor (`detection:repeat_signup priorAccounts:>2`), correlated by IP. ## Setup 1. Register a reCAPTCHA **v3** site or a Turnstile **Invisible** widget for the domains that serve your auth pages, including any [custom domain](/docs/custom-domains). 2. In the [console](https://console.authdog.com), open **Authentication > Add-ons**, choose **Google reCAPTCHA** or **Cloudflare Turnstile**, enter the site and secret keys, and select the `signup` and `waitlist` actions. 3. For reCAPTCHA v3, start with a permissive minimum score (the default `0.5`) and tighten after watching real traffic. For Turnstile, set widget mode to **Invisible** and match the widget you created in Cloudflare. 4. Under **Lidar > Monitors**, enable `repeat_signup` and lower `priorAccounts` if your signup volume is low and the default of `2` never fires. Do not enable both add-ons for the same action. If you do, reCAPTCHA is used. ## Verify - In a dev environment, hit the hosted sign-up page with a script that reuses one IP across several sign-ups: the invisible challenge rejects automated attempts, and `repeat_signup` raises a Signal once `priorAccounts` is exceeded. - Confirm a real sign-up from a fresh browser still passes; a too-high reCAPTCHA minimum rejects legitimate users on shared networks and privacy-hardened browsers. - Check the [Lidar Signals](/docs/console/lidar) view for the finding, then triage or dismiss it so the dedup key (`ai:repeat_signup::`) does not keep refreshing. ## Related - [Stop credential stuffing](/docs/recipes/stop-credential-stuffing): the same front-door control aimed at sign-in. - [Bot protection](/docs/bot-protection): reCAPTCHA and Turnstile reference and rollout order. - [Lidar concepts](/docs/concepts/lidar): how `repeat_signup` correlates and dedups. ### Stop credential stuffing Source: https://www.authdog.com/docs/recipes/stop-credential-stuffing.md Blunt list-testing against your sign-in. Attackers replay stolen username/password pairs at scale; a CAPTCHA on sign-in plus two Lidar monitors makes the attempt expensive, surfaces it as a Signal, and keeps a real user's failed sign-in from looking like an attack. ## What you use - [Bot protection](/docs/bot-protection) with an **invisible** challenge on `signin`: reCAPTCHA **v3**, or Cloudflare Turnstile **Invisible**. `signin` can render a checkbox, but a silent score is less disruptive for real users. - The Lidar `brute_force` monitor (`detection:brute_force attempts:>100 window:5m`), correlated by IP. - The Lidar `credential_stuffing` monitor (`detection:credential_stuffing attempts:>200 window:15m`), correlated across the environment. ## Setup 1. Add a reCAPTCHA **v3** site or a Turnstile **Invisible** widget in the [console](https://console.authdog.com) under **Authentication > Add-ons**, and select the `signin` action. 2. For reCAPTCHA, set the minimum score to `0.5` initially and require the echoed action to match `signin` so a token minted on another page cannot be replayed. For Turnstile, set widget mode to **Invisible**. 3. Under **Lidar > Monitors**, enable `brute_force` and `credential_stuffing`. Tune `attempts:` and `window:` to your traffic shape; a busy environment needs higher floors than the defaults or every legitimate sign-in wave looks like an attack. Do not enable both add-ons for `signin`. If you do, reCAPTCHA is used. ## Verify - Replay a small list of bad credentials from one IP in dev: the invisible challenge drops automated attempts, `brute_force` fires on the IP, and `credential_stuffing` fires across the environment once the floor is crossed. - Confirm a real user with a wrong password does not raise a Signal; the monitors key on volume, not single failures. - Triage the resulting Signals in [Lidar > Signals](/docs/console/lidar); the dedup keys keep re-analysis from creating duplicate alerts. ## Related - [Prevent signup and free-trial abuse](/docs/recipes/prevent-signup-abuse): the same front-door control aimed at sign-up. - [MFA](/docs/mfa): the second factor that contains a stolen password. - [Lidar concepts](/docs/concepts/lidar): monitor query syntax and correlation keys. - [Bot protection](/docs/bot-protection): score handling, widget modes, and fail-closed behavior. ### Stop MFA fatigue Source: https://www.authdog.com/docs/recipes/stop-mfa-fatigue.md Cap MFA push bombing. An attacker with a user's password triggers repeated second-factor prompts, hoping the user approves one to stop the noise. Lidar's `mfa_fatigue` monitor surfaces this against a single subject so you can step in before a prompt is approved. ## What you use - The Lidar `mfa_fatigue` monitor (`detection:mfa_fatigue challenges:>4 window:30m`), correlated by subject. - Optional: [Bot protection](/docs/bot-protection) on `mfa_verify` (reCAPTCHA **v3** or Turnstile **Invisible**) to add a silent check to the challenge step. ## Setup 1. Confirm [Lidar access](/docs/console/lidar) is approved for the environment. 2. Under **Lidar > Monitors**, enable `mfa_fatigue`. The default `challenges:>4 window:30m` is a starting point; lower `challenges:` for sensitive accounts or raise it if your UX legitimately retries MFA several times. 3. If you want a silent check on the challenge itself, enable reCAPTCHA **v3** or Turnstile **Invisible** on `mfa_verify` under **Authentication > Add-ons**. Visible widgets cannot render on this page. ## Verify - In dev, trigger MFA for one user more times than the `challenges:` floor within the window: a Signal appears against that subject, deduped by `ai:mfa_fatigue::`. - Confirm a user retrying MFA two or three times does not fire; the monitor keys on volume, not single retries. - Triage the Signal in [Lidar > Signals](/docs/console/lidar); suspending the user or rotating keys invalidates outstanding sessions on the next `userinfo` check. ## Related - [MFA](/docs/mfa): the TOTP second factor this recipe protects. - [Lidar concepts](/docs/concepts/lidar): subject-keyed correlation and dedup. - [Security](/docs/security): session revocation and key rotation. ### Detect impossible travel Source: https://www.authdog.com/docs/recipes/detect-impossible-travel.md Flag sign-ins that cannot be physically real. When the same user appears in two places far apart within a window shorter than travel would allow, it usually means a token or session is being reused from a new location. Lidar's `impossible_travel` monitor raises a Signal against the subject so you can challenge or step up. ## What you use - The Lidar `impossible_travel` monitor (`detection:impossible_travel distanceKm:>5000`), correlated by subject. ## Setup 1. Confirm [Lidar access](/docs/console/lidar) is approved for the environment. 2. Under **Lidar > Monitors**, enable `impossible_travel`. The default `distanceKm:>5000` is advisory text injected into the detector prompt, not a hard gate; tune it to the distance that matters for your user base. 3. Pair with [step-up](/docs/mfa) or a challenge step in your app for users who trip the monitor, rather than blocking outright; a real user on a VPN can look like impossible travel. ## Verify - In dev, sign in as one user from two geographically distant IPs within a few minutes: a Signal appears against that subject. - Confirm a normal sign-in from the user's usual location does not fire. - Triage the Signal in [Lidar > Signals](/docs/console/lidar); the finding is a derived detection, so correlate with neighboring [audit events](/docs/audit-logs) before remediating. ## Related - [Lidar concepts](/docs/concepts/lidar): subject-keyed correlation and the role of advisory tokens. - [Audit logs](/docs/audit-logs): the underlying event trail to correlate against. - [MFA](/docs/mfa): step-up as the response to a Signal. ## Connectors ### Apple Source: https://www.authdog.com/docs/connectors/apple.md Sign in with Apple lets users authenticate with the Apple ID already on their iPhone, iPad, or Mac. It is the only connector that does not use a static client secret: Apple requires a short-lived signed JWT, which Authdog mints for every token exchange from the key material you provide. Apple also requires it. If your iOS app offers any other social login, App Store review expects Sign in with Apple alongside it. ## Before you start You need a paid [Apple Developer Program](https://developer.apple.com/programs/) membership. The free account cannot create the Services ID or the key this connector depends on. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Apple**, and click **Enable**. The form shows the redirect URI for this connection: ```url https://identity.authdog.com/api/v1/callback/ ``` Keep that dialog open, Apple needs this value, and you need Apple's values to finish it. > **Caution** > > Apple rejects `http://` and bare hostnames. `localhost` cannot be registered > at all, so local development against Apple needs a public HTTPS host (a tunnel, > or an environment with a [custom domain](/docs/custom-domains)). ## Create the App ID 1. Open [Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources/identifiers/list). 2. Select **Identifiers**, then **+**. 3. Choose **App IDs**, then **App**. 4. Give it a description and a bundle ID (for example `com.example.app`). 5. Under **Capabilities**, enable **Sign In with Apple**. 6. Register. ## Create the Services ID The Services ID is the web client, it becomes the `client_id` Apple sees. 1. Back in **Identifiers**, select **+**, then **Services IDs**. 2. Enter a description and an identifier (for example `com.example.app.web`). Note it down. 3. Register, then reopen the Services ID. 4. Enable **Sign In with Apple** and select **Configure**. 5. Set **Primary App ID** to the App ID you just created. 6. Under **Domains and Subdomains**, add `identity.authdog.com`, or your custom domain host. 7. Under **Return URLs**, paste the redirect URI from Authdog. 8. Save, then **Continue** and **Save** on the Services ID. ## Create the sign-in key 1. Open **Keys** and select **+**. 2. Name the key, enable **Sign In with Apple**, and **Configure** it against the primary App ID. 3. Register, then **Download** the `.p8` file. Apple lets you download it exactly once. 4. Note the **Key ID** shown on the key's page. Your **Team ID** is in the top-right of the developer portal, and on the membership details page. ## Configure Authdog Return to the connection form and fill in: | Field | Value | | --- | --- | | **Team ID** | The 10-character team identifier from your developer account | | **Service ID** | The Services ID identifier, e.g. `com.example.app.web` | | **Key ID** | The 10-character identifier of the sign-in key | | **Private Key** | The full contents of the downloaded `.p8` file | Paste the private key including its header and footer lines: ```text -----BEGIN PRIVATE KEY----- MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg... -----END PRIVATE KEY----- ``` Save the connection, then toggle it **active**. Authdog signs a client-secret JWT with ES256 using this key for each token request, so there is no secret to rotate on a schedule, but the key itself is a credential. Store the `.p8` in [Vault](/docs/console/vault) or your own secret manager; Apple will not give you a second copy. ## What Apple returns Authdog requests the `openid email name` scopes and reads the profile from the `id_token` Apple issues, Apple exposes no userinfo endpoint. Two Apple behaviours shape the user records you end up with: - **The name is sent once.** Apple includes the user's name only on the very first authorization, and only if the user agrees to share it. Later sign-ins carry the identifier and email alone. Persist the name when you first see it. - **The email may be a relay.** Users can choose **Hide My Email**, which yields an address like `a1b2c3@privaterelay.appleid.com` that forwards to their real inbox. It is stable and deliverable, but it is not their public address, and it will not match an existing account created with a different provider. Apple posts the authorization response back with `response_mode=form_post`, which Authdog's callback endpoint accepts on both `GET` and `POST`. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select **Continue with Apple** and complete the flow. 3. Confirm the new user appears under **Users** in the console with an Apple identity linked. 4. Sign out, sign in again, and confirm you land on the same user rather than a duplicate. ## Troubleshooting | Symptom | Cause | | --- | --- | | `invalid_client` | Team ID, Key ID, or Services ID mismatch; or the key is not enabled for Sign In with Apple | | `invalid_grant` | The `.p8` contents are truncated or reformatted, repaste the whole file | | Apple's "redirect_uri" error page | The Return URL in the Services ID does not match Authdog's redirect URI exactly | | Domain verification prompt | The domain under **Domains and Subdomains** is missing or misspelled | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | | [Google](/docs/connectors/google) | Add the other half of most consumer sign-in pages | | [Vault](/docs/console/vault) | Store the `.p8` key alongside your other secrets | | [Custom domains](/docs/custom-domains) | Serve the callback from your own HTTPS host | ### Google Source: https://www.authdog.com/docs/connectors/google.md Google is the highest-converting consumer connector for most products: the account is already signed in on the browser, and the email it returns is verified. Setup takes a Google Cloud project, an OAuth consent screen, and one OAuth client. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Google**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Configure the consent screen 1. Open the [Google Cloud console](https://console.cloud.google.com/) and select or create a project. 2. Go to **APIs & Services > OAuth consent screen**. 3. Choose **External** for a public product, or **Internal** to restrict sign-in to your Google Workspace organization. 4. Fill in the app name, support email, and developer contact. 5. Add the scopes `.../auth/userinfo.email` and `.../auth/userinfo.profile`. 6. Save. While the consent screen is in **Testing**, only the test users you list can sign in, and refresh tokens expire after seven days. **Publish** it before you launch. > **Note** > > Publishing an External app that only requests email and profile does not need > Google's verification review. Verification is triggered by sensitive and > restricted scopes (Gmail, Drive, Calendar), which Authdog does not request. ## Create the OAuth client 1. Go to **APIs & Services > Credentials**. 2. Select **Create credentials**, then **OAuth client ID**. 3. Choose **Web application**. 4. Under **Authorized redirect URIs**, paste the redirect URI from Authdog. 5. Optionally add your site origin under **Authorized JavaScript origins**. 6. Create, then copy the **Client ID** and **Client secret**. ## Configure Authdog Back in the connection form: | Field | Value | | --- | --- | | **Client ID** | Ends in `.apps.googleusercontent.com` | | **Client Secret** | The secret shown when the client was created | Save, then toggle the connection **active**. ## What Google returns Authdog requests the `profile email` scopes and reads the profile from the `id_token` Google issues, email, verified flag, name, and picture. The `sub` claim is the stable identifier; a user's email can change, `sub` does not. Google Workspace domains land here too. If you want employer-controlled sign-in with domain routing, provisioning, and admin-side session control, configure Google Workspace as an [Enterprise SSO](/docs/sso) connection instead, this connector treats every Google account the same way. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select **Continue with Google** and complete the flow. 3. Confirm the user appears under **Users** with a Google identity linked and a verified email. ## Troubleshooting | Symptom | Cause | | --- | --- | | `Error 400: redirect_uri_mismatch` | The authorized redirect URI does not match Authdog's byte for byte | | `Access blocked: app not verified` | The consent screen is in Testing and the account is not a listed test user | | `invalid_client` | Client ID or secret pasted from a different Cloud project or client | | Sign-in works in one environment only | Each environment has its own `connectionId`, so each needs its redirect URI registered | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | | [Apple](/docs/connectors/apple) | Add the connector iOS review expects alongside Google | | [Enterprise SSO](/docs/sso) | Route Google Workspace users through an employer connection | ### Meta (Facebook) Source: https://www.authdog.com/docs/connectors/meta.md Meta's consumer login (still branded Facebook Login in the developer tooling and on the button itself) signs users in with their Facebook account. It reaches an audience that skews away from developer-centric providers, and it is one of the few connectors where you must plan for users with no email address. The connector is listed as **Facebook** in the catalog and uses the provider ID `facebook-oauth20`. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Facebook**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the Meta app 1. Open [Meta for Developers](https://developers.facebook.com/apps/) and select **Create App**. 2. Pick the **Authenticate and request data from users with Facebook Login** use case. 3. Name the app and give it a contact email. 4. From the dashboard, add the **Facebook Login** product. 5. Open **Facebook Login > Settings**. 6. Under **Valid OAuth Redirect URIs**, paste the redirect URI from Authdog. 7. Leave **Client OAuth login** and **Web OAuth login** enabled, and keep **Enforce HTTPS** on. 8. Save changes. Then open **App settings > Basic** and copy the **App ID** and **App secret**. > **Caution** > > A Meta app in **Development** mode only admits people with a role on the app , > admins, developers, testers. Switch it to **Live** before real users try to sign > in, and complete the privacy policy URL and data deletion callback Meta requires > to make that switch. ## Configure Authdog Back in the connection form: | Field | Value | | --- | --- | | **Client ID** | The Meta **App ID** | | **Client Secret** | The Meta **App secret** | Save, then toggle the connection **active**. ## What Meta returns Authdog requests the `email` scope and reads the profile from the Graph API. The identifier Meta returns is app-scoped: the same person gets a different ID in a different Meta app, so credentials are not portable between environments or products. Email is the recurring problem with this connector: - Accounts registered with a phone number only have no email to release. - Users can decline the email permission on the consent screen. - An account with an unconfirmed email may return nothing. Authdog links a returning user by the provider's stable identifier, so sign-in still works, but you cannot assume an email address exists on users who arrived through Meta. If your product requires one, prompt for it after the first sign-in. Anything beyond `email` and public profile requires Meta's App Review, with a screencast and a written justification. Keep the connector to the default scopes unless you have a concrete reason to go through that process. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select **Continue with Facebook** and complete the flow. 3. Confirm the user appears under **Users** with a Facebook identity linked. 4. Test once with the email permission declined, and confirm your application handles the missing address. ## Troubleshooting | Symptom | Cause | | --- | --- | | `URL Blocked: This redirect failed` | The redirect URI is missing from **Valid OAuth Redirect URIs** | | `App not active` | The app is still in Development mode | | `Invalid Scopes` | A scope beyond the defaults has not passed App Review | | User created with no email | The account has none, or the permission was declined, expected behaviour | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | | [Users](/docs/users) | Inspect and complete profiles after a first sign-in | | [Apple](/docs/connectors/apple) | Handle the other provider that withholds real email addresses | ### Microsoft Source: https://www.authdog.com/docs/connectors/microsoft.md The Microsoft connector signs users in with a Microsoft account, personal Outlook and Xbox accounts, work or school accounts from Microsoft Entra ID, or both, depending on how you register the app. For workforce identity where a customer's IT admin owns the tenant and you want email-domain routing, use the Entra ID connector under [Enterprise SSO](/docs/sso) instead. This connector is the consumer-and-developer path. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Microsoft**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Register the application 1. Open the [Azure portal](https://portal.azure.com) and go to **Microsoft Entra ID > App registrations**. 2. Select **New registration**. 3. Name the application. 4. Under **Supported account types**, choose the audience: - *Accounts in any organizational directory and personal Microsoft accounts*, the broadest option, and the one that matches this connector's default endpoint. - *Accounts in this organizational directory only*, single tenant. 5. Under **Redirect URI**, select **Web** and paste the redirect URI from Authdog. 6. Register, then copy the **Application (client) ID** from the overview. Create the secret: 1. Open **Certificates & secrets**. 2. Select **New client secret**, give it a description and an expiry. 3. Copy the secret **Value** immediately, Azure hides it after you navigate away. > **Caution** > > Client secrets expire, up to a maximum of 24 months. Put the expiry date in > your calendar: when it passes, every sign-in through this connector fails at > the token exchange with no warning beforehand. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | Application (client) ID | | **Client Secret** | The secret **Value**, not the secret ID | Save, then toggle the connection **active**. The connector is also available under the alias `azure-oauth20`, which behaves identically. ## What Microsoft returns Authdog requests the `user.read` scope and reads the profile from Microsoft Graph at `/v1.0/me`. Personal Microsoft accounts often return `userPrincipalName` rather than a conventional mail attribute, and some work accounts have no `mail` value set at all. Authdog falls back to the identifiers Graph does provide, and links returning users on the stable object identifier. Authdog uses the `common` endpoint, which accepts both personal and organizational accounts. If you registered the app as single-tenant, users outside that tenant will be refused by Microsoft even though the request reaches it. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select **Continue with Microsoft** and complete the flow. 3. Test with both a personal account and a work account if your audience covers both. ## Troubleshooting | Symptom | Cause | | --- | --- | | `AADSTS50011: redirect URI does not match` | The URI is missing from the app registration, or registered under the wrong platform type | | `AADSTS7000215: Invalid client secret` | The secret ID was pasted instead of the secret value, or the secret expired | | `AADSTS50020: user account does not exist in tenant` | The registration is single-tenant and the user is external | | `AADSTS65001: consent required` | An admin must grant consent for the tenant | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | | [Enterprise SSO](/docs/sso) | Connect Entra ID as a workforce identity provider with domain routing | ### GitHub Source: https://www.authdog.com/docs/connectors/github.md GitHub is the default connector for developer tools. The account is already open in the browser, the profile is rich, and sign-in doubles as proof that the user is a developer. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **GitHub**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the OAuth app 1. Open [Developer settings > OAuth Apps](https://github.com/settings/developers). 2. Select **New OAuth App**. 3. Fill in: - **Application name**: shown on the consent screen. - **Homepage URL**: your site. - **Authorization callback URL**: the redirect URI from Authdog. 4. Register the application. 5. Copy the **Client ID**, then select **Generate a new client secret** and copy the secret. An OAuth App accepts exactly one callback URL, so each Authdog environment needs its own OAuth App. Name them distinctly (`Acme (dev)`, `Acme`), the name is what users see when they authorize. > **Note** > > Use an **OAuth App**, not a **GitHub App**. GitHub Apps are built around > repository installations and short-lived tokens; this connector expects the > OAuth App authorization flow. Create the app under an organization rather than a personal account if more than one person needs to manage it. Organization-owned apps may also need owner approval before members can authorize them. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | From the OAuth App page | | **Client Secret** | Shown once when generated | Save, then toggle the connection **active**. ## What GitHub returns Authdog requests the `user` scope and reads the profile from `https://api.github.com/user`, login, name, avatar, and the numeric ID that identifies the account permanently. A user can rename their GitHub account; the numeric ID stays put, and that is what Authdog links on. The profile endpoint returns the user's **public** email, which is frequently `null`, many developers keep their address private, and GitHub's `noreply` setting hides it by default. Expect a share of GitHub users with no email address and prompt for one after sign-up if your product needs it. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select **Continue with GitHub** and authorize. 3. Confirm the user appears under **Users** with a GitHub identity linked. 4. Revoke the authorization in [GitHub's applications settings](https://github.com/settings/applications) and sign in again to check the consent screen. ## Troubleshooting | Symptom | Cause | | --- | --- | | `The redirect_uri MUST match the registered callback URL` | The callback URL differs from Authdog's, or you are testing the wrong environment's connection | | `Bad verify code` | Client secret mismatch, or a reused authorization code | | No email on the user record | The account's email is private, expected behaviour | | Members cannot authorize | The organization requires owner approval for OAuth Apps | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | | [GitLab](/docs/connectors/gitlab) | Offer the other developer identity most teams expect | | [Users](/docs/users) | Collect a verified email after a first sign-in | ### GitLab Source: https://www.authdog.com/docs/connectors/gitlab.md GitLab signs users in with their GitLab.com account. Because GitLab's OAuth provider is OpenID Connect compliant, this connector gets a proper `id_token` and a userinfo endpoint, including a `email_verified` claim, which most developer connectors do not provide. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **GitLab**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. Open [Applications in your GitLab user settings](https://gitlab.com/-/user_settings/applications). For an app your team shares, create it under **Group settings > Applications** instead. 2. Select **Add new application**. 3. Name it. 4. Under **Redirect URI**, paste the redirect URI from Authdog. GitLab accepts several, one per line, so all your environments can share one application. 5. Leave **Confidential** checked, Authdog exchanges the code from the server with a client secret. 6. Select the scopes `openid`, `email`, and `profile`. 7. Save, then copy the **Application ID** and **Secret**. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | GitLab **Application ID** | | **Client Secret** | GitLab **Secret** | Save, then toggle the connection **active**. ## What GitLab returns Authdog requests `openid email profile` and reads the profile from `https://gitlab.com/oauth/userinfo`: the `sub` identifier, email and its verified flag, username, name, and avatar. The connector targets GitLab.com. A self-managed GitLab instance runs the same endpoints on your own host, which this connector does not point at, configure it through the generic **OpenID Connect** connector under [Enterprise SSO](/docs/sso), using your instance's discovery URL. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select **Continue with GitLab** and authorize. 3. Confirm the user appears under **Users** with a verified email. ## Troubleshooting | Symptom | Cause | | --- | --- | | `The redirect URI included is not valid` | The URI is missing from the application's redirect list | | `invalid_scope` | `openid`, `email`, or `profile` was not selected on the application | | `invalid_client` | Application ID or secret mismatch, or the application was created as non-confidential | | Sign-in fails only for self-managed users | This connector targets GitLab.com, use an OIDC enterprise connection | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | | [GitHub](/docs/connectors/github) | Add the other developer identity provider | | [Enterprise SSO](/docs/sso) | Point a generic OIDC connection at a self-managed instance | ### LinkedIn Source: https://www.authdog.com/docs/connectors/linkedin.md LinkedIn is the connector for B2B products: the identity carries a work email and a professional profile, and users treat it as the natural choice on a business sign-up page. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **LinkedIn**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the LinkedIn app 1. Open the [LinkedIn Developer Portal](https://www.linkedin.com/developers/apps) and select **Create app**. 2. Fill in the app name, the **LinkedIn Page** it belongs to, and a logo. 3. Verify the app from the **Settings** tab, an admin of the associated LinkedIn Page must approve it. Nothing works until this is done. 4. Open the **Products** tab and request **Sign In with LinkedIn using OpenID Connect**. 5. Open the **Auth** tab, and under **Authorized redirect URLs for your app**, paste the redirect URI from Authdog. 6. Copy the **Client ID** and **Client Secret** from the same tab. > **Caution** > > The company page association and its admin verification are the step teams > forget. Without them the product request stays pending and every sign-in > attempt returns `unauthorized_scope_error`. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | From the **Auth** tab | | **Client Secret** | From the **Auth** tab | Save, then toggle the connection **active**. ## What LinkedIn returns Authdog requests `r_emailaddress r_liteprofile` and reads the profile from LinkedIn's v2 API, with the email fetched from a second endpoint, LinkedIn splits identity across `/v2/me` and `/v2/emailAddress`. Two limits are worth knowing before you build on this data: - The profile is deliberately thin. Name, identifier, and profile picture; no headline, company, or position without additional products and review. - The email is the address on the LinkedIn account, which for many users is personal rather than corporate. Do not treat a LinkedIn sign-in as proof of employment at a given company. LinkedIn periodically revises its API products and scope names. If sign-in breaks after working for months, check the **Products** tab first, a product may have been deprecated or need re-requesting. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select **Continue with LinkedIn** and authorize. 3. Confirm the user appears under **Users** with an email address attached. ## Troubleshooting | Symptom | Cause | | --- | --- | | `unauthorized_scope_error` | The Sign In product is not approved on the app | | `The redirect_uri does not match the registered value` | The URI is missing from **Authorized redirect URLs** | | App stuck unverified | The associated LinkedIn Page admin has not completed the verification link | | No email returned | The email endpoint call was refused, confirm the requested products cover email | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | | [Enterprise SSO](/docs/sso) | Sign in employees through their employer's identity provider instead | ### Discord Source: https://www.authdog.com/docs/connectors/discord.md Discord is the connector for products built around a community, game tooling, bots, creator platforms. The account maps directly to the server your users already talk in. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Discord**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the Discord application 1. Open the [Discord Developer Portal](https://discord.com/developers/applications) and select **New Application**. 2. Name it and accept the terms. 3. Open **OAuth2** in the sidebar. 4. Under **Redirects**, add the redirect URI from Authdog and save. Discord accepts several, so all your environments can share one application. 5. Copy the **Client ID**, then select **Reset Secret** to reveal and copy the **Client Secret**. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | From the **OAuth2** page | | **Client Secret** | Revealed under **Client Secret** | Save, then toggle the connection **active**. ## What Discord returns Authdog requests `identify email` and reads the profile from `https://discord.com/api/users/@me`: the snowflake ID, username, global display name, avatar, and email with its verified flag. Usernames are mutable and, since Discord's move to unique handles, no longer carry a discriminator. Never key anything on the username, the snowflake ID is the stable identifier, and it is what Authdog links on. Unverified Discord accounts can still authorize. If email verification matters to you, check the verified flag rather than assuming the address is confirmed. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select **Continue with Discord** and authorize. 3. Confirm the user appears under **Users** with a Discord identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `Invalid OAuth2 redirect_uri` | The URI is missing from **Redirects**, or was added but not saved | | `invalid_client` | The client secret was reset after being copied into Authdog | | Missing email | The `email` scope was stripped by a custom authorization link | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | | [Twitch](/docs/connectors/twitch) | Add the other community and gaming identity | ### Slack Source: https://www.authdog.com/docs/connectors/slack.md Slack signs users in with the workspace account they already have open all day. It suits internal tools and B2B products whose users live in Slack. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Slack**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the Slack app 1. Open [Slack API apps](https://api.slack.com/apps) and select **Create New App**, then **From scratch**. 2. Name it and pick a development workspace. 3. Open **OAuth & Permissions**. 4. Under **Redirect URLs**, add the redirect URI from Authdog and save. 5. Under **User Token Scopes**, add `users.profile:read`. 6. Install the app to your workspace. 7. Open **Basic Information > App Credentials** and copy the **Client ID** and **Client Secret**. > **Note** > > Add the scope under **User Token Scopes**, not Bot Token Scopes. Sign-in acts > as the person authorizing, not as a bot user. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | From **Basic Information** | | **Client Secret** | From **Basic Information** | Save, then toggle the connection **active**. ## What Slack returns Authdog requests `users.profile:read` and reads the profile from `https://slack.com/api/users.profile.get`, display name, real name, avatar, and the email on the workspace profile. Slack identity is workspace-scoped. The same person in two workspaces is two different users to Slack, and to Authdog. If your product is per-workspace, that is exactly what you want; if it is per-person, link on the email address instead. Workspaces controlled by an admin may restrict app installation, in which case an admin has to approve your app before anyone in that workspace can sign in. Plan for that on any distribution beyond your own workspace. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select **Continue with Slack** and authorize. 3. Confirm the user appears under **Users** with the workspace email attached. ## Troubleshooting | Symptom | Cause | | --- | --- | | `bad_redirect_uri` | The URI is missing from **Redirect URLs** | | `invalid_scope` | The scope was added as a bot scope instead of a user scope | | `missing_scope` on the profile call | The app was installed before `users.profile:read` was added, reinstall it | | App requires approval | The target workspace restricts app installation | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | | [Enterprise SSO](/docs/sso) | Use the customer's identity provider for workforce sign-in | ### X (Twitter) Source: https://www.authdog.com/docs/connectors/twitter.md X (still `twitter-oauth20` in the catalog and in the API hostnames) signs users in with their X account. It suits consumer and creator products where the handle is part of the user's identity in your product. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Twitter (X)**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the X app 1. Open the [X Developer Portal](https://developer.x.com/en/portal/dashboard) and create a project and an app. 2. In the app's **Settings**, find **User authentication settings** and select **Set up**. 3. Enable **OAuth 2.0**. 4. Set the **Type of App** to **Web App, Automated App or Bot**: a confidential client, which is what issues a client secret. 5. Under **Callback URI / Redirect URL**, paste the redirect URI from Authdog. 6. Fill in the website URL, and the terms and privacy URLs if prompted. 7. Save, then copy the **Client ID** and **Client Secret** from the **Keys and tokens** tab. > **Caution** > > Use the OAuth **2.0** Client ID and Client Secret. The API Key and API Secret on > the same page belong to OAuth 1.0a and will not work with this connector. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | OAuth 2.0 Client ID | | **Client Secret** | OAuth 2.0 Client Secret | Save, then toggle the connection **active**. ## What X returns Authdog requests `users.read tweet.read offline.access` and reads the profile from `https://api.twitter.com/2/users/me`, the numeric ID, name, and username. X does not release email addresses through OAuth 2.0. Every user who arrives through this connector has no email on their identity, so treat it as a secondary sign-in method: pair it with a connector or method that does provide one, or collect an address after the first sign-in. Handles change. Link on the numeric ID, which does not. Access is also tier-dependent, the free tier's rate limits are low enough to matter on a busy sign-in page. Check your project's access level before launching. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the X button and authorize. 3. Confirm the user appears under **Users**, and that your application's post-sign-up path handles a user with no email. ## Troubleshooting | Symptom | Cause | | --- | --- | | `Something went wrong` on the authorize screen | The callback URL does not match, or user authentication settings were never saved | | `invalid_client` | OAuth 1.0a keys used instead of the OAuth 2.0 client credentials | | `unauthorized_client` | The app type is set to a public client, which has no secret | | Rate limit errors | The project's access tier is too low for your sign-in volume | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | | [Users](/docs/users) | Collect an email after a first sign-in | ### TikTok Source: https://www.authdog.com/docs/connectors/tiktok.md TikTok signs users in with their TikTok account. It fits creator tools and consumer apps whose audience arrives from TikTok in the first place. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **TikTok**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the TikTok app 1. Open the [TikTok for Developers portal](https://developers.tiktok.com/apps) and register an app. 2. Complete the app details, name, icon, category, description, and the terms and privacy URLs. TikTok checks these during review. 3. Add the **Login Kit** product. 4. Under **Redirect URI**, paste the redirect URI from Authdog. 5. Request the `user.info.basic` scope. 6. Submit the app for review, and wait for approval. 7. Copy the **Client Key** and **Client Secret**. > **Caution** > > TikTok gates production access behind review. Before approval, only accounts > you have added as testers can sign in. Budget for the review turnaround in your > launch plan, it is the longest lead time of any connector here. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The TikTok **Client Key** | | **Client Secret** | The TikTok **Client Secret** | Save, then toggle the connection **active**. ## What TikTok returns Authdog requests `user.info.basic` and reads the profile from `https://open.tiktokapis.com/v2/user/info/`, an open ID, display name, and avatar. No email address is available at this scope, and TikTok does not offer an email scope for Login Kit. Users arriving through TikTok have a display name and an identifier only, so pair this connector with one that provides a contact address, or ask for an email after sign-up. TikTok's identifiers are app-scoped: the same person authorizing two of your apps produces two different open IDs. ## Test it 1. Add your test account under the app's tester list if the app is still in review. 2. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 3. Select the TikTok button and authorize. 4. Confirm the user appears under **Users**, with no email, expected for this connector. ## Troubleshooting | Symptom | Cause | | --- | --- | | `redirect_uri` error before the consent screen | The URI is not registered on the app, or the app is not approved | | `invalid_client_key` | The client key belongs to a different app or environment | | Only some accounts can sign in | The app is pre-review and limited to listed testers | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | | [Users](/docs/users) | Collect an email after a first sign-in | ### Spotify Source: https://www.authdog.com/docs/connectors/spotify.md Spotify signs users in with their Spotify account. It is the natural connector for music tooling, playlist apps, and anything that reads a listener's library. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Spotify**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the Spotify app 1. Open the [Spotify Developer Dashboard](https://developer.spotify.com/dashboard) and select **Create app**. 2. Fill in the app name and description. 3. Under **Redirect URIs**, add the redirect URI from Authdog. Several are allowed, so all your environments can share one app. 4. Under **Which API/SDKs are you planning to use**, select **Web API**. 5. Save, then open **Settings** and copy the **Client ID**. Select **View client secret** to reveal the secret. > **Note** > > New apps start in **development mode**, which admits at most 25 users, each > added by email in **User Management**. Request an extension quota before you > open sign-in to the public. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | From the app's **Settings** | | **Client Secret** | Revealed under **View client secret** | Save, then toggle the connection **active**. ## What Spotify returns Authdog requests `user-read-private user-read-email` and reads the profile from `https://api.spotify.com/v1/me`, the Spotify user ID, display name, email, country, avatar, and subscription product level. The email is the address on the Spotify account. Spotify does not indicate whether it is verified, so do not treat it as a verified address if that distinction matters to your product. ## Test it 1. Add your test account under **User Management** if the app is still in development mode. 2. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 3. Select the Spotify button and authorize. 4. Confirm the user appears under **Users** with the account email attached. ## Troubleshooting | Symptom | Cause | | --- | --- | | `INVALID_CLIENT: Invalid redirect URI` | The URI is missing from the app's redirect list | | `User not registered in the Developer Dashboard` | The app is in development mode and the account is not listed | | `invalid_client` | Client ID and secret come from different apps | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | | [Twitch](/docs/connectors/twitch) | Add another media and creator identity | ### Twitch Source: https://www.authdog.com/docs/connectors/twitch.md Twitch signs users in with their Twitch account, the right connector for streaming tools, overlays, and community products where the channel is the identity. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Twitch**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Register the Twitch application 1. Open the [Twitch Developer Console](https://dev.twitch.tv/console/apps) and select **Register Your Application**. Two-factor authentication must be enabled on the account first. 2. Name the application, names are globally unique on Twitch. 3. Under **OAuth Redirect URLs**, add the redirect URI from Authdog. Several are allowed. 4. Pick a category and create the application. 5. Open **Manage** and copy the **Client ID**, then select **New Secret** and copy the value. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | From the application's **Manage** page | | **Client Secret** | Shown once when generated | Save, then toggle the connection **active**. ## What Twitch returns Authdog requests `user:read:email` and reads the profile from `https://api.twitch.tv/helix/users`, the numeric user ID, login name, display name, avatar, and the account's email address. Login names change when a streamer rebrands; the numeric ID does not, and that is what Authdog links on. Store the display name for presentation, never as a key. Twitch's Helix API requires the client ID on every profile request alongside the access token, which Authdog handles as part of the exchange. A client secret regenerated on Twitch invalidates the one stored in Authdog immediately, update the connection in the same sitting. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the Twitch button and authorize. 3. Confirm the user appears under **Users** with an email address attached. ## Troubleshooting | Symptom | Cause | | --- | --- | | `redirect_uri does not match registered URI` | The URI is missing from **OAuth Redirect URLs** | | `invalid client secret` | The secret was regenerated after being copied into Authdog | | Missing email on the profile | The `user:read:email` scope was not granted | | Cannot register the application | Two-factor authentication is not enabled on the Twitch account | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | | [Discord](/docs/connectors/discord) | Add the community identity most Twitch audiences also use | ### Amazon Source: https://www.authdog.com/docs/connectors/amazon.md Login with Amazon signs users in with their Amazon consumer account, a large audience for retail, media, and device-adjacent products. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Amazon**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the security profile 1. Open the [Login with Amazon console](https://developer.amazon.com/loginwithamazon/console/site/lwa/overview.html) and create a **Security Profile**. 2. Fill in the name, description, and privacy policy URL. 3. Under **Web Settings**, add your site to **Allowed Origins**. 4. Add the redirect URI from Authdog under **Allowed Return URLs**. 5. Copy the **Client ID** and **Client Secret**. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The client identifier from the provider | | **Client Secret** | The client secret from the provider | Save, then toggle the connection **active**. ## What Amazon returns Authdog requests the `profile profile:user_id profile:email` scopes and reads the profile from `https://api.amazon.com/user/profile`. The profile endpoint returns the Amazon user id, name, email, and postal code where available. The user id is scoped to the security profile, so the same shopper gets different ids across two of your applications. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the Amazon button and complete the flow. 3. Confirm the user appears under **Users** in the console with a Amazon identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `invalid_client` | Client credentials belong to another security profile | | `invalid_scope` | The security profile does not permit the email scope | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | ### Asignio Source: https://www.authdog.com/docs/connectors/asignio.md Asignio authenticates users with a biometric signature gesture rather than a password, a passwordless option for products with an accessibility or fraud-reduction requirement. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Asignio**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. Create an OIDC client in your Asignio tenant. 2. Add the redirect URI from Authdog to the client's redirect URIs. 3. Copy the **Client ID** and **Client Secret**. 4. Note the tenant host to use as the Domain URI. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The client identifier from the provider | | **Client Secret** | The client secret from the provider | | **Domain URI** | Your Asignio tenant host, without a scheme | Save, then toggle the connection **active**. > **Caution** > > Authdog substitutes the Domain URI into this provider's endpoints for > AWS Cognito, Salesforce Community, Shopify, Klarna, and Keycloak. This > connector accepts the field in the console but is not covered by that > substitution on both legs of the flow, so verify it end to end in a > development environment before you rely on it in production. ## What Asignio returns Authdog requests the `openid profile email` scopes and reads the profile from ``https:///userinfo``. The tenant's userinfo endpoint returns `sub`, name, email, and picture claims for the authenticated user. Enter the Domain URI as a bare host, Authdog prefixes `https://` for this connector. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the Asignio button and complete the flow. 3. Confirm the user appears under **Users** in the console with a Asignio identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `invalid_client` | Credentials issued by a different tenant | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | ### AWS Cognito Source: https://www.authdog.com/docs/connectors/aws-cognito.md The AWS connector signs users in against an Amazon Cognito user pool. It is the bridge you want when an existing product already stores its users in Cognito and you are moving the front door to Authdog without migrating the directory on day one. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **AWS**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the app client 1. Open the [Amazon Cognito console](https://console.aws.amazon.com/cognito/) and select the user pool. 2. Under **App integration**, note the **Cognito domain**: that is the Domain URI Authdog needs. 3. Create an **App client** of type *confidential client*, so it is issued a client secret. 4. Under **Hosted UI settings**, add Authdog's redirect URI as an **Allowed callback URL**. 5. Enable the **Authorization code grant** flow. 6. Select the OpenID Connect scopes `openid`, `email`, `phone`, and `profile`. 7. Save, then copy the **Client ID** and **Client secret**. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The client identifier from the provider | | **Client Secret** | The client secret from the provider | | **Domain URI** | Your user pool domain, e.g. `https://acme.auth.us-east-2.amazoncognito.com` | Save, then toggle the connection **active**. ## What AWS Cognito returns Authdog requests the `openid email phone aws.cognito.signin.user.admin profile` scopes and reads the profile from the ID token the provider issues. Profile claims come from the ID token Cognito issues, `sub`, email and its verified flag, and any custom attributes you mapped into the token. The `aws.cognito.signin.user.admin` scope is part of the request, so the pool must allow it on the app client. If your pool does not expose it, sign-in fails at the authorize step rather than the exchange. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the AWS Cognito button and complete the flow. 3. Confirm the user appears under **Users** in the console with a AWS Cognito identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `invalid_client` | The app client was created as a public client with no secret | | `invalid_scope` | The app client does not have all the requested scopes enabled | | Domain not found | The Cognito domain is missing, or the region in the host is wrong | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | | [Enterprise SSO](/docs/sso) | Connect a workforce IdP instead of a user pool | ### Badge Source: https://www.authdog.com/docs/connectors/badge.md Badge replaces stored credentials with a device-independent key derived at each authentication, so there is no secret at rest to steal. It suits products where credential-database exposure is the threat you are designing against. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Badge**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. Create an OIDC application in your Badge tenant. 2. Add the redirect URI from Authdog to the application. 3. Copy the **Client ID** and **Client Secret**. 4. Note the tenant host to use as the Domain URI. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The client identifier from the provider | | **Client Secret** | The client secret from the provider | | **Domain URI** | Your Badge tenant host | Save, then toggle the connection **active**. > **Caution** > > Authdog substitutes the Domain URI into this provider's endpoints for > AWS Cognito, Salesforce Community, Shopify, Klarna, and Keycloak. This > connector accepts the field in the console but is not covered by that > substitution on both legs of the flow, so verify it end to end in a > development environment before you rely on it in production. ## What Badge returns Authdog requests the `openid profile email` scopes and reads the profile from ``https:///userinfo``. The tenant's userinfo endpoint returns the standard OpenID Connect identity claims. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the Badge button and complete the flow. 3. Confirm the user appears under **Users** in the console with a Badge identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `invalid_client` | Credentials issued by a different tenant | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | ### Baidu Source: https://www.authdog.com/docs/connectors/baidu.md Baidu signs users in with their Baidu account, one of the broadest consumer identities in mainland China. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Baidu**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. Open the [Baidu developer console](https://developer.baidu.com/) and register an application. 2. Set the authorization callback to the redirect URI from Authdog. 3. Copy the **API Key** and **Secret Key**. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The Baidu **API Key** | | **Client Secret** | The Baidu **Secret Key** | Save, then toggle the connection **active**. ## What Baidu returns Authdog requests the `basic` scope and reads the profile from `https://openapi.baidu.com/rest/2.0/passport/users/getInfo`. The passport endpoint returns the user id, username, and portrait. No email address is released at the `basic` scope. This connector does not return an email address, so users who arrive through it have a display name and an identifier only. Pair it with a method that collects one, or ask for an email after the first sign-in. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the Baidu button and complete the flow. 3. Confirm the user appears under **Users** in the console with a Baidu identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `redirect_uri_mismatch` | The callback registered on the app differs | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | ### Basecamp Source: https://www.authdog.com/docs/connectors/basecamp.md Basecamp (37signals' Launchpad identity) signs users in with the account they use for Basecamp projects. It fits integrations and reporting tools built around those projects. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Basecamp**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. Open [37signals integrations](https://integrate.37signals.com/) and register an application. 2. Fill in the name, company, and website. 3. Under **Redirect URI**, paste the redirect URI from Authdog. 4. Save, then copy the **Client ID** and **Client Secret**. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The client identifier from the provider | | **Client Secret** | The client secret from the provider | Save, then toggle the connection **active**. ## What Basecamp returns Authdog requests the provider's default scopes and reads the profile from `https://launchpad.37signals.com/authorization.json`. The authorization endpoint returns the identity (id, name, email) plus the list of accounts (Basecamp installations) the user can reach. A user with access to several Basecamp accounts still has one identity. Pick the account explicitly in your product rather than assuming the first one. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the Basecamp button and complete the flow. 3. Confirm the user appears under **Users** in the console with a Basecamp identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `invalid_request` | The redirect URI does not match the registered value | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | ### Beyond Identity Source: https://www.authdog.com/docs/connectors/beyondidentity.md Beyond Identity signs users in with a passkey bound to the user's device. Authdog talks to the OIDC endpoints on the application you create in a Beyond Identity realm. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Beyond Identity**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. In the Beyond Identity console, open the tenant, realm, and create an application that uses the External Protocol (OIDC). 2. Set the redirect URI to the value copied from Authdog. 3. Request the scopes `openid`, `profile`, and `email`. 4. Copy the **Client ID** and **Client secret**. 5. Copy the application base URL. It looks like `https://auth-us.beyondidentity.com/v1/tenants/{tenant}/realms/{realm}/applications/{app}` and has no trailing slash. That base is the Domain URI. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The client identifier from the Beyond Identity application | | **Client Secret** | The client secret from the Beyond Identity application | | **Domain URI** | The application base URL, with `/authorize` removed | Save, then toggle the connection **active**. ## What Beyond Identity returns Authdog requests the `openid profile email` scopes and reads the profile from `/userinfo`. The userinfo endpoint returns `sub`, name, and email claims for the authenticated user. Paste the application base as the Domain URI. Do not include `/authorize`, `/token`, or `/userinfo`. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the Beyond Identity button and complete the flow. 3. Confirm the user appears under **Users** in the console with a Beyond Identity identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `invalid_client` | Client ID or secret from a different application | | Redirect or callback URL error | The URI registered with Beyond Identity does not match Authdog's exactly | | 404 on authorize | The Domain URI includes `/authorize` or points at an older fixed host | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | ### BitBadges Source: https://www.authdog.com/docs/connectors/bitbadges.md BitBadges signs users in with their blockchain-backed BitBadges identity, where on-chain badges represent membership and credentials. It fits products that gate access on those holdings. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **BitBadges**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. Register an OIDC client with BitBadges. 2. Add the redirect URI from Authdog to the client. 3. Copy the **Client ID** and **Client Secret**. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The client identifier from the provider | | **Client Secret** | The client secret from the provider | | **Domain URI** | The BitBadges OIDC host | Save, then toggle the connection **active**. > **Caution** > > Authdog substitutes the Domain URI into this provider's endpoints for > AWS Cognito, Salesforce Community, Shopify, Klarna, and Keycloak. This > connector accepts the field in the console but is not covered by that > substitution on both legs of the flow, so verify it end to end in a > development environment before you rely on it in production. ## What BitBadges returns Authdog requests the `openid profile email` scopes and reads the profile from ``https:///userinfo``. The userinfo endpoint returns the identity claims BitBadges releases for the connected account. Badge holdings change on chain. Re-check them at the moment of authorization rather than trusting a claim captured at sign-in. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the BitBadges button and complete the flow. 3. Confirm the user appears under **Users** in the console with a BitBadges identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `invalid_client` | Client credentials mismatch | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | ### Bitbucket Source: https://www.authdog.com/docs/connectors/bitbucket.md Bitbucket signs users in with their Atlassian-hosted Bitbucket account. It belongs on developer tools whose users host code there rather than on GitHub or GitLab. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Bitbucket**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the OAuth consumer 1. Open your workspace settings on [Bitbucket](https://bitbucket.org/) and select **OAuth consumers**. 2. Select **Add consumer**. 3. Name the consumer and set the **Callback URL** to the redirect URI from Authdog. 4. Grant the **Account: Read** permission, enough to read the profile. 5. Save, then copy the **Key** and **Secret**. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The client identifier from the provider | | **Client Secret** | The client secret from the provider | Save, then toggle the connection **active**. ## What Bitbucket returns Authdog requests the provider's default scopes and reads the profile from `https://bitbucket.org/api/2.0/user`. The user endpoint returns the account UUID, username, display name, and avatar. Authdog links on the UUID, which survives username changes. Bitbucket keeps email addresses on a separate endpoint and hides them unless the account permits it, so expect users with no email on their Authdog record. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the Bitbucket button and complete the flow. 3. Confirm the user appears under **Users** in the console with a Bitbucket identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `invalid_grant` | The consumer's callback URL differs from the one used at sign-in | | Empty email | The Bitbucket account has no public email, expected behaviour | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | | [GitHub](/docs/connectors/github) | Offer the developer identity most users already have | ### 1Kosmos BlockID Source: https://www.authdog.com/docs/connectors/blockid.md 1Kosmos BlockID is a passwordless identity-proofing platform: it verifies a real person with documents and biometrics, then authenticates them without a password. It belongs on flows where knowing who the user actually is matters more than convenience. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **1Kosmos BlockID**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. Create an OIDC application in your 1Kosmos BlockID tenant. 2. Add the redirect URI from Authdog to the application's redirect URIs. 3. Copy the issued **Client ID** and **Client Secret**. 4. Note your tenant host, that is the Domain URI. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The client identifier from the provider | | **Client Secret** | The client secret from the provider | | **Domain URI** | Your 1Kosmos tenant host | Save, then toggle the connection **active**. > **Caution** > > Authdog substitutes the Domain URI into this provider's endpoints for > AWS Cognito, Salesforce Community, Shopify, Klarna, and Keycloak. This > connector accepts the field in the console but is not covered by that > substitution on both legs of the flow, so verify it end to end in a > development environment before you rely on it in production. ## What 1Kosmos BlockID returns Authdog requests the `openid profile email` scopes and reads the profile from ``https:///userinfo``. The tenant's userinfo endpoint returns the standard OpenID Connect claims for the verified identity. Proofing happens in BlockID, before Authdog sees the user. Treat the resulting session as high assurance only if the tenant's proofing policy actually requires it. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the 1Kosmos BlockID button and complete the flow. 3. Confirm the user appears under **Users** in the console with a 1Kosmos BlockID identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `invalid_client` | The application belongs to a different tenant than the Domain URI | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | ### Box Source: https://www.authdog.com/docs/connectors/box.md Box signs users in with their Box account. It is an enterprise content platform, so this connector usually appears on internal tools and document-centric B2B products. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Box**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. Open the [Box Developer Console](https://app.box.com/developers/console) and create a **Custom App**. 2. Choose **User Authentication (OAuth 2.0)** as the authentication method. 3. Under **OAuth 2.0 Redirect URI**, paste the redirect URI from Authdog. 4. Under **Application Scopes**, keep **Read all files and folders** or narrower, sign-in only needs the user record. 5. Save changes, then copy the **Client ID** and **Client Secret**. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The client identifier from the provider | | **Client Secret** | The client secret from the provider | Save, then toggle the connection **active**. ## What Box returns Authdog requests the provider's default scopes and reads the profile from `https://api.box.com/2.0/users/me`. The users endpoint returns the Box user ID, name, login (which is the email address), and avatar. Apps in a Box enterprise may need admin authorization before anyone can use them. If sign-in fails for everyone in one company but works elsewhere, that approval is the first thing to check. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the Box button and complete the flow. 3. Confirm the user appears under **Users** in the console with a Box identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `unauthorized_client` | The app has not been authorized by the enterprise admin | | `invalid_grant` | Redirect URI mismatch, or the authorization code was already used | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | ### Coinbase Source: https://www.authdog.com/docs/connectors/coinbase.md Coinbase signs users in with their Coinbase account, a practical identity for crypto-adjacent products, and one that comes with a verified email. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Coinbase**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. Open [Coinbase OAuth applications](https://www.coinbase.com/settings/api) and create a new OAuth2 application. 2. Name it and set the **Redirect URI** to the redirect URI from Authdog. 3. Request the `wallet:user:read` and `wallet:user:email` permissions. 4. Save, then copy the **Client ID** and **Client Secret**. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The client identifier from the provider | | **Client Secret** | The client secret from the provider | Save, then toggle the connection **active**. ## What Coinbase returns Authdog requests the `wallet:user:read wallet:user:email` scopes and reads the profile from `https://api.coinbase.com/v2/user`. The user endpoint returns the Coinbase user id, name, avatar, and (with the email permission) the account email address. Ask only for the user-scoped permissions. Wallet or transaction scopes turn a sign-in button into an authorization your users will refuse. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the Coinbase button and complete the flow. 3. Confirm the user appears under **Users** in the console with a Coinbase identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `invalid_scope` | A requested permission is not enabled on the application | | `invalid_request` | The redirect URI does not match the registered value | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | ### d ACCOUNT Source: https://www.authdog.com/docs/connectors/daccount.md d ACCOUNT is NTT Docomo's consumer identity in Japan, used across Docomo services and by partners that serve its subscriber base. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **dアカウント (d ACCOUNT)**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. Apply for d ACCOUNT OAuth credentials through NTT Docomo's partner programme. 2. Provide Docomo with the redirect URI from Authdog for allow-listing. 3. Copy the issued **Client ID** and **Client Secret**. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The client identifier from the provider | | **Client Secret** | The client secret from the provider | Save, then toggle the connection **active**. ## What d ACCOUNT returns Authdog requests the `openid profile email` scopes and reads the profile from `https://fw-ss.gw.docomo.ne.jp/daccount/userinfo`. The userinfo endpoint returns the OpenID Connect claims Docomo releases for the account, including the `sub` identifier and, where permitted, email. Credentials are issued through a partner agreement rather than self-service registration, so plan the lead time into your rollout. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the d ACCOUNT button and complete the flow. 3. Confirm the user appears under **Users** in the console with a d ACCOUNT identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | Authorization refused | The redirect URI is not allow-listed on the issued credentials | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | ### Descope Source: https://www.authdog.com/docs/connectors/descope.md Descope signs users in through a federated OpenID Connect application. Authdog uses the project's OIDC endpoints. The client ID is the Descope project ID. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Descope**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. In the Descope console, open the project and create an OIDC application that accepts a client secret (`client_secret_post`). 2. Set the redirect URI to the value copied from Authdog. 3. Grant the scopes `openid`, `profile`, and `email`. 4. Copy the **Project ID** (this is the client ID) and the **client secret**. 5. Note the API host for the project. The default is `https://api.descope.com`. A regional project uses a host such as `https://api.euc1.descope.com`. That host is the Domain URI. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The Descope project ID | | **Client Secret** | The client secret from the OIDC application | | **Domain URI** | The API host, e.g. `https://api.descope.com` | Save, then toggle the connection **active**. ## What Descope returns Authdog requests the `openid profile email` scopes and reads the profile from `/oauth2/v1/userinfo`. The userinfo endpoint returns `sub`, name, and email claims for the authenticated user. Paste the API host only. Do not include `/oauth2/v1/authorize`. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the Descope button and complete the flow. 3. Confirm the user appears under **Users** in the console with a Descope identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `invalid_client` | The client ID is not the project ID, or the secret belongs to another application | | Redirect or callback URL error | The URI registered with Descope does not match Authdog's exactly | | 404 on authorize | The Domain URI includes a path, or the region host is wrong | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | ### DigitalOcean Source: https://www.authdog.com/docs/connectors/digitalocean.md DigitalOcean signs users in with their DigitalOcean account, a natural fit for infrastructure tooling, deployment dashboards, and anything that already talks to their API. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **DigitalOcean**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. Open the [DigitalOcean API applications page](https://cloud.digitalocean.com/account/api/applications). 2. Select **Create OAuth application** (or **Register an application**). 3. Fill in the application name, homepage, and description. 4. Under **Redirect URI**, paste the redirect URI from Authdog. 5. Create the application, then copy the **Client ID** and **Client Secret**. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The client identifier from the provider | | **Client Secret** | The client secret from the provider | Save, then toggle the connection **active**. ## What DigitalOcean returns Authdog requests the `read` scope and reads the profile from `https://api.digitalocean.com/v2/account`. The account endpoint returns the account UUID, email, email verification status, and droplet limits. Authdog links returning users on the account UUID. The `read` scope is enough for sign-in. Requesting `write` would let your token change the user's infrastructure, do not ask for it just to authenticate. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the DigitalOcean button and complete the flow. 3. Confirm the user appears under **Users** in the console with a DigitalOcean identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `invalid_client` | Client ID or secret belongs to a different application | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | ### Dock Web3 ID Source: https://www.authdog.com/docs/connectors/dock-web3id.md Dock Web3 ID signs users in with verifiable credentials they hold in a wallet, rather than an account on a provider's server. It is the connector for products built on decentralized identity. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Dock Web3 ID**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. Create an OIDC client in your Dock Web3 ID account. 2. Add the redirect URI from Authdog to the client. 3. Copy the **Client ID** and **Client Secret**. 4. Note the host to use as the Domain URI. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The client identifier from the provider | | **Client Secret** | The client secret from the provider | | **Domain URI** | Your Dock Web3 ID host | Save, then toggle the connection **active**. > **Caution** > > Authdog substitutes the Domain URI into this provider's endpoints for > AWS Cognito, Salesforce Community, Shopify, Klarna, and Keycloak. This > connector accepts the field in the console but is not covered by that > substitution on both legs of the flow, so verify it end to end in a > development environment before you rely on it in production. ## What Dock Web3 ID returns Authdog requests the `openid profile email` scopes and reads the profile from ``https:///userinfo``. The userinfo endpoint returns the claims released from the presented verifiable credential. The subject identifier is derived from the credential, so a user who re-issues credentials may present a different subject. Decide up front whether your linking strategy tolerates that. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the Dock Web3 ID button and complete the flow. 3. Confirm the user appears under **Users** in the console with a Dock Web3 ID identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | Presentation rejected | The requested credential type is not one the wallet holds | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | ### DocuSign Source: https://www.authdog.com/docs/connectors/docusign.md DocuSign signs users in with their DocuSign account, which is the usual identity for products that send or track envelopes on the user's behalf. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **DocuSign**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. Open the [DocuSign Developer Center apps page](https://developers.docusign.com/) and add an app in your account under **Apps and Keys**. 2. Choose an **Authorization Code Grant** app. 3. Under **Redirect URIs**, paste the redirect URI from Authdog. 4. Generate a secret key and copy it, along with the **Integration Key**. 5. Grant consent for the app in your account before the first sign-in. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The DocuSign **Integration Key** | | **Client Secret** | The generated **Secret Key** | Save, then toggle the connection **active**. ## What DocuSign returns Authdog requests the `signature openid` scopes and reads the profile from `https://account.docusign.com/oauth/userinfo`. The userinfo endpoint returns the user id, name, email, and the accounts the user belongs to, including which is the default. The `signature` scope is part of the request because DocuSign issues it alongside identity. Consent is required once per user or granted organization-wide by an administrator. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the DocuSign button and complete the flow. 3. Confirm the user appears under **Users** in the console with a DocuSign identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `consent_required` | The user or an admin has not granted consent for the integration key | | `invalid_client` | Demo credentials used against the production host | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | ### Dribbble Source: https://www.authdog.com/docs/connectors/dribbble.md Dribbble signs users in with their Dribbble account, which suits hiring products, portfolio tools, and design communities. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Dribbble**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. Open [Dribbble's developer applications page](https://dribbble.com/account/applications/new). 2. Register an application with a name, description, and website. 3. Under **Callback URL**, paste the redirect URI from Authdog. 4. Save, then copy the **Client ID** and **Client Secret**. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The client identifier from the provider | | **Client Secret** | The client secret from the provider | Save, then toggle the connection **active**. ## What Dribbble returns Authdog requests the `public` scope and reads the profile from `https://api.dribbble.com/v2/user`. The user endpoint returns the id, name, login, avatar, and (for the authenticated user) the account email. If the account withholds its email, users who arrive through it have a display name and an identifier only. Pair it with a method that collects one, or ask for an email after the first sign-in. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the Dribbble button and complete the flow. 3. Confirm the user appears under **Users** in the console with a Dribbble identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `invalid_grant` | The callback URL does not match the registered one | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | ### Dropbox Source: https://www.authdog.com/docs/connectors/dropbox.md Dropbox signs users in with their Dropbox account. It suits products that also read or write files there, where one authorization covers both sign-in and access. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Dropbox**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. Open the [Dropbox App Console](https://www.dropbox.com/developers/apps) and select **Create app**. 2. Choose **Scoped access**, then an access type, **App folder** is enough for sign-in. 3. Name the app and create it. 4. On the **Settings** tab, add the redirect URI from Authdog under **OAuth 2 Redirect URIs**. 5. On the **Permissions** tab, enable `account_info.read` and submit. 6. Copy the **App key** and **App secret** from **Settings**. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The Dropbox **App key** | | **Client Secret** | The Dropbox **App secret** | Save, then toggle the connection **active**. ## What Dropbox returns Authdog requests the `account_info.read` scope and reads the profile from `https://api.dropboxapi.com/2/users/get_current_account`. The account endpoint returns the account ID, display name, email and its verified flag, and the account type. Dropbox apps start in **Development** mode, capped at a small number of linked accounts. Apply for production status before opening sign-in to the public. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the Dropbox button and complete the flow. 3. Confirm the user appears under **Users** in the console with a Dropbox identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `missing_scope` | The permission was enabled after the app was authorized, reauthorize | | Development-mode cap reached | The app needs production approval from Dropbox | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | ### Duo Source: https://www.authdog.com/docs/connectors/duo.md Duo signs users in through a Generic SSO OpenID Connect application. Authdog uses the standard authorize, token, and userinfo endpoints on the issuer Duo gives that application. Use a Generic OIDC application. Duo Universal Prompt is a different protocol and is not this connector. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Duo**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. In the Duo Admin Panel, add a **Generic OIDC** SSO application. 2. Set the redirect URI to the value copied from Authdog. 3. Grant the scopes `openid`, `profile`, and `email`. 4. Copy the **Client ID** and **Client secret**. 5. Open the application's OIDC metadata and copy the `issuer` value. That issuer, without `/.well-known/openid-configuration`, is the Domain URI. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The client identifier from the Duo application | | **Client Secret** | The client secret from the Duo application | | **Domain URI** | The OIDC issuer, e.g. `https://sso-abc1def2.sso.duosecurity.com/oidc/DIXXXXXXXXXXXXXXXXXX` | Save, then toggle the connection **active**. ## What Duo returns Authdog requests the `openid profile email` scopes and reads the profile from `/userinfo`. The userinfo endpoint returns `sub`, name, and email claims for the authenticated user. Paste the issuer itself as the Domain URI, not the authorize URL and not the well-known metadata URL. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the Duo button and complete the flow. 3. Confirm the user appears under **Users** in the console with a Duo identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `invalid_client` | Client ID or secret from a different Duo application | | Redirect or callback URL error | The URI registered with Duo does not match Authdog's exactly | | 404 on authorize | The Domain URI is not the OIDC issuer | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | ### Dwolla Source: https://www.authdog.com/docs/connectors/dwolla.md Dwolla signs users in with their Dwolla account, a payments identity, most often used by products that also move money through the platform. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Dwolla**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. Open the [Dwolla Dashboard](https://dashboard.dwolla.com/applications) and open your application. 2. Add the redirect URI from Authdog to the **OAuth redirect URLs**. 3. Copy the **Key** and **Secret**. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The Dwolla application **Key** | | **Client Secret** | The Dwolla application **Secret** | Save, then toggle the connection **active**. ## What Dwolla returns Authdog requests the `accountinfofull` scope and reads the profile from `https://api.dwolla.com/users/me`. The endpoint returns the account identifier and the profile information the `accountinfofull` scope permits. Dwolla runs separate sandbox and production environments with separate credentials and hosts. This connector targets production. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the Dwolla button and complete the flow. 3. Confirm the user appears under **Users** in the console with a Dwolla identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `access_denied` | Sandbox credentials used against production | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | ### Exact Online Source: https://www.authdog.com/docs/connectors/exact-online.md Exact Online signs users in with their Exact business account. Exact runs region-specific hosts, so this connector takes a Domain URI naming the one your customers use. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Exact Online**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. Open the [Exact Online App Centre developer portal](https://apps.exactonline.com/) and register an app. 2. Under **Redirect URI**, paste the redirect URI from Authdog. 3. Save, then copy the **Client ID** and **Client Secret**. 4. Note the regional host your customers sign in on, `start.exactonline.nl`, `.be`, `.de`, `.co.uk`, and so on. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The client identifier from the provider | | **Client Secret** | The client secret from the provider | | **Domain URI** | Your regional Exact host, e.g. `start.exactonline.nl` | Save, then toggle the connection **active**. > **Caution** > > Authdog substitutes the Domain URI into this provider's endpoints for > AWS Cognito, Salesforce Community, Shopify, Klarna, and Keycloak. This > connector accepts the field in the console but is not covered by that > substitution on both legs of the flow, so verify it end to end in a > development environment before you rely on it in production. ## What Exact Online returns Authdog requests the `read` scope and reads the profile from ``https:///api/v1/current/Me``. The `current/Me` endpoint returns the user id, full name, email, and the division (administration) the user is working in. Enter the Domain URI as a bare host without a scheme, Authdog prefixes `https://` itself. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the Exact Online button and complete the flow. 3. Confirm the user appears under **Users** in the console with a Exact Online identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | Endpoint not found | The regional host is wrong for the account | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | ### Figma Source: https://www.authdog.com/docs/connectors/figma.md Figma signs users in with their Figma account, the connector for design tooling, plugin companions, and anything that reads files or comments. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Figma**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. Open [Figma's developer settings](https://www.figma.com/developers/apps) and create an app. 2. Name it and upload a logo. 3. Under **Callbacks**, add the redirect URI from Authdog. 4. Save, then copy the **Client ID** and **Client secret**. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The client identifier from the provider | | **Client Secret** | The client secret from the provider | Save, then toggle the connection **active**. ## What Figma returns Authdog requests the provider's default scopes and reads the profile from `https://api.figma.com/v1/me`. The `/v1/me` endpoint returns the user id, email, handle, and avatar. Figma returns the email on the account, and it is the address the person signs in to Figma with. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the Figma button and complete the flow. 3. Confirm the user appears under **Users** in the console with a Figma identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `invalid_request` | The callback is not registered on the Figma app | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | ### Fitbit Source: https://www.authdog.com/docs/connectors/fitbit.md Fitbit signs users in with their Fitbit account, the connector for wellness products that also read activity or sleep data. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Fitbit**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. Open [dev.fitbit.com](https://dev.fitbit.com/apps/new) and register an application. 2. Set the **OAuth 2.0 Application Type** to **Server**, which is a confidential client. 3. Under **Redirect URL**, paste the redirect URI from Authdog. 4. Select the `profile`, `settings`, and `social` scopes. 5. Save, then copy the **OAuth 2.0 Client ID** and **Client Secret**. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The client identifier from the provider | | **Client Secret** | The client secret from the provider | Save, then toggle the connection **active**. ## What Fitbit returns Authdog requests the `profile settings social` scopes and reads the profile from `https://api.fitbit.com/1/user/-/profile.json`. The profile endpoint returns the encoded user id, display name, avatar, timezone, and locale. Fitbit does not release an email address through this scope set. Users who arrive through it have a display name and an identifier only. Pair it with a method that collects one, or ask for an email after the first sign-in. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the Fitbit button and complete the flow. 3. Confirm the user appears under **Users** in the console with a Fitbit identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `invalid_client` | The application type is Client or Personal rather than Server | | `invalid_scope` | A requested scope is not enabled on the application | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | ### FreshBooks Source: https://www.authdog.com/docs/connectors/freshbooks.md FreshBooks signs users in with their FreshBooks account, for integrations built around invoicing and time tracking. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **FreshBooks**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. Open the [FreshBooks Developer portal](https://my.freshbooks.com/#/developer) and create an app. 2. Fill in the name and description. 3. Under **Redirect URI**, paste the redirect URI from Authdog. 4. Save, then copy the **Client ID** and **Client Secret**. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The client identifier from the provider | | **Client Secret** | The client secret from the provider | Save, then toggle the connection **active**. ## What FreshBooks returns Authdog requests the `user:profile` scope and reads the profile from `https://api.freshbooks.com/auth/api/v1/users/me`. The users endpoint returns the user id, email, name, and the businesses the account belongs to. A FreshBooks identity can span several businesses. Resolve which one the session applies to in your own product. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the FreshBooks button and complete the flow. 3. Confirm the user appears under **Users** in the console with a FreshBooks identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `invalid_client` | Client ID or secret mismatch | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | ### Hugging Face Source: https://www.authdog.com/docs/connectors/huggingface.md Hugging Face signs users in with the account they use for models, datasets, and Spaces, the obvious connector for ML tooling and anything that pulls artifacts from the Hub. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Hugging Face**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. Open [Connected Applications in your Hugging Face settings](https://huggingface.co/settings/connected-applications) and create an OAuth app, or add OAuth to a Space. 2. Name the application and add a description. 3. Under **Redirect URLs**, paste the redirect URI from Authdog. 4. Select the `openid`, `profile`, and `email` scopes. 5. Save, then copy the **Client ID** and **Client Secret**. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The client identifier from the provider | | **Client Secret** | The client secret from the provider | Save, then toggle the connection **active**. ## What Hugging Face returns Authdog requests the `openid profile email` scopes and reads the profile from `https://huggingface.co/api/whoami-v2`. The whoami endpoint returns the username, full name, avatar, email, and the organizations the user belongs to. Usernames on the Hub are mutable; the stable identifier is what Authdog links on. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the Hugging Face button and complete the flow. 3. Confirm the user appears under **Users** in the console with a Hugging Face identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `invalid_scope` | The app was created without the `email` scope | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | ### IBMid Source: https://www.authdog.com/docs/connectors/ibmid.md IBMid signs users in with the IBM account used across IBM Cloud and IBM's product estate. It fits enterprise tooling sold into IBM-centric organizations. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **IBMid**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. Request an OIDC client registration for IBMid through your IBM partner or account contact. 2. Provide the redirect URI from Authdog for allow-listing on the registration. 3. Copy the issued **Client ID** and **Client Secret**. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The client identifier from the provider | | **Client Secret** | The client secret from the provider | Save, then toggle the connection **active**. ## What IBMid returns Authdog requests the `openid profile email` scopes and reads the profile from `https://login.ibm.com/oidc/endpoint/default/userinfo`. The userinfo endpoint returns the standard OpenID Connect claims, `sub`, email, given and family name. IBMid registration is not self-service, so allow lead time before a launch that depends on it. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the IBMid button and complete the flow. 3. Confirm the user appears under **Users** in the console with a IBMid identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `invalid_redirect_uri` | The URI is not on the client registration | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | ### ID.me Source: https://www.authdog.com/docs/connectors/idme.md ID.me is an identity-verification provider: it does not just authenticate a person, it attests to a verified group, military, student, teacher, first responder. Products that gate discounts or benefits on those groups use it as both sign-in and proof. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **ID.me**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. Open the [ID.me developer portal](https://developers.id.me/) and create an application. 2. Fill in the name and the policies your product requires. 3. Under **Redirect URI**, paste the redirect URI from Authdog. 4. Copy the **Client ID** and **Client Secret**. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The client identifier from the provider | | **Client Secret** | The client secret from the provider | Save, then toggle the connection **active**. ## What ID.me returns Authdog requests the `openid profile email` scopes and reads the profile from `https://api.id.me/api/public/v3/userinfo`. The userinfo endpoint returns the `sub` identifier, email, and name, plus verification attributes for the groups the user has proven. Verification status is a claim about the person at a point in time. Re-check it rather than caching it forever if your entitlement depends on it. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the ID.me button and complete the flow. 3. Confirm the user appears under **Users** in the console with a ID.me identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `invalid_scope` | The application is not approved for the requested policy | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | | [ID.me Community](/docs/connectors/idme-community) | Request the community verification scope | ### ID.me Community Source: https://www.authdog.com/docs/connectors/idme-community.md The ID.me Community connector is the ID.me connector plus the `community` scope, which returns the verified communities a user belongs to rather than identity alone. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **ID.me Community**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. Set up the application as in the [ID.me connector](/docs/connectors/idme). 2. Request approval for the community policy your product gates on. 3. Add the redirect URI from Authdog to the application. 4. Copy the **Client ID** and **Client Secret**. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The client identifier from the provider | | **Client Secret** | The client secret from the provider | Save, then toggle the connection **active**. ## What ID.me Community returns Authdog requests the `openid profile email community` scopes and reads the profile from `https://api.id.me/api/public/v3/userinfo`. The response adds community affiliation to the standard claims, so your application can branch on the group a user has proven. Approval is per policy. An application approved for one community is not automatically approved for another. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the ID.me Community button and complete the flow. 3. Confirm the user appears under **Users** in the console with a ID.me Community identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `invalid_scope` | The community policy has not been approved for this application | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | | [ID.me](/docs/connectors/idme) | Use identity verification without community scope | ### IDPartner Source: https://www.authdog.com/docs/connectors/idpartner.md IDPartner lets users authenticate through their bank, using an identity a financial institution has already verified. It suits onboarding flows that need a real, KYC-grade identity without running the checks yourself. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **IDPartner**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. Register an application in the IDPartner dashboard. 2. Add the redirect URI from Authdog to its redirect URIs. 3. Copy the **Client ID** and **Client Secret**. 4. Note the environment host to use as the Domain URI. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The client identifier from the provider | | **Client Secret** | The client secret from the provider | | **Domain URI** | Your IDPartner environment host | Save, then toggle the connection **active**. > **Caution** > > Authdog substitutes the Domain URI into this provider's endpoints for > AWS Cognito, Salesforce Community, Shopify, Klarna, and Keycloak. This > connector accepts the field in the console but is not covered by that > substitution on both legs of the flow, so verify it end to end in a > development environment before you rely on it in production. ## What IDPartner returns Authdog requests the `openid profile email` scopes and reads the profile from ``https:///userinfo``. The userinfo endpoint returns bank-verified claims, name, email, and, depending on the participating institution, address and date of birth. Available claims vary by bank. Design your sign-up so a missing optional claim does not block the user. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the IDPartner button and complete the flow. 3. Confirm the user appears under **Users** in the console with a IDPartner identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | No matching bank | The user's institution does not participate in the IDPartner network | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | ### Indeed Source: https://www.authdog.com/docs/connectors/indeed.md Indeed signs users in with their Indeed account, which fits recruiting products and job-board integrations where the candidate identity already lives there. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Indeed**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. Register an application in the [Indeed developer portal](https://developer.indeed.com/). 2. Add the redirect URI from Authdog to the allowed redirect URLs. 3. Request the OpenID Connect scopes for profile and email. 4. Copy the **Client ID** and **Client Secret**. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The client identifier from the provider | | **Client Secret** | The client secret from the provider | Save, then toggle the connection **active**. ## What Indeed returns Authdog requests the `openid profile email` scopes and reads the profile from `https://apis.indeed.com/v2/me`. The `/v2/me` endpoint returns the `sub` identifier, email, and name for the signed-in account. Indeed reviews applications and grants scopes per use case, so confirm which claims your registration is approved for before building on them. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the Indeed button and complete the flow. 3. Confirm the user appears under **Users** in the console with a Indeed identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `invalid_scope` | The registration is not approved for the requested scopes | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | ### Intuit Source: https://www.authdog.com/docs/connectors/intuit.md Intuit signs users in with the account they use for QuickBooks, the identity behind most accounting integrations. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Intuit**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. Open the [Intuit Developer portal](https://developer.intuit.com/app/developer/dashboard) and create an app. 2. Select the **Sign in with Intuit** scopes: `openid`, `email`, and `profile`. 3. Under **Keys & credentials**, add the redirect URI from Authdog to the **Redirect URIs** list. 4. Copy the **Client ID** and **Client Secret** for the environment you are targeting. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The client identifier from the provider | | **Client Secret** | The client secret from the provider | Save, then toggle the connection **active**. ## What Intuit returns Authdog requests the `openid email profile` scopes and reads the profile from `https://accounts.platform.intuit.com/v1/openid_connect/userinfo`. The userinfo endpoint returns the `sub` identifier, email and its verified flag, and the given and family names. Intuit issues separate credentials for its development and production keys. Match them to the Authdog environment you are configuring. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the Intuit button and complete the flow. 3. Confirm the user appears under **Users** in the console with a Intuit identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `invalid_client` | Development keys used against production, or the reverse | | `invalid_scope` | The app is not enabled for the Sign in with Intuit scopes | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | ### Kakao Source: https://www.authdog.com/docs/connectors/kakao.md Kakao signs users in with their Kakao account, the default consumer identity in South Korea, alongside NAVER. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Kakao**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. Open [Kakao Developers](https://developers.kakao.com/console/app) and create an application. 2. Under **App Keys**, note the **REST API key**: that is the client ID. 3. Enable **Kakao Login** and add the redirect URI from Authdog under **Redirect URI**. 4. Under **Consent Items**, enable the profile and email items. Email requires business verification. 5. Under **Security**, generate a **Client Secret** and set it to *enabled*. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The Kakao **REST API key** | | **Client Secret** | The generated **Client Secret**, with its status set to enabled | Save, then toggle the connection **active**. ## What Kakao returns Authdog requests the `profile account_email` scopes and reads the profile from `https://kapi.kakao.com/v2/user/me`. The user endpoint returns the Kakao id, nickname, profile image, and (when the consent item is approved) the account email with its verified flag. Email is an optional consent item. Until Kakao approves it for your app, expect users with no email address. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the Kakao button and complete the flow. 3. Confirm the user appears under **Users** in the console with a Kakao identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `KOE006` invalid redirect | The redirect URI is not registered on the app | | `KOE101` invalid client | The client secret is generated but not enabled | | No email in the profile | The email consent item is not approved for this app | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | | [NAVER](/docs/connectors/naver) | Add the other major Korean consumer identity | ### Keycloak Source: https://www.authdog.com/docs/connectors/keycloak.md Keycloak signs users in against a realm on a Keycloak server you or your customer runs. It is the self-hosted option in the catalog: the directory stays on your infrastructure while Authdog fronts the application. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Keycloak**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the realm client 1. Open the Keycloak admin console and select the realm. 2. Under **Clients**, create a client with **Client authentication** on, a confidential client. 3. Enable the **Standard flow** (authorization code). 4. Under **Valid redirect URIs**, add the redirect URI from Authdog. 5. Save, then copy the **Client ID** and the secret from the **Credentials** tab. 6. Note the realm base URL, that is the Domain URI. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The client identifier from the provider | | **Client Secret** | The client secret from the provider | | **Domain URI** | The realm base URL, e.g. `https://keycloak.example.com/realms/acme` | Save, then toggle the connection **active**. ## What Keycloak returns Authdog requests the `openid email profile` scopes and reads the profile from the ID token the provider issues. Claims come from the ID token the realm issues: `sub`, preferred username, email and its verified flag, and any protocol mappers you configured. Use the realm base URL as the Domain URI (`https://host/realms/`) not the admin console URL and not the full authorize endpoint. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the Keycloak button and complete the flow. 3. Confirm the user appears under **Users** in the console with a Keycloak identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `invalid_client` | The client is public rather than confidential, so it has no secret | | `Invalid parameter: redirect_uri` | The URI is missing from the client's valid redirect URIs | | 404 on authorize | The Domain URI is not the realm base | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | | [Enterprise SSO](/docs/sso) | Connect Keycloak as a generic OIDC enterprise connection | ### Klarna Source: https://www.authdog.com/docs/connectors/klarna.md Klarna signs users in with their Klarna account. Klarna runs separate hosts per region and for its playground, so this connector takes a Domain URI. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Klarna**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. Request OAuth credentials from Klarna for your merchant account. 2. Provide Klarna with the redirect URI from Authdog so it can be allow-listed. 3. Copy the issued **Client ID** and **Client Secret**. 4. Choose the Domain URI for the region you operate in. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The client identifier from the provider | | **Client Secret** | The client secret from the provider | | **Domain URI** | The Klarna login host for your region, e.g. `https://login.klarna.com` | Save, then toggle the connection **active**. ## What Klarna returns Authdog requests the `openid email profile` scopes and reads the profile from ``/connect/userinfo``. The userinfo endpoint returns the `sub` identifier, email, and name claims for the Klarna account. Known hosts: `https://login.klarna.com` (production EU), `https://login.klarna.com/na/lp/idp` (production NA), `https://login.playground.klarna.com` (playground EU), and `https://login.playground.klarna.com/na/lp/idp` (playground NA). ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the Klarna button and complete the flow. 3. Confirm the user appears under **Users** in the console with a Klarna identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | Discovery or authorize 404 | The Domain URI is missing the regional path segment | | `invalid_client` | Playground credentials used against production | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | ### LINE Source: https://www.authdog.com/docs/connectors/line.md LINE signs users in with their LINE account, the messaging identity most users in Japan, Taiwan, and Thailand already carry. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **LINE**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. Open the [LINE Developers console](https://developers.line.biz/console/) and create a provider, then a **LINE Login** channel. 2. Under **LINE Login settings**, add the redirect URI from Authdog to the **Callback URL** list. 3. Enable the `email` permission, it requires submitting a privacy policy and applying for the option. 4. Copy the **Channel ID** and **Channel secret** from **Basic settings**. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The LINE **Channel ID** | | **Client Secret** | The LINE **Channel secret** | Save, then toggle the connection **active**. ## What LINE returns Authdog requests the `openid profile email` scopes and reads the profile from `https://api.line.me/v2/profile`. The profile endpoint returns the LINE user id, display name, and picture. The email, when granted, arrives in the ID token. Email access is an opt-in that LINE reviews. Until it is approved, users arrive with a display name and identifier only. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the LINE button and complete the flow. 3. Confirm the user appears under **Users** in the console with a LINE identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `400 invalid redirect_uri` | The callback URL is missing from the channel | | No email claim | The email permission has not been approved for this channel | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | ### NAVER Source: https://www.authdog.com/docs/connectors/naver.md NAVER signs users in with their NAVER account, the other consumer identity most South Korean users already have. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **NAVER**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. Open the [NAVER Developers application page](https://developers.naver.com/apps/#/register) and register an application. 2. Select **네이버 아이디로 로그인** (Login with NAVER) as the API to use. 3. Choose the profile fields you need, name, email, nickname, profile image. 4. Set the environment to **PC웹** and paste the redirect URI from Authdog as the callback URL. 5. Copy the **Client ID** and **Client Secret**. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The client identifier from the provider | | **Client Secret** | The client secret from the provider | Save, then toggle the connection **active**. ## What NAVER returns Authdog requests the provider's default scopes and reads the profile from `https://openapi.naver.com/v1/nid/me`. The `nid/me` endpoint returns the NAVER id, and whichever of email, nickname, name, and profile image you requested. Fields you did not select at registration are simply absent from the response, check the app's configuration before assuming a mapping bug. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the NAVER button and complete the flow. 3. Confirm the user appears under **Users** in the console with a NAVER identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `invalid_request` | The callback URL does not match the registered one | | Missing email | The email field was not requested on the application | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | | [Kakao](/docs/connectors/kakao) | Add the other major Korean consumer identity | ### PayPal Source: https://www.authdog.com/docs/connectors/paypal.md Log in with PayPal signs users in with their PayPal account. It suits commerce products where the buyer already has PayPal and you want one identity across sign-in and checkout. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **PayPal**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. Open the [PayPal Developer Dashboard](https://developer.paypal.com/dashboard/applications/live) and create an app under **Live**. 2. Open the app and enable **Log in with PayPal**. 3. Select **Advanced options** and add the redirect URI from Authdog as a **Return URL**. 4. Choose the personal information you need, at minimum the full name and email. 5. Save, then copy the **Client ID** and **Secret**. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The client identifier from the provider | | **Client Secret** | The client secret from the provider | Save, then toggle the connection **active**. ## What PayPal returns Authdog requests the `openid profile address` scopes and reads the profile from `https://api.paypal.com/v1/identity/oauth2/userinfo`. The userinfo endpoint returns the payer id, name, email and its verified flag, and (with the `address` scope) the address on the account. Only attributes you enabled on the app are released, so a field you did not tick simply arrives empty. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the PayPal button and complete the flow. 3. Confirm the user appears under **Users** in the console with a PayPal identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `invalid_client` | Sandbox credentials used against the live endpoint | | Missing email or name | The corresponding attribute is not enabled on the PayPal app | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | | [PayPal Sandbox](/docs/connectors/paypal-sandbox) | Test the same flow against sandbox accounts | ### PayPal Sandbox Source: https://www.authdog.com/docs/connectors/paypal-sandbox.md The PayPal Sandbox connector is the PayPal connector pointed at `api.sandbox.paypal.com`, for testing with the sandbox buyer accounts in your developer dashboard. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **PayPal Sandbox**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. Create the app under **Sandbox** in the [PayPal Developer Dashboard](https://developer.paypal.com/dashboard/applications/sandbox). 2. Enable **Log in with PayPal** and add the redirect URI from Authdog as a **Return URL**. 3. Create sandbox personal accounts under **Testing Tools > Sandbox Accounts**. 4. Copy the sandbox app's **Client ID** and **Secret**. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The client identifier from the provider | | **Client Secret** | The client secret from the provider | Save, then toggle the connection **active**. ## What PayPal Sandbox returns Authdog requests the `openid profile address` scopes and reads the profile from `https://api.sandbox.paypal.com/v1/identity/oauth2/userinfo`. Sandbox responses mirror live ones. Sandbox buyer accounts have their own credentials, real PayPal logins will not work here. Configure this connector in a development environment and the live connector in production so a test credential can never issue a production session. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the PayPal Sandbox button and complete the flow. 3. Confirm the user appears under **Users** in the console with a PayPal Sandbox identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `invalid_client` | Live credentials used against the sandbox endpoint, or the reverse | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | | [PayPal](/docs/connectors/paypal) | Set up the live connector | ### Planning Center Source: https://www.authdog.com/docs/connectors/planning-center.md Planning Center signs users in with the account their church or organization already uses for people, services, and giving. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Planning Center**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. Open the [Planning Center developer applications page](https://api.planningcenteronline.com/oauth/applications). 2. Create a new application and name it. 3. Under **Redirect URI**, paste the redirect URI from Authdog. 4. Select the **People** scope. 5. Save, then copy the **Application ID** and **Secret**. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The Planning Center **Application ID** | | **Client Secret** | The Planning Center **Secret** | Save, then toggle the connection **active**. ## What Planning Center returns Authdog requests the `people` scope and reads the profile from `https://api.planningcenteronline.com/people/v2/me`. The People API returns the person record for the signed-in user: id, name, and the primary email address on file. Planning Center identities are organization-scoped. The same person at two organizations is two records. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the Planning Center button and complete the flow. 3. Confirm the user appears under **Users** in the console with a Planning Center identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `invalid_scope` | The application was created without the People scope | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | ### Renren Source: https://www.authdog.com/docs/connectors/renren.md Renren signs users in with their Renren account, a Chinese social network with a long-standing user base in university communities. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **人人网 (Renren)**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. Register an application on the Renren open platform. 2. Set the callback to the redirect URI from Authdog. 3. Copy the **API Key** and **Secret Key**. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The Renren **API Key** | | **Client Secret** | The Renren **Secret Key** | Save, then toggle the connection **active**. ## What Renren returns Authdog requests the provider's default scopes and reads the profile from `https://api.renren.com/v2/user/get`. The user endpoint returns the Renren user id, name, and avatar. This connector does not return an email address, so users who arrive through it have a display name and an identifier only. Pair it with a method that collects one, or ask for an email after the first sign-in. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the Renren button and complete the flow. 3. Confirm the user appears under **Users** in the console with a Renren identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `redirect_uri_mismatch` | The registered callback differs from Authdog's | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | ### Salesforce Source: https://www.authdog.com/docs/connectors/salesforce.md Salesforce signs users in with their production org account. It is the connector for products that sit next to a customer's CRM and want the same identity on both sides. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Salesforce**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the connected app 1. In Salesforce **Setup**, open **App Manager** and select **New Connected App**. 2. Fill in the name, API name, and contact email. 3. Enable **Enable OAuth Settings**. 4. Under **Callback URL**, paste the redirect URI from Authdog. 5. Add the OAuth scopes **Access the identity URL service (id, profile, email, address, phone)** and **Perform requests at any time (refresh_token, offline_access)**. 6. Save. Connected app changes take a few minutes to propagate. 7. Open **Manage Consumer Details** and copy the **Consumer Key** and **Consumer Secret**. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The Salesforce **Consumer Key** | | **Client Secret** | The Salesforce **Consumer Secret** | Save, then toggle the connection **active**. ## What Salesforce returns Authdog requests the `openid email profile` scopes and reads the profile from `https://login.salesforce.com/services/oauth2/userinfo`. The userinfo endpoint returns the user id, organization id, username, email, and display name. This connector targets production orgs at `login.salesforce.com`. Use [Salesforce Sandbox](/docs/connectors/salesforce-sandbox) for `test.salesforce.com`, and [Salesforce Community](/docs/connectors/salesforce-community) for Experience Cloud sites on a custom domain. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the Salesforce button and complete the flow. 3. Confirm the user appears under **Users** in the console with a Salesforce identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `OAUTH_APPROVAL_ERROR_GENERIC` | The connected app's policies block the user, or changes have not propagated yet | | `invalid_client_id` | The consumer key belongs to a different org or a sandbox | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | | [Salesforce Sandbox](/docs/connectors/salesforce-sandbox) | Point the same setup at a sandbox org | | [Salesforce Community](/docs/connectors/salesforce-community) | Sign in Experience Cloud users | ### Salesforce Sandbox Source: https://www.authdog.com/docs/connectors/salesforce-sandbox.md The Salesforce Sandbox connector is the production connector pointed at `test.salesforce.com`. Use it to develop and test against a sandbox org without touching production credentials. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Salesforce Sandbox**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the connected app 1. Create the connected app in the **sandbox** org, following the [Salesforce connector steps](/docs/connectors/salesforce). 2. Set the **Callback URL** to the redirect URI from your development environment's connection in Authdog. 3. Copy the sandbox org's **Consumer Key** and **Consumer Secret**. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The sandbox org's **Consumer Key** | | **Client Secret** | The sandbox org's **Consumer Secret** | Save, then toggle the connection **active**. ## What Salesforce Sandbox returns Authdog requests the `openid email profile` scopes and reads the profile from `https://test.salesforce.com/services/oauth2/userinfo`. The response shape matches production. Only the host differs, which means user ids and org ids from a sandbox never collide with production ones. Configure this connector in your development or staging Authdog environment, and the production connector in production. Mixing them is the usual cause of a sandbox refresh silently breaking sign-in. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the Salesforce Sandbox button and complete the flow. 3. Confirm the user appears under **Users** in the console with a Salesforce Sandbox identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `invalid_client_id` | A production consumer key was used against the sandbox host | | Sign-in stops working after a refresh | A sandbox refresh recreates the connected app, re-copy the credentials | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | | [Salesforce](/docs/connectors/salesforce) | Set up the production connector | ### Salesforce Community Source: https://www.authdog.com/docs/connectors/salesforce-community.md Salesforce Community signs in external users of an Experience Cloud site, customers and partners rather than internal Salesforce users. Because each site has its own host, this connector takes a Domain URI. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Salesforce Community**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the connected app 1. Create a connected app in the org that owns the Experience Cloud site, as in the [Salesforce connector steps](/docs/connectors/salesforce). 2. Set the **Callback URL** to the redirect URI from Authdog. 3. In **Digital Experiences > All Sites**, note the site's full URL including any path prefix. 4. Make sure the site is active and allows self-registration or has the users you intend to sign in. 5. Copy the **Consumer Key** and **Consumer Secret**. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The **Consumer Key** of the connected app | | **Client Secret** | The **Consumer Secret** of the connected app | | **Domain URI** | Your Experience Cloud site URL, e.g. `https://acme.my.site.com/customers` | Save, then toggle the connection **active**. ## What Salesforce Community returns Authdog requests the `openid email profile` scopes and reads the profile from ``/services/oauth2/userinfo``. The site's own userinfo endpoint returns the community user's id, username, email, and display name, scoped to that site, not to the internal org directory. Include the site's path prefix in the Domain URI. A site published at `/customers` will not authorize requests sent to the bare domain. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the Salesforce Community button and complete the flow. 3. Confirm the user appears under **Users** in the console with a Salesforce Community identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `invalid_client_id` | The connected app belongs to a different org than the site | | Endless redirect to the site login page | The site is inactive, or the user is not a member of it | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | | [Salesforce](/docs/connectors/salesforce) | Sign in internal org users instead | ### Shopify Source: https://www.authdog.com/docs/connectors/shopify.md Shopify signs in the staff user of a specific store. It authenticates against the Admin API, so the identity it establishes is a merchant, not a shopper. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Shopify**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. Open the [Shopify Partner dashboard](https://partners.shopify.com/) and create an app, or create a custom app in the store's admin. 2. Under **App setup**, add the redirect URI from Authdog to the **Allowed redirection URL(s)**. 3. Request the `read_products`, `read_orders`, and `read_customers` scopes. 4. Copy the **Client ID** (API key) and **Client secret**. 5. Note the store's `myshopify.com` host, that is the Domain URI. > **Note** > > This connector is for merchant and staff sign-in through the Admin API. > It is not Shopify's customer-account login, and it does not authenticate shoppers. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The client identifier from the provider | | **Client Secret** | The client secret from the provider | | **Domain URI** | The store's myshopify host, e.g. `https://acme.myshopify.com` | Save, then toggle the connection **active**. ## What Shopify returns Authdog requests the `read_products read_orders read_customers` scopes and reads the profile from ``/admin/api/2024-04/shop.json``. The shop endpoint returns the store record (shop id, name, and the shop owner's email), which Authdog uses as the profile. Because the Domain URI names one store, one connection signs in one store. A multi-store product needs a connection per store, or a different integration pattern. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the Shopify button and complete the flow. 3. Confirm the user appears under **Users** in the console with a Shopify identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `invalid_request: redirect_uri is not whitelisted` | The URI is missing from the allowed redirection URLs | | `403` from the shop endpoint | The app lacks the requested read scopes on that store | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | ### Vercel Source: https://www.authdog.com/docs/connectors/vercel.md Vercel signs users in with their Vercel account, which suits deployment tooling and integrations that already read a team's projects. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Vercel**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. Open the [Vercel integrations console](https://vercel.com/dashboard/integrations/console) and create an integration. 2. Fill in the name, description, and logo. 3. Under **Redirect URL**, paste the redirect URI from Authdog. 4. Save, then copy the **Client ID** and **Client Secret**. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The client identifier from the provider | | **Client Secret** | The client secret from the provider | Save, then toggle the connection **active**. ## What Vercel returns Authdog requests the `openid profile email` scopes and reads the profile from `https://api.vercel.com/login/oauth/userinfo`. The userinfo endpoint returns the `sub` identifier, name, email, and avatar for the signed-in Vercel user. A Vercel account can belong to several teams. Sign-in identifies the person, not the team, resolve team membership through Vercel's API after the session exists. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the Vercel button and complete the flow. 3. Confirm the user appears under **Users** in the console with a Vercel identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `invalid_redirect_uri` | The redirect URL on the integration does not match | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | ### Vimeo Source: https://www.authdog.com/docs/connectors/vimeo.md Vimeo signs users in with their Vimeo account, the connector for video tooling and creator platforms that read or publish videos. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Vimeo**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. Open the [Vimeo developer apps page](https://developer.vimeo.com/apps) and create an app. 2. Fill in the name, description, and app URL. 3. Under **Authentication**, add the redirect URI from Authdog as a callback URL. 4. Copy the **Client Identifier** and **Client Secret**. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The Vimeo **Client Identifier** | | **Client Secret** | The Vimeo **Client Secret** | Save, then toggle the connection **active**. ## What Vimeo returns Authdog requests the `public email` scopes and reads the profile from `https://api.vimeo.com/me`. The `/me` endpoint returns the user URI, display name, avatar, and (with the `email` scope) the account email. Vimeo user URIs look like `/users/12345`; the numeric portion is the stable identifier. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the Vimeo button and complete the flow. 3. Confirm the user appears under **Users** in the console with a Vimeo identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `invalid_scope` | The app was not granted the email scope by Vimeo | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | ### VKontakte Source: https://www.authdog.com/docs/connectors/vk.md VKontakte signs users in with their VK account, the dominant consumer social network in Russian-speaking markets. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **VKontakte**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. Open [VK for Developers](https://dev.vk.com/) and create an application of type **Website**. 2. Set the site address and the **Authorized redirect URI** to the redirect URI from Authdog. 3. Copy the **Application ID** and the **Secure key**. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The VK **Application ID** | | **Client Secret** | The VK **Secure key** | Save, then toggle the connection **active**. ## What VKontakte returns Authdog requests the `email` scope and reads the profile from `https://api.vk.com/method/users.get`. The users endpoint returns the numeric VK id, first and last name, and avatar. The email arrives with the token response rather than the profile call. VK requires the application to be published before users outside the app's admins can authorize it. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the VKontakte button and complete the flow. 3. Confirm the user appears under **Users** in the console with a VKontakte identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `redirect_uri is incorrect` | The URI is missing from the app's authorized list | | Only admins can sign in | The VK application is unpublished | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | ### Weibo Source: https://www.authdog.com/docs/connectors/weibo.md Sina Weibo signs users in with their Weibo account, a mainstream consumer identity in mainland China. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **新浪微博 (Weibo)**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. Open the [Weibo open platform](https://open.weibo.com/) and create a web application. 2. Set the authorization callback page to the redirect URI from Authdog. 3. Complete the application review Weibo requires before public use. 4. Copy the **App Key** and **App Secret**. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The Weibo **App Key** | | **Client Secret** | The Weibo **App Secret** | Save, then toggle the connection **active**. ## What Weibo returns Authdog requests the provider's default scopes and reads the profile from `https://api.weibo.com/2/account/get_uid.json`. The account endpoint returns the numeric Weibo uid, which Authdog uses as the stable identifier. This connector does not return an email address, so users who arrive through it have a display name and an identifier only. Pair it with a method that collects one, or ask for an email after the first sign-in. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the Weibo button and complete the flow. 3. Confirm the user appears under **Users** in the console with a Weibo identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `redirect_uri_mismatch` | The callback page does not match the registered value | | Only test accounts can sign in | The application has not passed Weibo's review | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | ### WordPress.com Source: https://www.authdog.com/docs/connectors/wordpress.md WordPress.com signs users in with their WordPress.com account, including users of Jetpack-connected self-hosted sites. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **WordPress**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. Open the [WordPress.com developer applications page](https://developer.wordpress.com/apps/) and create an application. 2. Fill in the name, description, and website URL. 3. Under **Redirect URLs**, paste the redirect URI from Authdog. 4. Set the **Type** to **Web Client**. 5. Save, then copy the **Client ID** and **Client Secret**. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The client identifier from the provider | | **Client Secret** | The client secret from the provider | Save, then toggle the connection **active**. ## What WordPress.com returns Authdog requests the `global` scope and reads the profile from `https://public-api.wordpress.com/rest/v1.1/me`. The `/me` endpoint returns the user id, username, display name, email and its verified flag, avatar, and primary site. This connector authenticates WordPress.com accounts. It does not sign in users of an arbitrary self-hosted WordPress installation. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the WordPress.com button and complete the flow. 3. Confirm the user appears under **Users** in the console with a WordPress.com identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `invalid_client` | The app type is not Web Client, so no secret applies | | `unauthorized` | The redirect URL is missing from the app | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | ### Xero Source: https://www.authdog.com/docs/connectors/xero.md Xero signs users in with their Xero account, the identity behind small-business accounting integrations. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Xero**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. Open the [Xero Developer portal](https://developer.xero.com/app/manage) and select **New app**. 2. Choose **Web app**, name it, and provide the company URL. 3. Under **Redirect URI**, paste the redirect URI from Authdog. 4. Create the app, then generate a client secret under **Configuration**. 5. Copy the **Client ID** and the generated **Client Secret**. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The client identifier from the provider | | **Client Secret** | The client secret from the provider | Save, then toggle the connection **active**. ## What Xero returns Authdog requests the `openid profile email` scopes and reads the profile from `https://api.xero.com/identity/connect/userinfo`. The identity userinfo endpoint returns the `xero_userid`, email, and name of the signed-in user. Xero separates identity from tenants: a user may authorize several organizations. Sign-in tells you who the person is, not which organization they are acting for. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the Xero button and complete the flow. 3. Confirm the user appears under **Users** in the console with a Xero identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `unauthorized_client` | The app type is not a web app, so it has no client secret | | `invalid_grant` | The client secret expired, Xero secrets have a fixed lifetime | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | ### Yahoo Source: https://www.authdog.com/docs/connectors/yahoo.md Yahoo signs users in with their Yahoo account. Its user base skews toward long-lived consumer email addresses, which makes it a useful secondary connector on mainstream products. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Yahoo**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. Open the [Yahoo Developer Network app page](https://developer.yahoo.com/apps/create/) and create an app. 2. Set the **Application Type** to **Web Application**. 3. Set the **Redirect URI** to the redirect URI from Authdog, Yahoo requires HTTPS. 4. Under **API Permissions**, enable **Profiles (Social Directory)** read access. 5. Copy the **Client ID (Consumer Key)** and **Client Secret (Consumer Secret)**. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The client identifier from the provider | | **Client Secret** | The client secret from the provider | Save, then toggle the connection **active**. ## What Yahoo returns Authdog requests the `openid profile email` scopes and reads the profile from `https://api.login.yahoo.com/openid/v1/userinfo`. The userinfo endpoint returns the `sub` identifier, name, email, and email verification flag. Yahoo rejects plain HTTP redirect URIs outright, so local development needs a public HTTPS host. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the Yahoo button and complete the flow. 3. Confirm the user appears under **Users** in the console with a Yahoo identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `INVALID_REDIRECT_URI` | The redirect URI is not HTTPS, or does not match | | `invalid_client` | Consumer key and secret come from different apps | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | ### Yammer Source: https://www.authdog.com/docs/connectors/yammer.md Yammer signs users in with their Yammer account, Microsoft's enterprise social network, now folded into Viva Engage. Use it for internal tools in organizations that still run on it. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Yammer**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. Register a client at [Yammer's developer page](https://www.yammer.com/client_applications). 2. Set the **Redirect URI** to the redirect URI from Authdog. 3. Copy the **Client ID** and **Client Secret**. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The client identifier from the provider | | **Client Secret** | The client secret from the provider | Save, then toggle the connection **active**. ## What Yammer returns Authdog requests the provider's default scopes and reads the profile from `https://www.yammer.com/api/v1/users/current.json`. The current-user endpoint returns the Yammer user id, full name, email, and network. Yammer identities are network-scoped, and Microsoft has been migrating them into Viva Engage. For new deployments, prefer [Microsoft](/docs/connectors/microsoft) or an [Enterprise SSO](/docs/sso) connection to Entra ID. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the Yammer button and complete the flow. 3. Confirm the user appears under **Users** in the console with a Yammer identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `invalid_client` | The client was registered in a different Yammer network | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | | [Microsoft](/docs/connectors/microsoft) | Use the current Microsoft identity instead | ### Yandex Source: https://www.authdog.com/docs/connectors/yandex.md Yandex signs users in with their Yandex account, widely used across Russian-speaking markets for mail, maps, and cloud services. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Yandex**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. Open the [Yandex OAuth application page](https://oauth.yandex.com/client/new) and create an application. 2. Choose **Web services** as the platform. 3. Set the **Redirect URI** to the redirect URI from Authdog. 4. Grant the `login:info` and `login:email` permissions. 5. Copy the **ClientID** and **Client secret**. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The client identifier from the provider | | **Client Secret** | The client secret from the provider | Save, then toggle the connection **active**. ## What Yandex returns Authdog requests the `login:info login:email` scopes and reads the profile from `https://login.yandex.ru/info`. The info endpoint returns the Yandex id, login, display name, real name, and default email. Yandex ids are numeric and stable; logins can change. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the Yandex button and complete the flow. 3. Confirm the user appears under **Users** in the console with a Yandex identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `invalid_scope` | The permissions were not granted on the application | | Redirect or callback URL error | The URI registered with the provider does not match Authdog's exactly | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI to register | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | ### Red Hat Source: https://www.authdog.com/docs/connectors/redhat.md Red Hat signs users in through Red Hat SSO, the OpenID Connect realm at `sso.redhat.com`. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com), select the project and environment, open **Authentication > Providers**, find **Red Hat**, and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Create the application 1. Register an OAuth client for the Red Hat SSO realm `redhat-external`. 2. Set the redirect URI to the value copied from Authdog. 3. Request the scopes `openid`, `email`, and `profile`. 4. Copy the **client ID** and **client secret**. ## Configure Authdog | Field | Value | | --- | --- | | **Client ID** | The Red Hat client ID | | **Client Secret** | The Red Hat client secret | Save, then toggle the connection **active**. ## What Red Hat returns Authdog uses the authorization-code flow against `https://sso.redhat.com/auth/realms/redhat-external/protocol/openid-connect/auth` and reads the profile from the realm userinfo endpoint. The userinfo response includes the subject and, when the `email` scope is granted, an email address. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the Red Hat button and complete the flow. 3. Confirm the user appears under **Users** in the console with a Red Hat identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `redirect_uri_mismatch` | The callback registered on the client differs from Authdog's redirect URI | | No email on the user | The client was not granted the `email` scope | | Works in one environment only | Each environment has its own `connectionId`, and so its own redirect URI | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | | [Enterprise SSO](/docs/sso) | Connect a SAML or OpenID Connect workforce IdP | ### ÉduConnect Source: https://www.authdog.com/docs/connectors/educonnect.md ÉduConnect is the French national OIDC identity provider for primary and secondary students and parents signing into school portals (ENT). Authdog fronts it as a first-class enterprise OIDC connector, so one connection serves every ENT that federates through ÉduConnect. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com/dashboard/authentication?tab=providers&category=federation), select the project and environment, find **ÉduConnect** (the **Federation** filter is pre-selected by this link), and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Register with ÉduConnect ÉduConnect is operated by the French Ministry of Education. Access is provisioned for eligible ENT publishers and education partners, not self-served. 1. Request a client from the ÉduConnect operator and sign the federation charter. 2. Provide the redirect URI from Authdog as a registered redirect URI. 3. The operator returns a **Client ID**, **Client Secret**, and the OIDC **discovery URL** of the ÉduConnect environment you were assigned (test, integration, or production). Use the operator's documentation for the exact discovery URL and required scopes for the environment. ÉduConnect may require specific scopes and a `acr` values parameter for the level of assurance requested. ## Configure Authdog | Field | Value | | --- | --- | | **Connection name** | A label that identifies this ÉduConnect connection, e.g. `educonnect-prod` | | **OIDC discovery URL** | The discovery document URL supplied by the ÉduConnect operator | | **Client ID** | The client identifier issued by ÉduConnect | | **Client secret** | The client secret issued by ÉduConnect | | **Scopes** | `openid profile email` plus any ÉduConnect-required scopes | | **Email domains** | The discovery domains that should route to this connection, e.g. `ent-example.fr` | Save, then toggle the connection **active**. ## What ÉduConnect returns Authdog requests the `openid profile email` scopes (plus any required by ÉduConnect) and reads the normalized profile from the ID token and the userinfo endpoint. Claims include the stable subject, email, and the level of assurance requested from ÉduConnect. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the ÉduConnect button and complete the flow on the operator's hosted page. 3. Confirm the user appears under **Users** in the console with an ÉduConnect identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `invalid_client` | The client credentials do not match the ÉduConnect environment | | `Invalid parameter: redirect_uri` | The URI registered with ÉduConnect does not match Authdog's exactly | | Discovery document 404 | The discovery URL is not the ÉduConnect environment's discovery endpoint | | Wrong user population | You pointed the connection at the test environment instead of production | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | | [Enterprise SSO](/docs/sso) | Connect any SAML or OIDC enterprise identity provider | ### FranceConnect Source: https://www.authdog.com/docs/connectors/franceconnect.md FranceConnect is the French government's central identity aggregator. Instead of authenticating users itself, it forwards sign-in to trusted public-service accounts (impots.gouv.fr, ameli, La Poste, etc.) and returns a verified identity to your application. Authdog fronts it as a first-class enterprise OIDC connector, which lets parents and citizens sign in through FranceConnect with one connection. ## Copy the redirect URI In the [Authdog console](https://console.authdog.com/dashboard/authentication?tab=providers&category=federation), select the project and environment, find **FranceConnect** (the **Federation** filter is pre-selected by this link), and click **Enable**. Copy the redirect URI shown in the form: ```url https://identity.authdog.com/api/v1/callback/ ``` ## Register with FranceConnect FranceConnect access is provisioned by the DGNum operator for eligible public-service and education partners. 1. Request a client from the FranceConnect operator for the integration environment, then production. 2. Provide the redirect URI from Authdog as a registered redirect URI. 3. The operator returns a **Client ID**, **Client Secret**, and the OIDC **discovery URL** for the environment. FranceConnect distinguishes an **integration** environment from **production**. Keep one Authdog connection per environment and use email domains or connection names to tell them apart. ## Configure Authdog | Field | Value | | --- | --- | | **Connection name** | A label that identifies this FranceConnect connection, e.g. `franceconnect-prod` | | **OIDC discovery URL** | The discovery document URL supplied by the FranceConnect operator | | **Client ID** | The client identifier issued by FranceConnect | | **Client secret** | The client secret issued by FranceConnect | | **Scopes** | `openid profile email` plus any FranceConnect-required scopes | | **Email domains** | The discovery domains that should route to this connection | Save, then toggle the connection **active**. ## What FranceConnect returns Authdog requests the `openid profile email` scopes and reads the normalized profile from the ID token and the userinfo endpoint. FranceConnect returns a verified identity and the level of assurance (`acr`) used for the sign-in, which you can read from the token claims. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the FranceConnect button and choose a trusted account on the FranceConnect hosted page. 3. Confirm the user appears under **Users** in the console with a FranceConnect identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `invalid_client` | The client credentials do not match the FranceConnect environment | | `Invalid parameter: redirect_uri` | The URI registered with FranceConnect does not match Authdog's exactly | | Discovery document 404 | The discovery URL is not the FranceConnect environment's discovery endpoint | | Wrong level of assurance | The `acr` values requested do not match what FranceConnect is configured to return | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | | [Enterprise SSO](/docs/sso) | Connect any SAML or OIDC enterprise identity provider | ### ARENA / AAF Source: https://www.authdog.com/docs/connectors/arena-aaf.md ARENA / AAF is the SAML identity provider for teachers, administrators, and education staff managed by the French regional academies. AAF (Annuaire Académique Fédéré) is the federated directory that backs it. Authdog fronts it as a first-class enterprise SAML connector, so one connection can serve staff across academies that federate through ARENA. ## Copy the service-provider values In the [Authdog console](https://console.authdog.com/dashboard/authentication?tab=providers&category=federation), select the project and environment, find **ARENA / AAF** (the **Federation** filter is pre-selected by this link), and click **Enable**. Authdog displays: - SP-initiated sign-in URL - ACS / Reply URL - SP Entity ID / audience - Downloadable SP metadata XML ## Register with the academy operator ARENA / AAF access is provisioned by the academy or the federation operator for eligible education services. 1. Request a SAML connection from the academy operator. 2. Provide Authdog's SP entity ID, ACS URL, and SP metadata to the operator. 3. The operator returns the **IdP SSO URL**, the **IdP X.509 signing certificate**, and the **IdP entity ID**. If the operator publishes a metadata URL, you can import it directly into Authdog instead of pasting values by hand. ## Configure Authdog | Field | Value | | --- | --- | | **Connection name** | A label that identifies this ARENA / AAF connection, e.g. `arena-aaf-prod` | | **IdP SSO URL** | The SAML SSO URL from the academy operator | | **IdP X.509 certificate** | The signing certificate from the operator or metadata | | **IdP Entity ID** | The entity ID from the operator or metadata | | **Email domains** | The discovery domains that should route to this connection, e.g. `ac-example.fr` | Save, then toggle the connection **active**. ## What ARENA / AAF returns Authdog reads the SAML subject and the email, name, and group claims the academy IdP releases. The exact claim names depend on the academy's attribute release policy; map them to profile fields in the connection form. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the ARENA / AAF button and complete the flow on the academy IdP. 3. Confirm the user appears under **Users** in the console with an ARENA / AAF identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `No response` / signature error | The IdP certificate in Authdog does not match the academy's current signing certificate | | Missing email claim | The academy did not release the email attribute to your SP | | `Invalid binding` | The operator requires a different SAML binding than the default | | Works in one academy only | Each academy may run its own IdP; add a connection per academy | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | | [Enterprise SSO](/docs/sso) | Connect any SAML or OIDC enterprise identity provider | ### RENATER Source: https://www.authdog.com/docs/connectors/renater.md RENATER is the *Fédération Éducation-Recherche*, the SAML federation for higher education and research institutions across France. It lets students and researchers sign in through their home institution's IdP without your application trusting each institution individually. Authdog fronts it as a first-class enterprise SAML connector. For institutions that publish their IdP metadata through eduGAIN, the generic **SAML 2.0** connector also works. Use the RENATER connector when you want a connection labeled and documented as RENATER in the console. ## Copy the service-provider values In the [Authdog console](https://console.authdog.com/dashboard/authentication?tab=providers&category=federation), select the project and environment, find **RENATER** (the **Federation** filter is pre-selected by this link), and click **Enable**. Authdog displays: - SP-initiated sign-in URL - ACS / Reply URL - SP Entity ID / audience - Downloadable SP metadata XML ## Register with RENATER RENATER federates institution IdPs through a metadata aggregate that your SP trusts. 1. Register your SP with the RENATER federation operator and sign the federation policy. 2. Provide Authdog's SP entity ID, ACS URL, and SP metadata. RENATER lists your SP in its metadata aggregate. 3. The operator returns the **metadata URL** for the federation aggregate (or a specific IdP's metadata, if you scope the connection to one institution). ## Configure Authdog | Field | Value | | --- | --- | | **Connection name** | A label that identifies this RENATER connection, e.g. `renater-prod` | | **IdP SSO URL** | The SAML SSO URL from the federation metadata | | **IdP X.509 certificate** | The signing certificate from the federation metadata | | **IdP Entity ID** | The entity ID from the federation metadata | | **Email domains** | The discovery domains that should route to this connection, e.g. `etu-example.fr`, `univ-example.fr` | Save, then toggle the connection **active**. For a multi-institution connection, prefer importing the federation metadata URL so certificate rotations are picked up when you re-fetch. For a single-institution connection, pin the certificate and rotate it manually. ## What RENATER returns Authdog reads the SAML subject and the eduPerson-scoped attributes the institution IdP releases, including `eduPersonPrincipalName`, `eduPersonAffiliation`, `mail`, and `displayName`. The exact attributes depend on the institution's attribute release policy; map them to profile fields in the connection form. ## Test it 1. Open your environment's hosted sign-in page, or link to `https://identity.authdog.com/api/v1/signin/`. 2. Select the RENATER button and pick your institution on the discovery service (WAYF) if prompted. 3. Complete the flow on your institution IdP. 4. Confirm the user appears under **Users** in the console with a RENATER identity linked. ## Troubleshooting | Symptom | Cause | | --- | --- | | `No response` / signature error | The federation metadata is stale; re-fetch the metadata URL to pick up a rotated certificate | | Missing `eduPersonPrincipalName` | The institution did not release the attribute to your SP | | WAYF shows no institutions | Your SP is not yet registered in the federation aggregate | | Works for one institution only | You pinned a single IdP instead of the federation aggregate | ## Related | Read | To learn how to | | --- | --- | | [Connectors](/docs/connectors) | Set up any other social provider | | [Enterprise SSO](/docs/sso) | Connect any SAML or OIDC enterprise identity provider | ## Framework SDKs ### Next.js Source: https://www.authdog.com/docs/frameworks/nextjs.md Just want sign-in working? Start with the [Next.js quickstart](/docs/quickstarts?sdk=nextjs). The `@authdog/nextjs-app` SDK integrates Authdog with the Next.js App Router: a client provider, `useAuth`/`useUser` hooks, and server helpers for callback exchange and logout. ## Install ```package-install @authdog/nextjs-app ``` Supports Next.js 15/16 and React 18 or 19. Set `NEXT_PUBLIC_PK_AUTHDOG` for client calls and `PK_AUTHDOG` for server helpers. Both contain the same public key (`pk_...`). ## Wrap your app Mount `AuthdogProvider` in the root layout. It stores a `?token=` value only when it has three JWT-shaped segments, then strips the URL. This regex is a shape check, not signature, issuer, audience, or expiry validation: ```tsx // app/layout.tsx import { AuthdogProvider } from "@authdog/nextjs-app/client" export default function RootLayout({ children, }: { children: React.ReactNode }) { return ( {children} ) } ``` ## Read the user `useUser` calls Authdog userinfo and returns the current profile. `useAuth` only reports whether a browser token exists; do not use it to authorize protected work: ```tsx "use client" import { useUser } from "@authdog/nextjs-app" export default function Dashboard() { const { user, isLoading } = useUser() if (isLoading) return null if (!user) return

Not signed in

return

Signed in as {user.emails?.[0]?.value}

} // useAuth() -> { token, isAuthenticated, isLoading } ``` ## Exchange the callback `useAuthMiddleware(publicKey)` processes `?token=` on matching requests, validates it through Authdog userinfo, and writes HttpOnly cookies. It does **not** protect routes or validate cookies on later requests: ```ts // middleware.ts import { useAuthMiddleware } from "@authdog/nextjs-app/server" export default useAuthMiddleware(process.env.PK_AUTHDOG!) export const config = { matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"], } ``` Return from hosted sign-in to a URL matched by this middleware. For protected Route Handlers or Server Actions, independently validate the credential on every request with a [backend SDK](/docs/backend), then apply [authorization](/docs/concepts/authorization). `logoutHandler` clears server cookies; `clearAuthdogSession()` clears only browser local storage, so complete logout should do both. ## Next steps - [Component reference](/docs/components): the `@authdog/react-elements` UI used with the provider. - [Backend requests](/docs/backend): validate the same session on your API routes. - [Quickstarts](/docs/quickstarts): the five-minute path. - [Team dashboard](/docs/examples/nextjs): runnable example for this stack. ### Next.js quickstart Source: https://www.authdog.com/docs/quickstarts/nextjs.md Outcome: a Next.js App Router app that signs a user in and shows their email on one protected page. About five minutes. 0. Prerequisites: Node 20+, a browser, and an Authdog account. You will leave with `NEXT_PUBLIC_PK_AUTHDOG` / `PK_AUTHDOG` set and `/dashboard` gated. ## 1. Create an environment and copy the public key In the [console](https://console.authdog.com): 1. Create a **tenant**, a **project**, and an **environment** (for example `dev`). 2. Open **Dashboard** (`/dashboard/home`) for that environment. 3. Copy **Public key** (`pk_...`). You can also copy it from the environment picker → **Authdog Public Key**. The public key is publishable. It selects the environment. Do not put an `adenv_` API secret in the Next.js client. Details: [Environments](/docs/console/environments). ## 2. Install the SDK ```package-install @authdog/nextjs-app ``` Supports Next.js 15/16 and React 18 or 19. ## 3. Set the env vars ```bash NEXT_PUBLIC_PK_AUTHDOG=pk_... PK_AUTHDOG=pk_... ``` Both values are the same public key. The `NEXT_PUBLIC_` copy is for the browser provider; `PK_AUTHDOG` is for server middleware. ## 4. Wrap the app and exchange the callback ```tsx // app/layout.tsx import { AuthdogProvider } from "@authdog/nextjs-app/client" export default function RootLayout({ children, }: { children: React.ReactNode }) { return ( {children} ) } ``` ```ts // middleware.ts import { useAuthMiddleware } from "@authdog/nextjs-app/server" export default useAuthMiddleware(process.env.PK_AUTHDOG!) export const config = { matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"], } ``` Return from hosted sign-in to a URL this middleware matches. The helper writes HttpOnly cookies. It does not authorize later requests on its own. ## 5. Protect one route ```tsx // app/dashboard/page.tsx "use client" import { useUser } from "@authdog/nextjs-app" export default function Dashboard() { const { user, isLoading } = useUser() if (isLoading) return null if (!user) return

Not signed in

return

Signed in as {user.emails?.[0]?.value}

} ``` Send users to hosted sign-in from your [Account portal](/docs/account-portal), then back to `/dashboard`. ## 6. Verify You should see the signed-in email on `/dashboard` after completing hosted sign-in. If it fails: - **Blank user after redirect** — confirm the callback URL is matched by `middleware.ts` and both env vars are the same `pk_...`. - **Wrong environment** — the public key is from `dev` but you signed in on `prod`, or the reverse. - **401 on a Route Handler** — `useAuthMiddleware` does not protect API routes. Validate the session with a [backend SDK](/docs/backend). ## Next The [Next.js guide](/docs/frameworks/nextjs) covers `useAuth`, logout, and what the middleware does not do. ### React Elements Source: https://www.authdog.com/docs/frameworks/react.md Just want hosted sign-in on a navbar? Start with the [React quickstart](/docs/quickstarts?sdk=react). `@authdog/react-elements` is a presentational UI kit for React 18+: navbar, user profile, dropdown, and TOTP input. It does not provide a session, provider, sign-in flow, or access control. Pair it with [Next.js](/docs/frameworks/nextjs), [Remix](/docs/frameworks/remix), or your own [backend](/docs/backend). ## Install ```package-install @authdog/react-elements ``` Peer dependencies are `react` and `react-dom` 18 or 19. Import the stylesheet once at your app root: ```tsx import "@authdog/react-elements/styles.css" ``` ## Components | Component | Role | | --- | --- | | `Navbar` | App navigation with a user menu. Requires `identityHost` and `environmentId`. | | `UserProfile` | Renders the signed-in user's profile. `loading` is required. | | `UserDropdown` | The account menu on its own. | | `Button`, `ClientOnly`, `PlaceholderAlert` | `ClientOnly` is an SSR guard. | | `TOTPValidator` | `{ onValidate: (code: string) => Promise }` for MFA prompts. | ## Usage Declare every dependency explicitly. This example receives validated identity data and navigation/logout callbacks from its host app: ```tsx import { Navbar } from "@authdog/react-elements" type AppNavbarProps = { user: unknown isLoading: boolean identityHost: string environmentId: string onNavigate: (href: string) => void onLogout: () => void } export function AppNavbar(props: AppNavbarProps) { return ( props.onNavigate("/profile")} onLogout={props.onLogout} /> ) } ``` `Navbar` uses `identityHost` and `environmentId` to open hosted sign-in when no user is supplied. Pass values from a trusted, validated public-key configuration. Components render identity and invoke callbacks only; validate sessions and enforce access server-side. ## Next steps - [Component reference](/docs/components): every component and prop in depth. - [Next.js](/docs/frameworks/nextjs): the most common pairing for these elements. - [Account portal](/docs/account-portal): the zero-config hosted alternative to building your own UI. - [Customer navbar](/docs/examples/react): runnable example for this stack. ### React Elements quickstart Source: https://www.authdog.com/docs/quickstarts/react.md Outcome: a React app whose navbar opens Authdog hosted sign-in for the selected environment. About five minutes. `@authdog/react-elements` is UI only. It does not mint a session or protect a server route. For cookies and a gated page, use the [Next.js quickstart](/docs/quickstarts?sdk=nextjs) after this. 0. Prerequisites: React 18 or 19, and an Authdog account. ## 1. Create an environment and copy the IDs In the [console](https://console.authdog.com): 1. Create a **tenant**, a **project**, and an **environment**. 2. Open **Dashboard** (`/dashboard/home`). 3. Copy **Public key** (`pk_...`) and **Environment ID**. 4. Copy the identity host from the environment picker (the origin of hosted sign-in). The public key is publishable. Do not put an `adenv_` API secret in the browser. Details: [Environments](/docs/console/environments). ## 2. Install the kit ```package-install @authdog/react-elements ``` ## 3. Set the env vars ```bash VITE_AUTHDOG_IDENTITY_HOST=https://your-identity-host VITE_AUTHDOG_ENVIRONMENT_ID= ``` Use the identity host and environment ID from step 1. Vite is shown here; CRA and other bundlers use their own `REACT_APP_` / `PUBLIC_` prefix. ## 4. Import the stylesheet ```tsx import "@authdog/react-elements/styles.css" ``` Import it once at the app root. ## 5. Render the navbar ```tsx import { Navbar } from "@authdog/react-elements" export function AppNavbar() { return ( { window.location.href = href }} onProfileSelected={() => { window.location.href = "/profile" }} onLogout={() => undefined} /> ) } ``` With no `user`, **Navbar** opens hosted sign-in using `identityHost` and `environmentId`. ## 6. Verify Click the sign-in control. You should land on the Authdog hosted flow for that environment. If it fails: - **Wrong host** — `identityHost` must be the identity origin for this environment, not `console.authdog.com`. - **Wrong environment** — `environmentId` must match the environment whose public key you copied. - **No session in your app after return** — expected. Elements do not store a session. Continue with [Next.js](/docs/quickstarts?sdk=nextjs) or a [backend SDK](/docs/backend). ## Next - [React Elements guide](/docs/frameworks/react): every component and prop. - [Account portal](/docs/account-portal): hosted profile and session UI. - [Next.js quickstart](/docs/quickstarts?sdk=nextjs): cookies and a protected route. ### Remix Source: https://www.authdog.com/docs/frameworks/remix.md The `@authdog/remix-node` SDK integrates Authdog with Remix through a callback-aware loader, client URL cleanup, and HttpOnly session cookies. ## Install ```package-install @authdog/remix-node ``` Requires `@remix-run/node` `^2.15`+ and React 18 or 19. Set `PK_AUTHDOG` (your environment's public key, `pk_...`) on the server. ## Wrap the app Run `identityLoader()` on the route users return to after hosted sign-in. The loader validates `?token=` through Authdog userinfo before setting HttpOnly cookies. `AuthdogProvider` removes the token after the server has handled the request: ```tsx // app/root.tsx import { Outlet, Scripts } from "@remix-run/react" import { AuthdogProvider, ReloadPage } from "@authdog/remix-node/client" import { identityLoader } from "@authdog/remix-node" export const loader = identityLoader() export default function App() { return ( ) } ``` ## Resolve the session in a loader The loader returns `{ user, isAuthenticated, signinUri }`. It revalidates cookie credentials through userinfo, but returning an unauthenticated result does not redirect or deny access. Enforce the result in each protected loader: ```tsx // app/routes/profile.tsx import { useLoaderData } from "@remix-run/react" import { identityLoader } from "@authdog/remix-node" import { redirect, type LoaderFunctionArgs } from "@remix-run/node" const loadIdentity = identityLoader() export const loader = async (args: LoaderFunctionArgs) => { const response = await loadIdentity(args) const identity = await response.json() if (!identity.isAuthenticated) throw redirect(identity.signinUri) return identity } export default function Profile() { const { user } = useLoaderData() return

Signed in as {user.emails?.[0]?.value}

} ``` If this protected loader may receive the initial callback, preserve the original `Response` headers when adapting it; otherwise its `Set-Cookie` headers are lost. A dedicated callback/root loader avoids that issue. ## Sign out Wire a route to `logoutLoader`, which clears the session and redirects: ```ts // app/routes/logout.ts import { logoutLoader } from "@authdog/remix-node" export const loader = logoutLoader ``` Pair the loader-resolved session with your [authorization](/docs/concepts/authorization) model to decide what a user may do. ## Next steps - [Component reference](/docs/components): the `@authdog/react-elements` UI (e.g. `UserProfile`). - [Backend requests](/docs/backend): the verification model, in depth. - [Quickstarts](/docs/quickstarts): the five-minute path. - [Support inbox](/docs/examples/remix): runnable example for this stack. ### React Native / Expo Source: https://www.authdog.com/docs/frameworks/expo.md The `@authdog/react-native` SDK brings Authdog to React Native and Expo apps: a provider, session and user hooks, sign-in/out helpers, and a secure-storage adapter. Sign-in uses a deep-link redirect that lands with a token. It reads the [session](/docs/concepts/sessions-tokens) Authdog issues. ## Install ```package-install @authdog/react-native npx expo install expo-secure-store ``` Requires React 18/19 and React Native `>=0.74`. There's no hard Expo dependency; `expo-secure-store` is only for persistent storage. Set `EXPO_PUBLIC_PK_AUTHDOG` to your environment's public key (`pk_...`). ## Wrap your app Pass the public key and a storage adapter. Without one, sessions are held in memory and don't survive a restart, so use the SecureStore adapter in real apps: ```tsx import * as SecureStore from "expo-secure-store" import { AuthdogProvider, createSecureStoreAdapter, } from "@authdog/react-native" export default function App() { return ( ) } ``` ## Sign in with a deep link `useSignIn` opens the hosted flow. Handle both cold-start and warm links; `useRedirectHandler` checks JWT structure before storing the token: ```tsx import { useEffect } from "react" import { Button, Linking } from "react-native" import { useSignIn, useRedirectHandler } from "@authdog/react-native" export function SignInButton() { const { signIn } = useSignIn() const { handleRedirect } = useRedirectHandler() useEffect(() => { Linking.getInitialURL().then((url) => url && handleRedirect(url)) const subscription = Linking.addEventListener("url", ({ url }) => { handleRedirect(url) }) return () => subscription.remove() }, [handleRedirect]) return } >
{JSON.stringify(user(), null, 2)}
) } ``` ## Sign out `signOut(redirectTo?)` clears the token from state and `localStorage`, and navigates to `redirectTo` if given. There is no server cookie to clear. ## Caveats - `isAuthenticated()` reflects token presence only. It is a UI hint, not validation. - The token lives in `localStorage`, and the SDK sends it only to the trusted `https:` identity host. - The package exposes no server helpers. Validate the bearer token with a [backend SDK](/docs/backend) and enforce [authorization](/docs/concepts/authorization) on every protected operation server-side. ## Next steps - [Sign up & sign in](/docs/authentication): the flows the provider drives. - [Backend requests](/docs/backend): validate sessions on your API. ### React Router Source: https://www.authdog.com/docs/frameworks/react-router.md The `@authdog/react-router` SDK provides a loader-based session, a logout loader, and a minimal client provider for React Router 7 in framework mode. Loaders and actions run on the Web Fetch API, so the SDK returns plain `Response` objects and needs no Node adapter. `@authdog/react-router` is not yet published to npm (`npm view @authdog/react-router` returns 404 at the time of writing). Version `0.1.0` is set to publish publicly from the `web-sdk` repository, so install it from there until it lands on the registry. ## Install ```package-install @authdog/react-router ``` `react` and `react-dom` (`^18.2` or `^19`) and `react-router` `^7` are peer dependencies. The package ships two entry points: `@authdog/react-router` (server loaders) and `@authdog/react-router/client` (provider). Set `PK_AUTHDOG` to your environment's public key (`pk_...`) in the server environment. ## Read the session in a loader `identityLoader()` reads `PK_AUTHDOG` and returns a loader. Read its result with `useLoaderData`: ```ts // app/routes/_index.tsx import { identityLoader } from "@authdog/react-router" export const loader = identityLoader() ``` ```tsx import { useLoaderData } from "react-router" export default function Index() { const { user, isAuthenticated, signinUri } = useLoaderData() return isAuthenticated ?

Hi {user.displayName}

: Sign in } ``` The loader returns `{ user, isAuthenticated, signinUri }`, where `signinUri` is the hosted sign-in URL for your environment. Behaviour: - If the request has a `?token=`, the loader resolves it through the Authdog userinfo endpoint. On success it sets two HttpOnly `Secure` `SameSite=Strict` cookies, `user_session_` (the user JSON) and `user_session_hash_` (the token), with no-store cache headers. - Otherwise it re-validates the session by calling userinfo with the token from `user_session_hash_`. - If neither succeeds, it returns `{ user: null, isAuthenticated: false, signinUri }`. The raw token never reaches client JavaScript. It throws if `PK_AUTHDOG` is missing, malformed, or points at an untrusted identity host. The cookies are `Secure`, so test over HTTPS or on `localhost`. ## Provider Mount `AuthdogProvider` at the app root. It takes no props besides `children`. When it sees a `?token=` in the URL, it strips the parameter and reloads once so the loader runs: ```tsx import { AuthdogProvider } from "@authdog/react-router/client" export default function App() { return {/* ... */} } ``` `ReloadPage` is also exported. It strips `?token=` from the URL without reloading. ## Sign out Export `logoutLoader` from a route. It clears both session cookies and redirects to `/`: ```ts // app/routes/logout.tsx import { logoutLoader } from "@authdog/react-router" export const loader = logoutLoader ``` For local development, `identityDevAction({ redirectTo })` clears the same cookies and redirects to a sanitized `redirectTo` (default `/`). It throws in production. ## Other exports `reactRouterAuthLoader`, `authenticateWithCookies`, `createAuthResponse`, `jsonResponse`, and `redirectResponse` are exported for custom loaders. Most apps need only `identityLoader`. ## Caveats - `isAuthenticated` means userinfo accepted the token on that request. Cookie presence alone is not treated as authentication. - The SDK does not protect routes for you. Check `isAuthenticated` in each loader that needs it, and enforce [authorization](/docs/concepts/authorization) in actions and API routes. - Verbose `[Authdog]` debug logging is enabled whenever `NODE_ENV` is not `production`. ## Next steps - [Sign up & sign in](/docs/authentication): the flows behind the redirect. - [Backend requests](/docs/backend): the verification model, in depth. ### JavaScript Source: https://www.authdog.com/docs/frameworks/javascript.md The `@authdog/javascript` SDK is a framework-agnostic browser client. It handles the OIDC redirect, token storage, sign-in and sign-out, and user resolution with no framework dependency. `@authdog/javascript` is not yet published to npm (`npm view @authdog/javascript` returns 404 at the time of writing). Version `0.1.0` is set to publish publicly from the `web-sdk` repository, so install it from there until it lands on the registry. ## Install ```package-install @authdog/javascript ``` No peer dependencies. It works in any browser bundle. Use your environment's public key (`pk_...`); it is safe to expose to the browser. ## Create the client `createAuthdogClient` validates the public key once and throws immediately if it is missing, malformed, or points at an untrusted identity host: ```ts import { createAuthdogClient } from "@authdog/javascript" const authdog = createAuthdogClient({ publicKey: "pk_...", // storageKey: "token", // optional localStorage key override }) // On the page the identity provider redirects back to, consume the token. authdog.handleRedirectCallback() const render = async () => { if (authdog.isAuthenticated()) { const user = await authdog.getUser() console.log("signed in as", user) } } render() signinButton.onclick = () => authdog.signIn() signupButton.onclick = () => authdog.signUp() signoutButton.onclick = () => authdog.signOut("/") // React to token changes in this tab and other tabs. const unsubscribe = authdog.subscribe(() => render()) ``` ## Client methods | Method | Behaviour | | --- | --- | | `handleRedirectCallback()` | Reads `?token=` from the URL, always strips it, and stores it in `localStorage` only if JWT-shaped. Returns the token or `null`. No-op without a token, so call it on every load | | `signIn(redirectUri?)` | Redirects to the OIDC `authorize` flow. `redirectUri` defaults to `window.location.origin` | | `signUp(redirectUri?)` | Same as `signIn`, with `prompt=signup` | | `signOut(redirectTo?)` | Clears the stored token and navigates to `redirectTo` if given | | `getUser()` | Calls the OIDC `userinfo` endpoint with the stored token. Returns the user, or `null` if unauthenticated or rejected. Never throws | | `getToken()` | The stored token, or `null` | | `isAuthenticated()` | Whether a token is present. Presence only | | `subscribe(listener)` | Calls `listener(token)` on token changes in this tab (custom `authdog:token-updated` event) and other tabs (`storage` event). Returns an unsubscribe function | | `getSigninUrl()` | The hosted sign-in URL, for use as a plain link target | | `getPublicKeyPayload()` / `getPublicKey()` | The parsed public-key payload as an object / as a JSON string | The default storage key is `token`. The `JWT_PATTERN` check is a shape test only. It is not signature, issuer, audience, or expiry validation. ## Billing The client also exposes `authdog.billing`: `listPlans({ for? })` (no auth required), `getSubscription()`, `createSubscription({ planId, planPeriod? })` (returns a Stripe PaymentIntent client secret), and `cancelSubscription()`. Authenticated calls return `null` or `false` when there is no token or the request fails. ## Other exports `buildAuthorizeUrl`, `buildSigninUrl`, `getTokenFromUri`, `isJwtShaped`, `getPublicKeyPayload`, `TOKEN_STORAGE_KEY`, `TOKEN_UPDATED_EVENT`, and `JWT_PATTERN` are exported for custom flows. ## Caveats - The token lives in `localStorage`. This suits public-client SPA flows. For server-rendered apps that can hold an HttpOnly cookie, use a server-side framework SDK. - `isAuthenticated()` is a UI hint. Validate the bearer token with a [backend SDK](/docs/backend) and enforce [authorization](/docs/concepts/authorization) on every protected operation server-side. - Outside a browser (no `window`), the client returns `null` or does nothing instead of throwing. ## Next steps - [Sign up & sign in](/docs/authentication): the flows behind the redirect. - [Backend requests](/docs/backend): validate sessions on your API. ## Backend SDKs ### Express Source: https://www.authdog.com/docs/backend/express.md Just want one protected route? Start with the [Express quickstart](/docs/quickstarts?sdk=express). `@authdog/express` resolves Authdog sessions for Express 4 and 5. It reads a cookie or bearer token, checks it through the environment's OIDC `userinfo` endpoint, and exposes the result to route handlers. ## Install ```package-install @authdog/express express ``` `@authdog/express` is published on npm. `express` is a peer dependency (`^4.18` or `^5`); `@authdog/node-commons` installs automatically. ## Configure Create one client with your environment's public key (`pk_...`): ```ts import express from "express" import { createAuthdog } from "@authdog/express" const app = express() const authdog = createAuthdog({ publicKey: process.env.PK_AUTHDOG! }) ``` The key is safe to expose. It is parsed at startup; malformed keys and identity hosts outside the trusted HTTPS allowlist fail immediately. ## Attach the session Mount `attachSession()` before routes: ```ts app.use(authdog.attachSession()) ``` It prefers the `authdog-session` cookie, then reads `Authorization: Bearer `. A valid token requires a successful `userinfo` response (`meta.code === 200` with a user). Missing, expired, malformed, or unverifiable tokens do not fail the request; they produce: ```ts { token: string | null, user: unknown | null, isAuthenticated: boolean, userInfo?: UserInfoResponse | null, } ``` `attachSession({ fetchUser: false })` only surfaces the unverified token. It deliberately leaves `user` null and `isAuthenticated` false, so the built-in `requireAuth` rejects the request with 401. Use this option only when separate server-side validation and enforcement replace Authdog's gate. ## Protect a route `attachSession` only records context. `requireAuth` is the security boundary and returns `401 {"error":"Unauthorized"}` unless `isAuthenticated` is true: ```ts app.get("/me", authdog.requireAuth, (req, res) => { res.json(req.authdog!.user) }) ``` Authentication identifies a caller; it does not authorize actions. Apply your [authorization](/docs/concepts/authorization) checks after `requireAuth`. Client-side checks never protect server routes. ## Sign out `logout` expires the local cookie and redirects to a sanitized same-origin `redirect_uri`: ```ts app.get("/logout", authdog.logout) ``` This does not revoke a bearer token or end an upstream identity-provider session. ## Next steps - [Backend requests](/docs/backend) - [Fastify](/docs/backend/fastify) - [Calling the REST API](/docs/api) - [Orders API](/docs/examples/express): runnable example for this stack. ### Express quickstart Source: https://www.authdog.com/docs/quickstarts/express.md Outcome: an Express app that rejects anonymous callers on `GET /me` and returns the Authdog user after a valid session. About five minutes. This is a **backend** gate. Hosted sign-in still happens in a browser (Account portal or a frontend SDK). This page only validates the session Express receives. 0. Prerequisites: Node 20+ and an Authdog environment. You will set `PK_AUTHDOG` to that environment's public key. ## 1. Create an environment and copy the public key In the [console](https://console.authdog.com): 1. Create a **tenant**, a **project**, and an **environment**. 2. Open **Dashboard** (`/dashboard/home`) and copy **Public key** (`pk_...`). The public key is publishable. Do not put an `adenv_` API secret in this process unless you are calling Vault or another privileged API. Details: [Environments](/docs/console/environments). ## 2. Install the SDK ```package-install @authdog/express express ``` `@authdog/express` is on npm. Express `^4.18` or `^5` is a peer. ## 3. Set the env var ```bash PK_AUTHDOG=pk_... ``` ## 4. Create the client and attach the session ```ts import express from "express" import { createAuthdog } from "@authdog/express" const app = express() const authdog = createAuthdog({ publicKey: process.env.PK_AUTHDOG! }) app.use(authdog.attachSession()) ``` `attachSession` records context. It does not return 401 on its own. ## 5. Protect one route ```ts app.get("/me", authdog.requireAuth, (req, res) => { res.json(req.authdog!.user) }) app.listen(3000) ``` `requireAuth` is the security boundary. Missing or invalid sessions get `401 {"error":"Unauthorized"}`. ## 6. Verify Send a request **without** a session. You should get 401. Send the same request with a valid `authdog-session` cookie or `Authorization: Bearer ` from a completed hosted sign-in. You should get the user JSON. If it fails: - **401 with a cookie you just set** — the public key is from a different environment than the one that minted the session. - **User is always null** — you used `attachSession` without `requireAuth`, or `fetchUser: false`. - **Startup throw** — malformed `pk_...` or an identity host outside the trusted HTTPS allowlist. ## Next The [Express guide](/docs/backend/express) covers logout, `fetchUser: false`, and what authentication does not authorize. Apply [authorization](/docs/concepts/authorization) after `requireAuth`. ### Fastify Source: https://www.authdog.com/docs/backend/fastify.md `@authdog/fastify` resolves Authdog sessions on Fastify 4 and 5. Its plugin decorates requests with session context and exposes an authentication guard. ## Install ```package-install @authdog/fastify fastify ``` `@authdog/fastify` is published on npm. `fastify` is a peer dependency (`^4` or `^5`); `@authdog/node-commons` and `fastify-plugin` install automatically. `@fastify/cookie` is not required. ## Register the plugin Register once with your environment's public key (`pk_...`): ```ts import Fastify from "fastify" import { authdogPlugin } from "@authdog/fastify" const app = Fastify() await app.register(authdogPlugin, { publicKey: process.env.PK_AUTHDOG! }) ``` The key is safe to expose. Registration rejects malformed keys and identity hosts outside the trusted HTTPS allowlist. On each request, the plugin prefers the `authdog-session` cookie, then reads `Authorization: Bearer `. It calls the environment's OIDC `userinfo` endpoint and sets `request.authdog` to `{ token, user, isAuthenticated }`. Only a success envelope (`meta.code === 200` with a user) authenticates the request. Missing, invalid, or unverifiable tokens yield anonymous context instead of an application error. `fetchUserInfo: false` skips `userinfo` but does not authenticate token presence. `token` may be set while `user` remains null and `isAuthenticated` remains false. Built-in `requireAuth` therefore returns 401; use this option only with separate server-side validation and enforcement. ## Protect a route Use `app.authdog.requireAuth` as a `preHandler`. It is the security boundary and returns `401 {"error":"Unauthorized"}` for anonymous context: ```ts app.get( "/me", { preHandler: app.authdog.requireAuth }, async (req) => req.authdog!.user, ) ``` Authentication identifies a caller; it does not authorize actions. Apply [authorization](/docs/concepts/authorization) checks after this guard. Client-side checks never protect server routes. ## Sign out `app.authdog.logout` clears the local cookie and redirects to a sanitized same-origin `redirect_uri`: ```ts app.get("/logout", (req, reply) => app.authdog.logout(req, reply)) ``` This does not revoke a bearer token or end an upstream identity-provider session. ## Next steps - [Backend requests](/docs/backend) - [Express](/docs/backend/express) - [Calling the REST API](/docs/api) - [Inventory API](/docs/examples/fastify): runnable example for this stack. ### FastAPI Source: https://www.authdog.com/docs/backend/fastapi.md Just want one protected route? Start with the [FastAPI quickstart](/docs/quickstarts?sdk=fastapi). `authdog.fastapi` exposes session context and a 401 gate as FastAPI dependencies. Other Python frameworks have their own guides: [Django](/docs/backend/django), [Flask](/docs/backend/flask), [Starlette](/docs/backend/starlette), [aiohttp](/docs/backend/aiohttp). Hub: [Python](/docs/backend/python). ## Availability and install **Unreleased on PyPI.** Source-only in [`packages/python`](https://github.com/authdog/web-sdk/tree/main/packages/python). Python 3.10+, FastAPI 0.110+, `httpx` 0.27+. `pip install authdog` is the [management client](/docs/sdks/python), not this binding. ```bash python -m pip install "./packages/python[fastapi]" ``` Pin a commit. Published compatibility is not yet guaranteed. ## Configure ```python import os from authdog.fastapi import Authdog authdog = Authdog(public_key=os.environ["PK_AUTHDOG"]) ``` The key is publishable. Construction rejects malformed keys and identity hosts outside the trusted HTTPS allowlist. ## Session and gate `session` is optional context. `require_auth` is the 401 dependency. Context is cached on `request.state`, so composing both costs one `userinfo` call. ```python from fastapi import Depends, FastAPI, Request app = FastAPI() @app.get("/") async def index(ctx=Depends(authdog.session)): return {"authenticated": ctx.is_authenticated} @app.get("/me") async def me(user=Depends(authdog.require_auth)): return user @app.get("/logout") async def logout(request: Request): return authdog.logout(request) ``` `logout(request)` expires the cookie and redirects to a sanitized `redirect_uri`. ## Shared rules The resolver prefers `authdog-session`, then `Authorization: Bearer `. Failed `userinfo` is anonymous. `fetch_user=False` leaves `is_authenticated` false, so `require_auth` rejects. Authentication is not authorization — apply [authorization](/docs/concepts/authorization) after the gate. Self-hosted identity hosts need `AUTHDOG_ALLOWED_IDENTITY_HOSTS`. ## Example - [Booking API](/docs/examples/fastapi): runnable example for this stack. ### FastAPI quickstart Source: https://www.authdog.com/docs/quickstarts/fastapi.md Outcome: a FastAPI route that returns the Authdog user only when `require_auth` succeeds. About five minutes. **Unreleased:** not on PyPI. Install from the public source tree. Do not run `pip install authdog-fastapi` in production. 0. Prerequisites: Python 3.10+ and an Authdog environment. ## 1. Create an environment and copy the public key In the [console](https://console.authdog.com): 1. Create a **tenant**, a **project**, and an **environment**. 2. Open **Dashboard** (`/dashboard/home`) and copy **Public key** (`pk_...`). The public key is publishable. Details: [Environments](/docs/console/environments). ## 2. Install from source ```bash python -m pip install "./packages/python[fastapi]" ``` Clone or pin [`packages/python`](https://github.com/authdog/web-sdk/tree/main/packages/python). FastAPI 0.110+. ## 3. Set the env var ```bash export PK_AUTHDOG="pk_..." ``` ## 4. Construct the client ```python import os from authdog.fastapi import Authdog authdog = Authdog(public_key=os.environ["PK_AUTHDOG"]) ``` ## 5. Protect one route ```python from fastapi import Depends, FastAPI app = FastAPI() @app.get("/me") async def me(user=Depends(authdog.require_auth)): return user ``` ## 6. Verify `GET /me` without a session should be 401. With a valid `authdog-session` cookie or `Authorization: Bearer ` from hosted sign-in, you should get the user. If it fails: wrong environment key; used `session` instead of `require_auth`; or pip looked on PyPI (use the source extra). ## Next The [FastAPI guide](/docs/backend/fastapi) covers logout, caching, and `fetch_user=False`. Other Python frameworks: [Django](/docs/quickstarts?sdk=django), [Flask](/docs/quickstarts?sdk=flask), [Starlette](/docs/quickstarts?sdk=starlette), [aiohttp](/docs/quickstarts?sdk=aiohttp). ### Django Source: https://www.authdog.com/docs/backend/django.md Just want one protected view? Start with the [Django quickstart](/docs/quickstarts?sdk=django). `authdog.django` is middleware plus a `require_auth` view decorator. Other Python frameworks: [FastAPI](/docs/backend/fastapi), [Flask](/docs/backend/flask), [Starlette](/docs/backend/starlette), [aiohttp](/docs/backend/aiohttp). Hub: [Python](/docs/backend/python). ## Availability and install **Unreleased on PyPI.** Source-only in [`packages/python`](https://github.com/authdog/web-sdk/tree/main/packages/python). Python 3.10+, Django 4.2+, `httpx` 0.27+. ```bash python -m pip install "./packages/python[django]" ``` `import authdog.django` is lazy: Django does not need to be configured until a binding is called. ## Configure ```python # auth.py import os from authdog.django import Authdog authdog = Authdog(public_key=os.environ["PK_AUTHDOG"]) ``` ```python # settings.py MIDDLEWARE = [ # ... "myapp.auth.authdog.middleware", ] ``` The public key is publishable. Construction rejects malformed keys and identity hosts outside the trusted HTTPS allowlist. ## Session and gate Middleware puts a resolved context on `request.authdog_context`. It never raises. `session(request)` reads that context from any view. `@require_auth` is the 401 boundary (`JsonResponse({"error": "Unauthorized"}, status=401)`). Django drives `userinfo` with `asyncio.run` on the sync view path. ```python from django.http import JsonResponse @authdog.require_auth def me(request): return JsonResponse(authdog.session(request).user) def logout(request): return authdog.logout(request) ``` Middleware, decorator, and `session()` share one resolved context per request. ## Shared rules The resolver prefers `authdog-session`, then `Authorization: Bearer `. `fetch_user=False` leaves `is_authenticated` false, so the decorator rejects. Apply [authorization](/docs/concepts/authorization) after the gate. Self-hosted identity hosts need `AUTHDOG_ALLOWED_IDENTITY_HOSTS`. ## Example - [Clinic portal](/docs/examples/django): runnable example for this stack. ### Django quickstart Source: https://www.authdog.com/docs/quickstarts/django.md Outcome: a Django view that returns 401 unless Authdog authenticates the caller. About five minutes. **Unreleased:** not on PyPI. Install from the public source tree. 0. Prerequisites: Python 3.10+, Django 4.2+, and an Authdog environment. ## 1. Create an environment and copy the public key In the [console](https://console.authdog.com), create a tenant → project → environment and copy **Public key** from **Dashboard**. Details: [Environments](/docs/console/environments). ## 2. Install from source ```bash python -m pip install "./packages/python[django]" ``` Pin [`packages/python`](https://github.com/authdog/web-sdk/tree/main/packages/python). ## 3. Set the env var ```bash export PK_AUTHDOG="pk_..." ``` ## 4. Create the client and register middleware ```python # auth.py import os from authdog.django import Authdog authdog = Authdog(public_key=os.environ["PK_AUTHDOG"]) ``` ```python # settings.py MIDDLEWARE = [ # ... "myapp.auth.authdog.middleware", ] ``` ## 5. Protect one view ```python from django.http import JsonResponse @authdog.require_auth def me(request): return JsonResponse(authdog.session(request).user) ``` ## 6. Verify `GET` the view without a session → 401. With a valid cookie or bearer token from hosted sign-in → user JSON. If it fails: middleware path wrong; `PK_AUTHDOG` from another environment; decorated without middleware (session still resolves, but register middleware so the context is shared). ## Next The [Django guide](/docs/backend/django) covers logout and `request.authdog_context`. Other Python frameworks: [FastAPI](/docs/quickstarts?sdk=fastapi), [Flask](/docs/quickstarts?sdk=flask), [Starlette](/docs/quickstarts?sdk=starlette), [aiohttp](/docs/quickstarts?sdk=aiohttp). ### Flask Source: https://www.authdog.com/docs/backend/flask.md Just want one protected view? Start with the [Flask quickstart](/docs/quickstarts?sdk=flask). `authdog.flask` caches session context on `flask.g` and exposes a `require_auth` decorator. Other Python frameworks: [FastAPI](/docs/backend/fastapi), [Django](/docs/backend/django), [Starlette](/docs/backend/starlette), [aiohttp](/docs/backend/aiohttp). Hub: [Python](/docs/backend/python). ## Availability and install **Unreleased on PyPI.** Source-only in [`packages/python`](https://github.com/authdog/web-sdk/tree/main/packages/python). Python 3.10+, Flask 3.0+, `httpx` 0.27+. ```bash python -m pip install "./packages/python[flask]" ``` ## Configure ```python import os from flask import Flask from authdog.flask import Authdog app = Flask(__name__) authdog = Authdog(public_key=os.environ["PK_AUTHDOG"]) ``` The public key is publishable. Construction rejects malformed keys and identity hosts outside the trusted HTTPS allowlist. ## Session and gate `session()` returns a typed context from any view and caches it on `flask.g`. Repeat calls in one request do not hit `userinfo` again. `@require_auth` is the 401 boundary. Flask drives `userinfo` with `asyncio.run` on the sync view path. ```python @app.get("/") def index(): return {"authenticated": authdog.session().is_authenticated} @app.get("/me") @authdog.require_auth def me(): return authdog.session().user @app.get("/logout") def logout(): return authdog.logout() ``` `logout()` (no request argument) returns a redirect `Response` that expires the cookie. Works with blueprints as ordinary view decorators. ## Shared rules The resolver prefers `authdog-session`, then `Authorization: Bearer `. `fetch_user=False` leaves `is_authenticated` false, so the decorator rejects. Apply [authorization](/docs/concepts/authorization) after the gate. Self-hosted identity hosts need `AUTHDOG_ALLOWED_IDENTITY_HOSTS`. ## Example - [Status board](/docs/examples/flask): runnable example for this stack. ### Flask quickstart Source: https://www.authdog.com/docs/quickstarts/flask.md Outcome: a Flask view that returns 401 unless Authdog authenticates the caller. About five minutes. **Unreleased:** not on PyPI. Install from the public source tree. 0. Prerequisites: Python 3.10+, Flask 3.0+, and an Authdog environment. ## 1. Create an environment and copy the public key In the [console](https://console.authdog.com), create a tenant → project → environment and copy **Public key** from **Dashboard**. Details: [Environments](/docs/console/environments). ## 2. Install from source ```bash python -m pip install "./packages/python[flask]" ``` Pin [`packages/python`](https://github.com/authdog/web-sdk/tree/main/packages/python). ## 3. Set the env var ```bash export PK_AUTHDOG="pk_..." ``` ## 4. Construct the client ```python import os from flask import Flask from authdog.flask import Authdog app = Flask(__name__) authdog = Authdog(public_key=os.environ["PK_AUTHDOG"]) ``` ## 5. Protect one view ```python @app.get("/me") @authdog.require_auth def me(): return authdog.session().user ``` ## 6. Verify `GET /me` without a session → 401. With a valid cookie or bearer token from hosted sign-in → user JSON. If it fails: decorator order (route above `require_auth`); wrong environment key; used `session()` alone as a gate. ## Next The [Flask guide](/docs/backend/flask) covers `flask.g` caching and logout. Other Python frameworks: [FastAPI](/docs/quickstarts?sdk=fastapi), [Django](/docs/quickstarts?sdk=django), [Starlette](/docs/quickstarts?sdk=starlette), [aiohttp](/docs/quickstarts?sdk=aiohttp). ### Starlette Source: https://www.authdog.com/docs/backend/starlette.md Just want one protected route? Start with the [Starlette quickstart](/docs/quickstarts?sdk=starlette). `authdog.starlette` is ASGI middleware plus async `session` / `require_auth` for **pure Starlette**. FastAPI has its own binding: [FastAPI](/docs/backend/fastapi). Also: [Django](/docs/backend/django), [Flask](/docs/backend/flask), [aiohttp](/docs/backend/aiohttp). Hub: [Python](/docs/backend/python). ## Availability and install **Unreleased on PyPI.** Source-only in [`packages/python`](https://github.com/authdog/web-sdk/tree/main/packages/python). Python 3.10+, Starlette 0.37+, `httpx` 0.27+. ```bash python -m pip install "./packages/python[starlette]" ``` ## Configure ```python import os from authdog.starlette import Authdog authdog = Authdog(public_key=os.environ["PK_AUTHDOG"]) ``` Add `authdog.middleware` to the Starlette app. The public key is publishable. Construction rejects malformed keys and identity hosts outside the trusted HTTPS allowlist. ## Session and gate Bindings are **async**. `await session(request)` is optional context. `await require_auth(request)` is the 401 boundary. ```python from starlette.applications import Starlette from starlette.responses import JSONResponse from starlette.routing import Route async def index(request): ctx = await authdog.session(request) return JSONResponse({"authenticated": ctx.is_authenticated}) async def me(request): user = await authdog.require_auth(request) return JSONResponse(user) async def logout(request): return authdog.logout(request) app = Starlette( routes=[ Route("/", index), Route("/me", me), Route("/logout", logout), ], middleware=[authdog.middleware], ) ``` ## Shared rules The resolver prefers `authdog-session`, then `Authorization: Bearer `. `fetch_user=False` leaves `is_authenticated` false, so `require_auth` rejects. Apply [authorization](/docs/concepts/authorization) after the gate. Self-hosted identity hosts need `AUTHDOG_ALLOWED_IDENTITY_HOSTS`. ## Example - [Intake service](/docs/examples/starlette): runnable example for this stack. ### Starlette quickstart Source: https://www.authdog.com/docs/quickstarts/starlette.md Outcome: a Starlette route that returns 401 unless `require_auth` succeeds. About five minutes. This is **pure Starlette**. FastAPI has a [separate quickstart](/docs/quickstarts?sdk=fastapi). **Unreleased:** not on PyPI. Install from the public source tree. 0. Prerequisites: Python 3.10+, Starlette 0.37+, and an Authdog environment. ## 1. Create an environment and copy the public key In the [console](https://console.authdog.com), create a tenant → project → environment and copy **Public key** from **Dashboard**. Details: [Environments](/docs/console/environments). ## 2. Install from source ```bash python -m pip install "./packages/python[starlette]" ``` Pin [`packages/python`](https://github.com/authdog/web-sdk/tree/main/packages/python). ## 3. Set the env var ```bash export PK_AUTHDOG="pk_..." ``` ## 4. Construct the client ```python import os from authdog.starlette import Authdog authdog = Authdog(public_key=os.environ["PK_AUTHDOG"]) ``` ## 5. Protect one route ```python from starlette.applications import Starlette from starlette.responses import JSONResponse from starlette.routing import Route async def me(request): user = await authdog.require_auth(request) return JSONResponse(user) app = Starlette( routes=[Route("/me", me)], middleware=[authdog.middleware], ) ``` ## 6. Verify `GET /me` without a session → 401. With a valid cookie or bearer token from hosted sign-in → user JSON. If it fails: forgot `authdog.middleware`; used FastAPI `Depends` instead of `await require_auth`; wrong environment key. ## Next The [Starlette guide](/docs/backend/starlette) covers logout and async session. Other Python frameworks: [FastAPI](/docs/quickstarts?sdk=fastapi), [Django](/docs/quickstarts?sdk=django), [Flask](/docs/quickstarts?sdk=flask), [aiohttp](/docs/quickstarts?sdk=aiohttp). ### aiohttp Source: https://www.authdog.com/docs/backend/aiohttp.md Just want one protected handler? Start with the [aiohttp quickstart](/docs/quickstarts?sdk=aiohttp). `authdog.aiohttp` is middleware plus a `require_auth` wrapper for aiohttp handlers. Other Python frameworks: [FastAPI](/docs/backend/fastapi), [Django](/docs/backend/django), [Flask](/docs/backend/flask), [Starlette](/docs/backend/starlette). Hub: [Python](/docs/backend/python). ## Availability and install **Unreleased on PyPI.** Source-only in [`packages/python`](https://github.com/authdog/web-sdk/tree/main/packages/python). Python 3.10+, aiohttp 3.9+, `httpx` 0.27+. ```bash python -m pip install "./packages/python[aiohttp]" ``` ## Configure ```python import os from authdog.aiohttp import Authdog authdog = Authdog(public_key=os.environ["PK_AUTHDOG"]) ``` Add `authdog.middleware` to the aiohttp application. The public key is publishable. Construction rejects malformed keys and identity hosts outside the trusted HTTPS allowlist. ## Session and gate Bindings are **async**. `await session(request)` is optional context. `@require_auth` is the 401 boundary. ```python from aiohttp import web async def index(request): ctx = await authdog.session(request) return web.json_response({"authenticated": ctx.is_authenticated}) @authdog.require_auth async def me(request): ctx = await authdog.session(request) return web.json_response(ctx.user) async def logout(request): return authdog.logout(request) app = web.Application(middlewares=[authdog.middleware]) app.router.add_get("/", index) app.router.add_get("/me", me) app.router.add_get("/logout", logout) ``` `logout` raises aiohttp `HTTPFound`, following that framework’s redirect convention. ## Shared rules The resolver prefers `authdog-session`, then `Authorization: Bearer `. `fetch_user=False` leaves `is_authenticated` false, so the wrapper rejects. Apply [authorization](/docs/concepts/authorization) after the gate. Self-hosted identity hosts need `AUTHDOG_ALLOWED_IDENTITY_HOSTS`. ## Example - [Event ingest](/docs/examples/aiohttp): runnable example for this stack. ### aiohttp quickstart Source: https://www.authdog.com/docs/quickstarts/aiohttp.md Outcome: an aiohttp handler that returns 401 unless Authdog authenticates the caller. About five minutes. **Unreleased:** not on PyPI. Install from the public source tree. 0. Prerequisites: Python 3.10+, aiohttp 3.9+, and an Authdog environment. ## 1. Create an environment and copy the public key In the [console](https://console.authdog.com), create a tenant → project → environment and copy **Public key** from **Dashboard**. Details: [Environments](/docs/console/environments). ## 2. Install from source ```bash python -m pip install "./packages/python[aiohttp]" ``` Pin [`packages/python`](https://github.com/authdog/web-sdk/tree/main/packages/python). ## 3. Set the env var ```bash export PK_AUTHDOG="pk_..." ``` ## 4. Construct the client ```python import os from authdog.aiohttp import Authdog authdog = Authdog(public_key=os.environ["PK_AUTHDOG"]) ``` ## 5. Protect one handler ```python from aiohttp import web @authdog.require_auth async def me(request): ctx = await authdog.session(request) return web.json_response(ctx.user) app = web.Application(middlewares=[authdog.middleware]) app.router.add_get("/me", me) ``` ## 6. Verify `GET /me` without a session → 401. With a valid cookie or bearer token from hosted sign-in → user JSON. If it fails: middleware not registered; wrong environment key; expected FastAPI `Depends` (this binding is a decorator plus `await session`). ## Next The [aiohttp guide](/docs/backend/aiohttp) covers logout (`HTTPFound`) and async session. Other Python frameworks: [FastAPI](/docs/quickstarts?sdk=fastapi), [Django](/docs/quickstarts?sdk=django), [Flask](/docs/quickstarts?sdk=flask), [Starlette](/docs/quickstarts?sdk=starlette). ### Go Source: https://www.authdog.com/docs/backend/go.md Just want one protected Gin route? Start with the [Go quickstart](/docs/quickstarts?sdk=go). Authdog's Go module resolves sessions for Go web services. **Gin is the only framework with a ready-made adapter.** `net/http`, chi, Echo, and other routers require manual integration with core functions. ## Availability and install ```bash go get github.com/authdog/web-sdk/packages/go@latest ``` ```go import authdog "github.com/authdog/web-sdk/packages/go" ``` The module is public source, has no tagged module versions, and currently resolves as an untagged pseudo-version. Pin a commit for reproducible builds. It declares Go 1.25 and directly depends on Gin 1.10. ## Configure Create one client with your environment's public key (`pk_...`): ```go ad, err := authdog.New(authdog.Config{ PublicKey: os.Getenv("PK_AUTHDOG"), }) if err != nil { log.Fatal(err) } ``` The key is safe to expose. `New` rejects malformed keys and identity hosts outside the trusted HTTPS allowlist. `Config.HTTPClient` can provide timeout, transport, and observability policy. ## Attach and protect (Gin) Mount `AttachSession()` before routes and `RequireAuth()` on protected routes: ```go r := gin.Default() r.Use(ad.AttachSession()) r.GET("/me", ad.RequireAuth(), func(c *gin.Context) { c.JSON(http.StatusOK, authdog.FromGin(c).User) }) r.GET("/logout", ad.Logout) r.Run(":3000") ``` `AttachSession` prefers the `authdog-session` cookie, then reads `Authorization: Bearer `. It calls the environment's OIDC `userinfo` endpoint and stores: ```go type Context struct { Token string User any IsAuthenticated bool UserInfo *UserInfoResponse } ``` Missing, invalid, or unverifiable tokens yield anonymous context and do not abort. Only `meta.code == 200` with a user sets `IsAuthenticated`. `RequireAuth` is the authentication boundary and returns `401 {"error":"Unauthorized"}` otherwise. To disable `userinfo`: ```go fetchUser := false ad, err := authdog.New(authdog.Config{ PublicKey: os.Getenv("PK_AUTHDOG"), FetchUser: &fetchUser, }) ``` With `FetchUser` disabled, only `Context.Token` is populated; `User` remains nil and `IsAuthenticated` remains false. Built-in `RequireAuth` therefore rejects every such token. Use this mode only when separate server-side validation and enforcement replace Authdog's gate. ## Other routers No `net/http`, chi, or Echo middleware is shipped. Manual adapters can compose `ValidateAndParsePublicKey`, `GetSessionToken`, `FetchUserData`, `IsAuthenticatedUserInfo`, and `SanitizeRedirectPath`. Your adapter must propagate request cancellation, treat lookup failures as unauthenticated, and enforce authentication before protected handlers. ## Security boundaries - `AttachSession` is informational; `RequireAuth` is the Gin security boundary. - Authentication does not grant application permissions. Apply [authorization](/docs/concepts/authorization) separately. - Bearer tokens are sent only to a trusted HTTPS identity host. Self-hosted hosts require explicit `AUTHDOG_ALLOWED_IDENTITY_HOSTS` entries. - `Logout` clears the local cookie and sanitizes `redirect_uri`; it does not revoke bearer tokens or end an upstream provider session. ## Next steps - [Backend requests](/docs/backend) - [Calling the REST API](/docs/api) - [Billing service](/docs/examples/go): runnable example for this stack. ### Go quickstart Source: https://www.authdog.com/docs/quickstarts/go.md Outcome: a Gin service that returns 401 on `GET /me` unless Authdog authenticates the caller. About five minutes. **Gin is the only shipped adapter.** `net/http`, chi, and Echo need a manual adapter. The Go module has no tagged versions; pin a commit. 0. Prerequisites: Go 1.25+ and an Authdog environment. ## 1. Create an environment and copy the public key In the [console](https://console.authdog.com): 1. Create a **tenant**, a **project**, and an **environment**. 2. Open **Dashboard** (`/dashboard/home`) and copy **Public key** (`pk_...`). The public key is publishable. Details: [Environments](/docs/console/environments). ## 2. Install the module ```bash go get github.com/authdog/web-sdk/packages/go@latest ``` Pin the resolved pseudo-version in `go.mod` for reproducible builds. ## 3. Set the env var ```bash export PK_AUTHDOG="pk_..." ``` ## 4. Create the client ```go ad, err := authdog.New(authdog.Config{ PublicKey: os.Getenv("PK_AUTHDOG"), }) if err != nil { log.Fatal(err) } ``` `New` rejects malformed keys and identity hosts outside the trusted HTTPS allowlist. ## 5. Protect one Gin route ```go r := gin.Default() r.Use(ad.AttachSession()) r.GET("/me", ad.RequireAuth(), func(c *gin.Context) { c.JSON(http.StatusOK, authdog.FromGin(c).User) }) r.Run(":3000") ``` `AttachSession` is informational. `RequireAuth` is the 401 boundary. ## 6. Verify `GET /me` without a session should be `401 {"error":"Unauthorized"}`. `GET /me` with a valid `authdog-session` cookie or `Authorization: Bearer ` from hosted sign-in should return the user. If it fails: - **401 after a real sign-in** — `PK_AUTHDOG` is from another environment. - **Compile against another router** — there is no chi/Echo/net/http middleware. See the [Go guide](/docs/backend/go). - **Floating latest** — pin a commit; `@latest` is an untagged pseudo-version. ## Next The [Go guide](/docs/backend/go) covers `FetchUser`, logout, and manual adapters. Apply [authorization](/docs/concepts/authorization) after `RequireAuth`. ### Rust Source: https://www.authdog.com/docs/backend/rust.md Authdog's Rust source provides a shared session core and adapters for axum, actix-web, Rocket, warp, and Poem. ## Availability **Unreleased:** `authdog-core`, `authdog-axum`, `authdog-actix`, `authdog-rocket`, `authdog-warp`, and `authdog-poem` are not available on crates.io. Commands such as `cargo add authdog-axum` will fail. Current code is source-only in the [`packages/rust` workspace](https://github.com/authdog/web-sdk/tree/main/packages/rust), versioned `0.1.0`. Vendor or pin a repository commit and use Cargo path dependencies from that checkout. Do not rely on the unshipped crates.io version declarations in its manifests. The workspace targets Rust 1.75+ and edition 2021. Current manifests integrate axum 0.8, actix-web 4, Rocket 0.5, warp 0.3, and Poem 3. Published compatibility and semver stability are not yet guaranteed. ## Configure Every adapter exports `Authdog`. Create one instance from your environment's public key (`pk_...`): ```rust let authdog = Authdog::new(&std::env::var("PK_AUTHDOG")?)?; ``` The key is safe to expose. Construction rejects malformed keys and identity hosts outside the trusted HTTPS allowlist. A custom `reqwest::Client` can supply timeout, transport, and observability policy: ```rust let authdog = Authdog::builder(&pk).http_client(client).build()?; ``` Each adapter resolves: ```rust AuthContext { token: Option, user: Option, is_authenticated: bool, user_info: Option, } ``` Resolution prefers the `authdog-session` cookie, then reads `Authorization: Bearer `. Missing, invalid, or failed OIDC `userinfo` requests produce anonymous context. Only `meta.code == 200` with a user sets `is_authenticated`. `Authdog::builder(&pk).fetch_user(false).build()` only exposes the unverified token. `user` and `user_info` remain `None`, and `is_authenticated` remains false. Every built-in guard therefore rejects it; use this mode only when separate server-side validation and enforcement replace Authdog's gate. ## Framework integration | Framework | Optional session | Authentication boundary | Setup | | --- | --- | --- | --- | | axum | `AuthContext` extractor after `attach_session` | `require_auth` middleware | Add state and `from_fn_with_state(..., attach_session)`; layer guard on protected routes | | actix-web | `AuthContext` extractor | `RequireAuth` extractor | Register `web::Data` | | Rocket | `AuthContext` request guard | `RequireAuth` guard | Manage `Authdog`; register `unauthorized` catcher for JSON 401 | | warp | `with_session(authdog)` filter | `require_auth(authdog)` filter | Recover `Unauthorized` with `recover_unauthorized` | | Poem | `AuthContext` extractor | `RequireAuth` extractor | Attach `Authdog` with `.data(authdog)` | Example axum wiring: ```rust use authdog_axum::{attach_session, require_auth, AuthContext, Authdog}; use axum::{middleware, routing::get, Router}; let authdog = Authdog::new(&std::env::var("PK_AUTHDOG")?)?; let app = Router::new() .route("/me", get(|ctx: AuthContext| async move { format!("{:?}", ctx.user) }) .layer(middleware::from_fn(require_auth))) .layer(middleware::from_fn_with_state(authdog.clone(), attach_session)) .with_state(authdog); ``` ## Security boundaries - Optional context is informational; each framework's guard above is the authentication boundary. - Authentication does not grant application permissions. Apply [authorization](/docs/concepts/authorization) separately. - Bearer tokens are sent only to a trusted HTTPS identity host. Self-hosted hosts require explicit `AUTHDOG_ALLOWED_IDENTITY_HOSTS` entries. - Logout handlers clear the local cookie and sanitize `redirect_uri`; they do not revoke bearer tokens or end an upstream provider session. ## Next steps - [Backend requests](/docs/backend) - [Calling the REST API](/docs/api) - [Policy service](/docs/examples/rust): runnable example for this stack. ### Hono Source: https://www.authdog.com/docs/backend/hono.md `@authdog/hono` resolves Authdog sessions for Hono. Its middleware stores session context on the Hono context and exposes an authentication guard. ## Availability **Unreleased:** `@authdog/hono` is not on npm at the time of writing (the registry returns 404). `bun add @authdog/hono` will fail until it is published. Source lives in the [`packages/hono` directory](https://github.com/authdog/web-sdk/tree/main/packages/hono) of the web SDK repository (version `0.3.0`, configured to publish to npm with public access). Vendor a repository checkout until a release ships. ## Install ```package-install @authdog/hono hono ``` `hono` is a peer dependency (`^4`); `@authdog/node-commons` installs automatically. Hono runs on Bun, Node.js, Cloudflare Workers, and Deno. ## Configure Create one instance with your environment's public key (`pk_...`): ```ts import { Hono } from "hono" import { createAuthdog } from "@authdog/hono" const app = new Hono() const authdog = createAuthdog({ publicKey: process.env.PK_AUTHDOG! }) ``` The key is safe to expose. It is parsed once at startup; a missing key throws `Public key is not defined`, and malformed keys or identity hosts outside the trusted HTTPS allowlist fail immediately. `createAuthdog` also accepts an optional `secretKey`, which is reserved and currently unused. ## Attach the session Mount `attachSession()` before routes: ```ts app.use(authdog.attachSession()) ``` It prefers the `authdog-session` cookie, then reads `Authorization: Bearer `. A valid token requires a successful `userinfo` response (`meta.code === 200` with a user). Missing, invalid, or unverifiable tokens never fail the request or return a 500; they yield anonymous context stored as `c.get("authdog")`: ```ts { token: string | null, user: unknown | null, isAuthenticated: boolean, userInfo?: UserInfoResponse | null, } ``` `attachSession({ fetchUser: false })` only surfaces the unverified token. It leaves `user` null and `isAuthenticated` false, so `requireAuth` rejects the request with 401. Use this option only when separate server-side validation and enforcement replace Authdog's gate. ## Protect a route `requireAuth` is the security boundary and returns `401 {"error":"Unauthorized"}` unless `isAuthenticated` is true: ```ts app.get("/me", authdog.requireAuth, (c) => c.json(c.get("authdog")!.user)) ``` Authentication identifies a caller; it does not authorize actions. Apply your [authorization](/docs/concepts/authorization) checks after `requireAuth`. Client-side checks never protect server routes. ## Sign out `logout` expires the `authdog-session` cookie (`HttpOnly`, `SameSite=Lax`, `Secure` when `NODE_ENV` is `production`) and redirects (302) to a sanitized same-origin `redirect_uri`, falling back to `/`: ```ts app.get("/logout", authdog.logout) ``` This does not revoke a bearer token or end an upstream identity-provider session. ## Next steps - [Backend requests](/docs/backend) - [Fastify](/docs/backend/fastify) - [Express](/docs/backend/express) - [Calling the REST API](/docs/api) ### Elysia Source: https://www.authdog.com/docs/backend/elysia.md `@authdog/elysia` resolves Authdog sessions for Elysia. Its plugin injects session context into handlers and exposes an authentication guard. ## Availability **Unreleased:** `@authdog/elysia` is not on npm at the time of writing (the registry returns 404). `bun add @authdog/elysia` will fail until it is published. Source lives in the [`packages/elysia` directory](https://github.com/authdog/web-sdk/tree/main/packages/elysia) of the web SDK repository (version `0.3.0`, configured to publish to npm with public access). Vendor a repository checkout until a release ships. ## Install ```package-install @authdog/elysia elysia ``` `elysia` is a peer dependency (`^1.0.0`); `@authdog/node-commons` installs automatically. The SDK is aimed at Bun-first Elysia apps; `logout` returns a Web `Response`, so it is not tied to one runtime. ## Configure Create one instance with your environment's public key (`pk_...`): ```ts import { Elysia } from "elysia" import { createAuthdog } from "@authdog/elysia" const authdog = createAuthdog({ publicKey: process.env.PK_AUTHDOG! }) ``` The key is safe to expose. It is parsed once at startup; a missing key throws `Public key is not defined`, and malformed keys or identity hosts outside the trusted HTTPS allowlist fail immediately. `createAuthdog` also accepts an optional `secretKey`, which is reserved and currently unused. ## Attach the session Apply the plugin on the root app before routes: ```ts const app = new Elysia().use(authdog.attachSession()) ``` `attachSession()` returns an Elysia plugin that derives a global `authdog` property into every handler context. It prefers the `authdog-session` cookie, then reads `Authorization: Bearer `. A valid token requires a successful `userinfo` response (`meta.code === 200` with a user). Missing, invalid, or unverifiable tokens never fail the request or return a 500; they yield anonymous context: ```ts { token: string | null, user: unknown | null, isAuthenticated: boolean, userInfo?: UserInfoResponse | null, } ``` `attachSession({ fetchUser: false })` only surfaces the unverified token. It leaves `user` null and `isAuthenticated` false, so `requireAuth` rejects the request with 401. Use this option only when separate server-side validation and enforcement replace Authdog's gate. ## Protect a route `requireAuth` (a `beforeHandle` hook in Elysia) is the security boundary and returns `401 {"error":"Unauthorized"}` unless `isAuthenticated` is true: ```ts new Elysia() .use(authdog.attachSession()) .get("/me", ({ authdog: ctx }) => ctx.user, { beforeHandle: authdog.requireAuth, }) ``` Authentication identifies a caller; it does not authorize actions. Apply your [authorization](/docs/concepts/authorization) checks after `requireAuth`. Client-side checks never protect server routes. ## Sign out `logout` expires the `authdog-session` cookie (`HttpOnly`, `SameSite=Lax`, `Secure` when `NODE_ENV` is `production`) and redirects (302) to a sanitized same-origin `redirect_uri`, falling back to `/`: ```ts new Elysia().use(authdog.attachSession()).get("/logout", authdog.logout) ``` This does not revoke a bearer token or end an upstream identity-provider session. ## Next steps - [Backend requests](/docs/backend) - [Fastify](/docs/backend/fastify) - [Express](/docs/backend/express) - [Calling the REST API](/docs/api) ### Koa Source: https://www.authdog.com/docs/backend/koa.md `@authdog/koa` resolves Authdog sessions for Koa. Its middleware sets `ctx.state.authdog` and exposes an authentication guard. ## Availability **Unreleased:** `@authdog/koa` is not on npm at the time of writing (the registry returns 404). `bun add @authdog/koa` will fail until it is published. Source lives in the [`packages/koa` directory](https://github.com/authdog/web-sdk/tree/main/packages/koa) of the web SDK repository (version `0.3.0`, configured to publish to npm with public access). Vendor a repository checkout until a release ships. ## Install ```package-install @authdog/koa koa ``` `koa` is a peer dependency (`^2.15.0 || ^3.0.0`); `@authdog/node-commons` installs automatically. Examples also use `@koa/router`, which is not required by the SDK. ## Configure Create one instance with your environment's public key (`pk_...`): ```ts import Koa from "koa" import Router from "@koa/router" import { createAuthdog } from "@authdog/koa" const app = new Koa() const router = new Router() const authdog = createAuthdog({ publicKey: process.env.PK_AUTHDOG! }) ``` The key is safe to expose. It is parsed once at startup; a missing key throws `Public key is not defined`, and malformed keys or identity hosts outside the trusted HTTPS allowlist fail immediately. `createAuthdog` also accepts an optional `secretKey`, which is reserved and currently unused. ## Attach the session Mount `attachSession()` before routes: ```ts app.use(authdog.attachSession()) ``` It prefers the `authdog-session` cookie, then reads `Authorization: Bearer `. A valid token requires a successful `userinfo` response (`meta.code === 200` with a user). Missing, invalid, or unverifiable tokens never fail the request or return a 500; they yield anonymous context on `ctx.state.authdog`: ```ts { token: string | null, user: unknown | null, isAuthenticated: boolean, userInfo?: UserInfoResponse | null, } ``` `attachSession({ fetchUser: false })` only surfaces the unverified token. It leaves `user` null and `isAuthenticated` false, so `requireAuth` rejects the request with 401. Use this option only when separate server-side validation and enforcement replace Authdog's gate. ## Protect a route `requireAuth` is the security boundary and returns `401 {"error":"Unauthorized"}` unless `isAuthenticated` is true: ```ts router.get("/me", authdog.requireAuth, (ctx) => { ctx.body = ctx.state.authdog!.user }) ``` Authentication identifies a caller; it does not authorize actions. Apply your [authorization](/docs/concepts/authorization) checks after `requireAuth`. Client-side checks never protect server routes. ## Sign out `logout` expires the `authdog-session` cookie (`HttpOnly`, `SameSite=Lax`, `Secure` when `NODE_ENV` is `production`) and redirects (302) to a sanitized same-origin `redirect_uri`, falling back to `/`: ```ts router.get("/logout", authdog.logout) ``` This does not revoke a bearer token or end an upstream identity-provider session. ## Next steps - [Backend requests](/docs/backend) - [Fastify](/docs/backend/fastify) - [Express](/docs/backend/express) - [Calling the REST API](/docs/api) ### Kotlin Source: https://www.authdog.com/docs/backend/kotlin.md `com.authdog:authdog-ktor` resolves Authdog sessions in Ktor servers. It reads a cookie or bearer token, checks it through the environment's OIDC `userinfo` endpoint, and gives route handlers an authentication context and gate. ## Availability **Unverified release:** the SDK README says the artifact is published to Maven Central as `com.authdog:authdog-ktor` (version `0.1.0`), but no `maven-metadata.xml` for it was found on Maven Central at the time of writing. If Gradle cannot resolve it, build from the [`packages/kotlin` directory](https://github.com/authdog/web-sdk/tree/main/packages/kotlin) of the web SDK repository. ## Install ```kotlin // build.gradle.kts dependencies { implementation("com.authdog:authdog-ktor:0.1.0") } ``` The module is built against Ktor 3.5.1 and Kotlin 2.2.0 on JVM 21. It uses the Ktor CIO client for `userinfo` lookups unless you inject your own. ## Configure Create one `Authdog` instance with your environment's public key (`pk_...`): ```kotlin import com.authdog.Authdog val authdog = Authdog(System.getenv("PK_AUTHDOG")) ``` The key is safe to expose. It is validated at construction; a malformed key or an identity host outside the trusted HTTPS allowlist throws `PublicKeyException` immediately. Extra host suffixes can be added with the comma-separated `AUTHDOG_ALLOWED_IDENTITY_HOSTS` environment variable. | Parameter | Default | Notes | | --- | --- | --- | | `publicKey` | required | Environment public key (`pk_...`). | | `fetchUser` | `true` | When `false`, `resolve` surfaces the token but leaves `isAuthenticated` false. | | `client` | `HttpClient(CIO)` | Optional Ktor `HttpClient` for `userinfo`, for example a `MockEngine` in tests. | ## Resolve the session `authdog.resolve(call)` prefers the `Authorization: Bearer ` header, then the `authdog-session` cookie. A valid token requires a successful `userinfo` response (`meta.code == 200` with a non-null user). It never throws: missing, invalid, or unverifiable tokens, and failed or untrusted lookups, produce an unauthenticated context. The result is cached on the call, so several gates cost one `userinfo` request. ```kotlin data class AuthdogContext( val token: String? = null, val user: JsonElement? = null, val isAuthenticated: Boolean = false, val userInfo: UserInfoResponse? = null, ) ``` ```kotlin get("/session") { val ctx = authdog.resolve(call) call.respondText(if (ctx.isAuthenticated) "signed in" else "anonymous") } ``` With `fetchUser = false`, the token is surfaced but `user` stays null and `isAuthenticated` stays false, so `requireAuth` responds 401. Use this only when separate server-side validation and enforcement replace Authdog's gate. ## Protect a route `requireAuth` is the security boundary. It responds `401 {"error":"Unauthorized"}` (JSON) and returns `null` when the session is not authenticated; otherwise it returns the context: ```kotlin get("/me") { val ctx = authdog.requireAuth(call) ?: return@get call.respondText(ctx.user.toString()) } ``` Authentication identifies a caller; it does not authorize actions. Apply your [authorization](/docs/concepts/authorization) checks after `requireAuth`. Client-side checks never protect server routes. ## Sign out `logout` expires the `authdog-session` cookie (`HttpOnly`, `SameSite=Lax`, path `/`) and issues a temporary redirect (302) to a sanitized same-origin `redirect_uri` query parameter, falling back to `/`: ```kotlin get("/logout") { authdog.logout(call) } ``` This does not revoke a bearer token or end an upstream identity-provider session. ## Next steps - [Backend requests](/docs/backend) - [Fastify](/docs/backend/fastify) - [Express](/docs/backend/express) - [Calling the REST API](/docs/api) ## Examples ### Team dashboard Source: https://www.authdog.com/docs/examples/nextjs.md The team dashboard lists project status for the signed-in member. The public home only links to sign-in and `/dashboard`. Clone [starters/nextjs](https://github.com/authdog/samples/tree/main/starters/nextjs). SDK reference: [Next.js](/docs/frameworks/nextjs). The project list answers who the member is. It does not grant a permission. Validate again on Route Handlers and Server Actions with a [backend SDK](/docs/backend), then apply [authorization](/docs/concepts/authorization). ## What you need | Requirement | Detail | | --- | --- | | Public key | `pk_...` from the [console](https://console.authdog.com) | | Environment | Copy `.env.example` to `.env.local` and set `PK_AUTHDOG` and `NEXT_PUBLIC_PK_AUTHDOG` to that same key | | Secret key | Never put `sk_...` in the app | | Return URL | Account portal back to a URL this app serves. Local: `http://localhost:3000/dashboard` | ## Run ```bash npm install npm run dev ``` Open http://localhost:3000. Sign in, then open `/dashboard`. You should see Billing, Mobile, and Docs with a status, plus `Signed in as` and an email from userinfo. Without a session the page says `Not signed in`. ## Callback `useAuthMiddleware` from `@authdog/nextjs-app` does not load in the Next.js 15 Edge runtime. This example forwards `?token=` to a Node route that runs the same userinfo exchange and writes the same `user_session_*` cookies. ```ts // middleware.ts import { NextResponse, type NextRequest } from "next/server" export function middleware(request: NextRequest) { const token = request.nextUrl.searchParams.get("token") if (!token || request.nextUrl.pathname === "/auth/callback") { return NextResponse.next() } const url = request.nextUrl.clone() url.pathname = "/auth/callback" url.searchParams.set("next", request.nextUrl.pathname) return NextResponse.redirect(url) } ``` ```ts // app/auth/callback/route.ts import { fetchUserData, isAuthenticatedUserInfo, sanitizeRedirectPath, } from "@authdog/node-commons" import { getServerSidePayloadPublicKey } from "@authdog/nextjs-app/server" import { NextResponse, type NextRequest } from "next/server" export async function GET(request: NextRequest) { const token = request.nextUrl.searchParams.get("token") const nextPath = sanitizeRedirectPath( request.nextUrl.searchParams.get("next"), "/dashboard", ) const dest = new URL(nextPath, request.url) if (token) dest.searchParams.set("token", token) const publicKey = process.env.PK_AUTHDOG if (!token || !publicKey) { return NextResponse.redirect(dest) } const { identityHost, environmentId } = getServerSidePayloadPublicKey(publicKey) const userData = await fetchUserData(identityHost, environmentId, token) const response = NextResponse.redirect(dest) if (isAuthenticatedUserInfo(userData)) { const options = { maxAge: 60 * 60 * 24 * 7, path: "/", httpOnly: true, secure: process.env.NODE_ENV === "production", sameSite: "lax" as const, } response.cookies.set({ name: `user_session_${environmentId}`, value: JSON.stringify(userData.user), ...options, }) response.cookies.set({ name: `user_session_hash_${environmentId}`, value: token, ...options, }) } return response } ``` The callback does not guard later requests. ## Projects `useUser` calls userinfo. The project rows render only after that call returns a user. `useAuth` only reports that a browser token exists. Do not use it to authorize. ```tsx // app/dashboard/page.tsx "use client" import { useUser } from "@authdog/nextjs-app" const PROJECTS = [ { name: "Billing", status: "On track" }, { name: "Mobile", status: "At risk" }, { name: "Docs", status: "On track" }, ] export default function DashboardPage() { const { user, isLoading } = useUser() if (isLoading) return

Loading…

if (!user) return

Not signed in

return (
    {PROJECTS.map((project) => (
  • {project.name} · {project.status}
  • ))}
) } ``` The provider stores `?token=` after a JWT-shape check only. Real validation happens at userinfo. ### Customer navbar Source: https://www.authdog.com/docs/examples/react.md The customer navbar sits on a shop. Shop items are public. Orders stay empty until a server app holds the session. Clone [starters/react](https://github.com/authdog/samples/tree/main/starters/react). SDK reference: [React Elements](/docs/frameworks/react). Pair this UI with [Team dashboard](/docs/examples/nextjs) or [Orders API](/docs/examples/express) for the session gate. `@authdog/react-elements` does not mint a session, store a cookie, or check a permission. ## What you need | Requirement | Detail | | --- | --- | | Identity | Identity host and environment id from the [console](https://console.authdog.com) | | Environment | Copy `.env.example` to `.env` and set `VITE_AUTHDOG_IDENTITY_HOST` and `VITE_AUTHDOG_ENVIRONMENT_ID`. `VITE_AUTHDOG_IDENTITY_HOST` is the identity origin, not `console.authdog.com` | | Secret key | Never put `sk_...` in the app | ## Run ```bash npm install npm run dev ``` Open the URL Vite prints. **Shop** lists Field kit ($48) and Spare sensor ($19). **Sign in** opens the hosted flow for that environment. After return, this app still has no session, so **Orders** does not list anything. That is expected. ## Shop and navbar ```tsx // src/App.tsx import { Navbar } from "@authdog/react-elements" const identityHost = import.meta.env.VITE_AUTHDOG_IDENTITY_HOST const environmentId = import.meta.env.VITE_AUTHDOG_ENVIRONMENT_ID const PRODUCTS = [ { name: "Field kit", price: "$48" }, { name: "Spare sensor", price: "$19" }, ] export function App() { return ( <> { window.location.href = href }} onLogout={() => undefined} />
    {PRODUCTS.map((product) => (
  • {product.name} · {product.price}
  • ))}
) } ``` Do not check permissions in the client. Continue in a server example before any authorization decision. ### Support inbox Source: https://www.authdog.com/docs/examples/remix.md The support inbox lists customer tickets for the signed-in agent. The home page stays reachable without a session. Clone [starters/remix](https://github.com/authdog/samples/tree/main/starters/remix). SDK reference: [Remix](/docs/frameworks/remix). The ticket list answers who the agent is. It does not grant a permission. Apply [authorization](/docs/concepts/authorization) after the identity check. ## What you need | Requirement | Detail | | --- | --- | | Public key | `pk_...` from the [console](https://console.authdog.com) | | Environment | Copy `.env.example` to `.env` and set `PK_AUTHDOG` | | Secret key | Never put `sk_...` in client code | | Return URL | Account portal back to this app. Local: `http://localhost:5173`. The root loader must receive `?token=` | ## Run ```bash npm install npm run dev ``` Open http://localhost:5173. Sign in, then open `/inbox`. You should see `tkt_88` (Northwind, invoice PDF) and `tkt_89` (Contoso, locked test user). Without a session, `/inbox` redirects to `signinUri`. ## Root loader `identityLoader()` validates `?token=` through userinfo and sets HttpOnly cookies. Keep that exchange on the root loader. Calling `.json()` on the callback response and rebuilding it drops `Set-Cookie`. ```tsx // app/root.tsx import { Outlet, Scripts } from "@remix-run/react" import { AuthdogProvider, ReloadPage } from "@authdog/remix-node/client" import { identityLoader } from "@authdog/remix-node" export const loader = identityLoader() export default function App() { return ( ) } ``` `AuthdogProvider` strips `?token=` in the browser. The HttpOnly cookie is set by the server loader. The loader itself returns an unauthenticated result instead of denying the request. The inbox enforces it. ## Inbox ```tsx // app/routes/inbox.tsx import { identityLoader } from "@authdog/remix-node" import { redirect, type LoaderFunctionArgs } from "@remix-run/node" const TICKETS = [ { id: "tkt_88", from: "Northwind", subject: "Invoice PDF missing", state: "open" }, { id: "tkt_89", from: "Contoso", subject: "SSO test user locked", state: "waiting" }, ] const loadIdentity = identityLoader() export const loader = async (args: LoaderFunctionArgs) => { const response = await loadIdentity(args) if (!(response instanceof Response)) { throw new Response("Identity loader failed", { status: 500 }) } const identity = (await response.json()) as { isAuthenticated: boolean signinUri: string } if (!identity.isAuthenticated) throw redirect(identity.signinUri) return { ...identity, tickets: TICKETS } } ``` ### Field companion Source: https://www.authdog.com/docs/examples/expo.md The field companion lists today's site visits after hosted sign-in returns through a deep link. The session token stays in secure storage. Clone [starters/expo](https://github.com/authdog/samples/tree/main/starters/expo). SDK reference: [React Native / Expo](/docs/frameworks/expo). The job list answers who the technician is on the device. Client state is UX, not a security boundary. Your API must validate the bearer. Apply [authorization](/docs/concepts/authorization) there. ## What you need | Requirement | Detail | | --- | --- | | Public key | `pk_...` from the [console](https://console.authdog.com) | | Environment | Copy `.env.example` to `.env` and set `EXPO_PUBLIC_PK_AUTHDOG` | | Secret key | Never put `sk_...` in the app | | Callback | Register the deep-link callback. Local default: `authdog-authn://callback` | ## Run ```bash npm install npx expo start ``` Open the project in Expo Go or a simulator. Tap **Sign in**, finish the hosted Account portal, and return through the deep link. You should see `job_18` (Pier 4, replace meter) and `job_19` (Warehouse B, calibrate sensor). ## Jobs `handleRedirect` stores `?token=` after a structure check only. `fetchUser()` validates it through userinfo. The visits render only after that call. This example uses `expo-secure-store`. Do not ship the in-memory fallback. ```tsx // App.tsx import { useEffect } from "react" import { Button, Linking, Text } from "react-native" import * as SecureStore from "expo-secure-store" import { AuthdogProvider, createSecureStoreAdapter, useRedirectHandler, useSignIn, useUser, } from "@authdog/react-native" const publicKey = process.env.EXPO_PUBLIC_PK_AUTHDOG ?? "" function Home() { const { signIn } = useSignIn() const { handleRedirect } = useRedirectHandler() const { fetchUser, isAuthenticated } = useUser() const redirectUrl = "authdog-authn://callback" useEffect(() => { Linking.getInitialURL().then((url) => { if (url) handleRedirect(url) }) const sub = Linking.addEventListener("url", ({ url }) => handleRedirect(url)) return () => sub.remove() }, [handleRedirect]) useEffect(() => { if (isAuthenticated) fetchUser() }, [isAuthenticated, fetchUser]) if (!isAuthenticated) { return