# Authfu > Authfu is a hosted identity provider: passwordless magic-link sign-in, two-factor > authentication that is checked *before* any email is sent, and a standard > OpenID Connect / OAuth 2.0 endpoint. Developers register an app, prove they own > their domain with one DNS TXT record, and add sign-in with a few lines of config. Hosted sign-in and signed-in app launcher: https://authfu.app Website and setup guide: https://authfu.com Issuer: https://authfu.app Discovery: https://authfu.app/.well-known/openid-configuration ## Endpoints - Authorization: https://authfu.app/api/oauth/authorize (response_type=code; PKCE S256 supported) - Token: https://authfu.app/api/oauth/token (grants: authorization_code, refresh_token; auth: client_secret_basic or client_secret_post) - Userinfo: https://authfu.app/api/oauth/userinfo (Bearer access token) - JWKS: https://authfu.app/api/oauth/.well-known/jwks.json (ID tokens are RS256) - Scopes: openid profile email - Claims: sub, name, given_name, family_name, email, email_verified, picture, roles, groups ## Authorization claims: roles, groups, permissions `roles` contains app-specific role names. `groups` contains the signed-in user's group display names from that app's organization. Both are present as arrays in the ID token and userinfo response, including `[]` when the user has none. Do not infer permissions from a group name unless the application explicitly defines that mapping. Each roles/groups array is capped at 50 entries to avoid oversized JWT headers. Authfu preserves stored role order and sorts groups by display name. When values are omitted, `roles_truncated: true` or `groups_truncated: true` is included. Authorization code must fail closed if a truncated list could hide a required deny or entitlement. `permissions` is reserved for granular app actions. Treat a missing permissions claim as an empty array, and never manufacture permissions from roles or groups in generic integration code. Applications should map only documented role/group values to their own permissions and should reject unknown values by default. ## Environment variables an app needs AUTHFU_CLIENT_ID=... # from the Authfu console AUTHFU_CLIENT_SECRET=... # from the Authfu console AUTHFU_ISSUER=https://authfu.app # optional; use http://localhost:3000 against a local provider The redirect URL registered in the console must match the callback your framework uses, e.g. https://yourapp.com/api/auth/callback/authfu for Auth.js / NextAuth. ## Local development Leave EMAIL_PROVIDER unset to use the built-in test inbox. Open /inbox to view messages and complete local sign-in. ## Development and Production apps Every app has an environment. Development apps send no email: eligible sign-in links go to the app's Test inbox in the Authfu console. Eligible addresses are the app owner, organization members, and configured test users. Production apps send real email and count toward plan quotas. Signed-in app owners and scripts using an owner session can fetch development links: GET /api/clients/YOUR_CLIENT_ID/inbox GET /api/clients/YOUR_CLIENT_ID/inbox?id=MESSAGE_ID The first response lists newest-first summaries with `actionLink`; the second also includes `text` and `html`. Test inboxes store at most 100 messages per rolling 24 hours by default, retain messages for 24 hours, and keep the newest 50 per app. ## Next.js with Auth.js v5 (recommended) npm install next-auth@beta @authfu/next // auth.ts import NextAuth from "next-auth" import { Authfu } from "@authfu/next" export const { handlers, auth, signIn, signOut } = NextAuth({ providers: [Authfu()] }) // app/api/auth/[...nextauth]/route.ts import { handlers } from "@/auth" export const { GET, POST } = handlers Without the SDK, the equivalent provider object is: { id: "authfu", name: "Authfu", type: "oidc", issuer: "https://authfu.app", clientId: process.env.AUTHFU_CLIENT_ID, clientSecret: process.env.AUTHFU_CLIENT_SECRET, checks: ["pkce", "state"] } ## Next.js with NextAuth v4 import { AuthfuV4 } from "@authfu/next" export const authOptions = { providers: [AuthfuV4()] } Or by hand: { id: "authfu", name: "Authfu", type: "oauth", version: "2.0", wellKnown: "https://authfu.app/.well-known/openid-configuration", authorization: { params: { scope: "openid profile email" } }, idToken: true, checks: ["pkce", "state"], clientId: ..., clientSecret: ... } ## Any other language or framework Use any OpenID Connect client library with the discovery URL above. Authorization code flow with PKCE; exchange the code at the token endpoint with the client secret. ## How sign-in behaves for end users 1. The user enters their email on the hosted Authfu page. 2. If that account has two-factor enabled, they must enter a valid authenticator code first. No email is sent until the code is correct. 3. Authfu emails a single-use link (from authfu.link) that expires in minutes. 4. Opening the link completes sign-in and returns the user to your app with a code. ## Domain verification Add a DNS TXT record on the domain: `authfu-verify=`, then click Verify in the console. Apps on unverified domains run in sandbox mode. ## Notes for coding agents - Do not implement passwords or your own email sending; Authfu handles the whole flow. - Never hardcode the client secret; read it from the environment. - Register every callback URL (including http://localhost:3000/... for local dev) in the console. - Session handling is your framework's job (Auth.js JWT sessions work with no database). ## WordPress The Authfu WordPress plugin uses standard OpenID Connect authorization code flow with PKCE S256 and state. Register the exact callback URL `https://your-wordpress-site.example/wp-login.php?authfu=callback`. An app owner can fetch a one-paste Setup Key while signed in to the Authfu console: GET /api/wordpress/setup-key?client_id=YOUR_CLIENT_ID The response contains `setup_key`, a base64url-encoded JSON object with version, issuer, client ID, and client secret. Treat the Setup Key like a password. WordPress role assignments are returned in the `roles` claim in both the ID token and userinfo. Supported WordPress roles are administrator, editor, author, contributor, and subscriber. The plugin applies the first supported role and otherwise uses its configured default role. The plugin can sync existing users in batches of 500: POST /api/wordpress/sync Authorization: Basic base64(client_id:client_secret) Content-Type: application/json {"site":"https://example.com","users":[{"email":"editor@example.com","roles":["editor"],"external_id":"42","display_name":"Example Editor"}]} The sync endpoint accepts at most 5,000 users per request and returns created, updated, and skipped counts. Two-factor recovery waits seven days by default, is cancellable by email or a strong sign-in, and never bypasses the 2FA-before-email gate before its deadline. Disabling 2FA, regenerating backup codes, and administrator resets require a fresh authenticator or one-time backup code and are audited.