React Components
Pre-built, themeable components — buttons, profile and organization cards, the interaction forms, PasskeyButton, CanPermission, and KeyRotation.
@adonis-agora/authkit-react exports three layers: auth-state primitives, a typed HTTP
client with TanStack Query hooks, and the pre-built UI components documented here. The
components are built on the headless hooks and consume the
same auth state and AuthkitProvider config — no extra wiring beyond what
React sets up. Several render nothing when the user is unauthenticated,
so they're safe to drop into a shared layout.
For data-fetching in management screens (admin panels, account pages), see Typed Client & TanStack Query.
Import the stylesheet once (anywhere in your app entry):
import '@adonis-agora/authkit-react/styles.css'
import {
SignInButton,
SignOutButton,
UserButton,
UserProfile,
AuthorizedApps,
Avatar,
PasswordStrengthMeter,
OrganizationSwitcher,
OrganizationProfile,
InteractionForm,
MagicLinkButton,
OAuthButton,
PasskeyButton,
CanPermission,
KeyRotation,
} from '@adonis-agora/authkit-react'All components are SSR-safe (no window access at module load).
SignInButton
Starts the OIDC login redirect (via useSignIn). Hidden when already authenticated unless
showWhenAuthenticated is set. Extends <button> — any extra HTML button attribute is
forwarded.
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | 'Entrar' | Button label. |
returnTo | string | current URL | Where to return after login. |
showWhenAuthenticated | boolean | false | Render even when logged in. |
className | string | — | Appended to authkit-button authkit-button--primary. |
...rest | ButtonHTMLAttributes | — | Forwarded to the <button>. |
<SignInButton returnTo="/dashboard">Sign in</SignInButton>SignOutButton
Logs out (via useSignOut). Renders nothing when unauthenticated.
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | 'Sair' | Button label. |
returnTo | string | — | Where to go after logout. |
className | string | — | Appended to authkit-button authkit-button--ghost. |
...rest | ButtonHTMLAttributes | — | Forwarded to the <button>. |
<SignOutButton returnTo="/" />UserButton
A clickable avatar that opens a dropdown menu with a profile link (config.profileUrl from
AuthkitProvider) and a sign-out item. Renders nothing when unauthenticated.
| Prop | Type | Default | Description |
|---|---|---|---|
profileLabel | string | 'Perfil' | Label of the profile menu item. |
signOutLabel | string | 'Sair' | Label of the sign-out menu item. |
className | string | — | Appended to the authkit-userbutton wrapper. |
<UserButton profileLabel="Profile" signOutLabel="Sign out" />UserProfile
A card showing the avatar, name, and email, with an inline edit form that POSTs to the
configured profile endpoint (via useProfile). Renders nothing when unauthenticated;
disables the submit button while saving and surfaces errors.
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | — | Appended to authkit-card authkit-profile. |
<UserProfile />AuthorizedApps
Lists the OAuth/OIDC apps the user has authorized, each with a revoke button (via
useAuthorizedApps). Shows loading, error, and empty states.
| Prop | Type | Default | Description |
|---|---|---|---|
revokeLabel | string | 'Revogar' | Label of each revoke button. |
emptyLabel | string | 'Nenhum app autorizado.' | Shown when the list is empty. |
className | string | — | Appended to the authkit-apps list. |
<AuthorizedApps revokeLabel="Revoke" emptyLabel="No authorized apps." />Avatar
The avatar primitive used by UserButton and UserProfile. Renders the user's
avatarUrl as an <img>, or falls back to initials derived from name / email.
| Prop | Type | Default | Description |
|---|---|---|---|
user | Pick<AuthUser, 'name' | 'email' | 'avatarUrl'> | — | The user to render (required). |
size | number | 36 | Width/height in pixels. |
className | string | — | Appended to authkit-avatar. |
import { useAuth } from '@adonis-agora/authkit-react'
function Me() {
const { user } = useAuth()
return user ? <Avatar user={user} size={48} /> : null
}PasswordStrengthMeter
A visual meter (four segments plus a label) for password strength feedback. It is not a dumb
bar: give it the password and it calls
usePasswordStrength itself, so the meter and the score can
never drift apart.
| Prop | Type | Default | Description |
|---|---|---|---|
password | string | — | The password to evaluate (required). |
scorer | (password: string) => { score, feedback? } | built-in heuristic | Custom scorer, e.g. zxcvbn. |
showFeedback | boolean | true | Render the tip list below the bar. |
labels | [string, string, string, string, string] | ['Very weak', 'Weak', 'Fair', 'Good', 'Strong'] | One label per score, for i18n. |
className | string | — | Appended to authkit-strength. |
import { useState } from 'react'
import { PasswordStrengthMeter } from '@adonis-agora/authkit-react'
function PasswordField() {
const [password, setPassword] = useState('')
return (
<>
<input type="password" value={password} onChange={e => setPassword(e.target.value)} />
<PasswordStrengthMeter password={password} />
</>
)
}The bar is a role="meter" with aria-valuemin=0, aria-valuemax=4, aria-valuenow set to
the score, and the matching label as its accessible name — a screen reader gets the same
signal as the colour.
Localise it with labels, hide the tips with showFeedback={false}, and swap the algorithm
with scorer:
import { zxcvbn } from '@zxcvbn-ts/core'
// Module scope: a stable function identity keeps the memoisation working.
const scorer = (password: string) => {
const { score, feedback } = zxcvbn(password)
return { score, feedback: feedback.suggestions }
}
<PasswordStrengthMeter
password={password}
scorer={scorer}
labels={['Muito fraca', 'Fraca', 'Razoável', 'Boa', 'Forte']}
/>CSS hooks: .authkit-strength (root), .authkit-strength__bar (carries data-score="0…4"),
.authkit-strength__segment / --filled, .authkit-strength__label, and
.authkit-strength__feedback. Colour per score is driven from the data-score attribute, so
you can restyle the scale without touching the component.
OrganizationSwitcher
A dropdown to switch the active organization. It reads the list and the active id from
useOrganizations() and calls useSwitchOrganization() on selection — selecting the personal
account entry deactivates the current org, selecting another org activates it. Each row shows
the org name and the user's role in it.
It renders nothing when the user is unauthenticated, when the server reports that
organizations are not supported, or when the provider is configured with idp: 'external'.
| Prop | Type | Default | Description |
|---|---|---|---|
personalAccountLabel | string | 'Conta pessoal' | Label of the "no active org" entry. |
className | string | — | Appended to authkit-orgswitcher. |
<OrganizationSwitcher personalAccountLabel="Personal account" />Creating an organization is not part of this component — it is a server-side decision (see
Organizations). Build the create flow with useOrganizations() and your
own form when your deployment allows self-service creation.
CSS hooks: .authkit-orgswitcher, __trigger, __label, __chevron, __menu, __item
(with --active), __item-name, __item-role.
OrganizationProfile
A card for the active organization: name and slug, the member list with each member's role, an invite form, and a "leave organization" button.
There is no orgId prop, and that is the point — the component asks useOrganizations() for
the active org id and loads its detail with useOrganization(). The active org is server-side
session state, so a prop here could disagree with the session; instead, switching orgs (via
OrganizationSwitcher or useSwitchOrganization()) is what changes what this card shows. It
renders nothing when unauthenticated or when there is no active org, and degrades to
null under idp: 'external'.
The invite form only renders when the loaded org says the current member may manage it, so a plain member sees the roster without controls they cannot use.
| Prop | Type | Default | Description |
|---|---|---|---|
inviteLabel | string | 'Convidar membro' | Heading and submit label of the invite form. |
leaveLabel | string | 'Sair da organização' | Label of the leave button. |
className | string | — | Appended to authkit-card authkit-org-profile. |
<OrganizationProfile inviteLabel="Invite member" leaveLabel="Leave organization" />InteractionForm
The composable primitive behind the login-page buttons. It renders a
<form method="POST"> pointing at the right AuthKit interaction endpoint (resolved from the
uid via interactionUrls, so you never hand-concatenate /auth/interaction/...) plus the
hidden _csrf field — you own the fields and the styling inside it. Use it directly when
you want full control of a step's form; the pre-built buttons are built on top of it.
| Prop | Type | Default | Description |
|---|---|---|---|
uid | string | — | The interaction uid (rendered by authkit-server into the login view). |
step | 'identifier' | 'login' | 'magic' | 'otpVerify' | — | Which POST endpoint to submit to. |
csrfToken | string | — | Becomes the hidden _csrf field. |
basePath | string | '/auth/interaction' | Mount prefix, if you changed it server-side. |
children | ReactNode | — | Your fields and submit button. |
...rest | FormHTMLAttributes | — | Forwarded to the <form> (minus method/action). |
The four steps are the four POST endpoints of the login interaction:
step | What the form submits | Typical fields |
|---|---|---|
identifier | The email/username, moving the user to the credential step | email |
login | Password authentication | email, password |
magic | A request for a magic link or an emailed code | email (plus an optional channel) |
otpVerify | The six-digit code from the magic-link email | the field named by OTP_CODE_FIELD |
import { InteractionForm } from '@adonis-agora/authkit-react'
// Password step — the app owns the inputs and layout.
<InteractionForm uid={uid} step="login" csrfToken={csrfToken}>
<input type="email" name="email" autoComplete="username" />
<input type="password" name="password" autoComplete="current-password" />
<button type="submit" className="authkit-button authkit-button--primary">Sign in</button>
</InteractionForm>For the OTP step, take the input's name from OTP_CODE_FIELD rather than typing "code" — the
screen and your end-to-end test then read the same constant, and a typo breaks both together
instead of drifting apart silently:
import { InteractionForm, OTP_CODE_FIELD } from '@adonis-agora/authkit-react'
<InteractionForm uid={uid} step="otpVerify" csrfToken={csrfToken}>
<input
name={OTP_CODE_FIELD}
inputMode="numeric"
autoComplete="one-time-code"
maxLength={6}
/>
<button type="submit">Verify</button>
</InteractionForm>Every InteractionForm carries a double-submit lock, whatever children you put in it: on
submit, its submit buttons are disabled and the form gets aria-busy="true". These endpoints
trigger expensive work — sending an email, starting a WebAuthn ceremony — and an impatient
double click otherwise turns into a duplicate POST (and, on the magic step, into the host's
rate limiter). The lock is applied after the browser has built the form's entry list, so a
named submit button still contributes its value, and it never fires when a handler of yours
called preventDefault() or when native validation blocked the submit. There is nothing to
unlock: the response is a new document, so the state resets by itself.
MagicLinkButton
A drop-in magic-link button: an InteractionForm targeting the magic step with a single
submit button. Themeable via className (merged with authkit-button) and children.
| Prop | Type | Default | Description |
|---|---|---|---|
uid | string | — | The interaction uid. |
csrfToken | string | — | CSRF token for the form. |
basePath | string | '/auth/interaction' | Mount prefix, if changed. |
channel | 'code' | 'link' | — | Submits a hidden channel field, asking for only the emailed code or only the link. Omit it to let the server decide. |
children | ReactNode | 'Enviar link de login' | Button label. |
...rest | ButtonHTMLAttributes | — | Forwarded to the <button> (minus type). |
<MagicLinkButton uid={uid} csrfToken={csrfToken}>Email me a login link</MagicLinkButton>
{/* A "send me a code" button on a screen that then renders the otpVerify form */}
<MagicLinkButton uid={uid} csrfToken={csrfToken} channel="code">
Email me a 6-digit code
</MagicLinkButton>OAuthButton
A drop-in social-login link. Because the OAuth redirect is a full-page navigation, this
renders an <a> (not a JS button) pointing at the provider's redirect URL
(/{basePath}/{provider}/redirect/{uid}). It's intentionally generic — pass the provider's
icon/label as children and style it with className.
| Prop | Type | Default | Description |
|---|---|---|---|
provider | string | — | OAuth provider, e.g. 'google', 'github'. |
uid | string | — | The interaction uid. |
basePath | string | '/auth' | OAuth mount prefix, if changed. |
children | ReactNode | Entrar com {Provider} | Button content (icon + label). |
...rest | AnchorHTMLAttributes | — | Forwarded to the <a> (minus href). |
<OAuthButton provider="google" uid={uid}>
<GoogleIcon /> Continue with Google
</OAuthButton>PasskeyButton
A drop-in passkey login button — the top tier of the
three-tier passkey API. It renders the button, runs
the WebAuthn ceremony on click (options → browser prompt → full-page verify POST),
disables itself while busy, and shows an inline error on failure. Drop to
usePasskeyLogin when you want your
own markup, and to the bare functions when your screen owns the state machine.
| Prop | Type | Default | Description |
|---|---|---|---|
optionsUrl | string | — | The interaction passkey/options endpoint (POST). |
verifyUrl | string | — | The interaction passkey/verify endpoint (full-page POST). |
csrfToken | string? | — | Sent as x-csrf-token on options and _csrf on verify. |
children | ReactNode | 'Entrar com passkey' | Button label. |
errorContent | ReactNode | default message | Shown on failure. Pass null to disable (handle errors yourself). |
className | string | — | Merged with authkit-button. |
...rest | ButtonHTMLAttributes | — | Forwarded to the <button> (minus onClick). |
import { PasskeyButton, interactionUrls } from '@adonis-agora/authkit-react'
const urls = interactionUrls(uid)
<PasskeyButton
optionsUrl={urls.passkeyOptions}
verifyUrl={urls.passkeyVerify}
csrfToken={csrfToken}
>
Sign in with a passkey
</PasskeyButton>@simplewebauthn/browser is an optional peer dependency; without it the ceremony fails with
an install instruction instead of reaching for a CDN. See
WebAuthn / passkeys for the server side.
Interaction URLs
Every component on this page that talks to the login interaction takes URLs, not a uid plus
string concatenation. interactionUrls(uid, basePath?) is the single typed source for them —
if the server-side mount prefix changes, one argument moves instead of a dozen template
literals breaking silently.
import { interactionUrls, oauthRedirectUrl, OTP_CODE_FIELD } from '@adonis-agora/authkit-react'
const urls = interactionUrls(uid)
// urls.identifier POST — submit the email, advance to the credential step
// urls.login POST — password login
// urls.magic POST — send the magic link / code
// urls.otpVerify POST — verify the six-digit code
// urls.passkeyOptions POST — begin the passkey ceremony
// urls.passkeyVerify POST — finish it (full-page POST)
// urls.signup GET — the account-creation screen
// urls.switch GET — switch account (back to the identifier step)
// A custom mount prefix, passed once:
const urls = interactionUrls(uid, '/entrar/interaction')
// Social login is a full-page navigation to the provider redirect:
const google = oauthRedirectUrl('google', uid) // '/auth/google/redirect/{uid}'
const github = oauthRedirectUrl('github', uid, '/entrar')OTP_CODE_FIELD is the name of the six-digit-code input the server reads on the otpVerify
step. Import it instead of typing the literal, so the screen and your tests cannot disagree.
CanPermission
Renders its children only when the authz endpoint grants permission (optionally on a
resource), via POST <canPath>. Built on the
useCan hook, which caches and
dedupes the answers.
This gates on database-backed permissions, which need a server round-trip — use it for
decisions that depend on the resource ("may they edit this post?"). For the global roles
already carried in the auth shared-prop, useAuth().hasGlobalRole(...) answers
synchronously, with no request at all.
| Prop | Type | Default | Description |
|---|---|---|---|
permission | string | — | The permission to check, e.g. posts.update. |
resource | string? | — | Optional resource the permission is evaluated against. |
children | ReactNode | — | Rendered when allowed. |
loadingFallback | ReactNode | null | Rendered while the check is in flight. |
fallback | ReactNode | null | Rendered when denied. |
<CanPermission
permission="posts.update"
resource={post.id}
loadingFallback={<Spinner />}
fallback={<ReadOnlyBadge />}
>
<EditPostButton post={post} />
</CanPermission>The gate is fail-closed: if the check cannot be made, children are not rendered. Rendering
fallback (rather than nothing) while a decision is pending is a common mistake — that is
what loadingFallback is for.
KeyRotation
An admin panel for the JWKS signing key: current key age, the rotation policy and ETA of
the next rotation, and a Rotate now button (with an option to retire old keys
immediately). It reads and mutates through the admin query hooks, so it must live inside an
AuthkitClientProvider and a QueryClientProvider — the admin console
already provides both.
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | — | Appended to authkit-card authkit-keys. |
import { KeyRotation } from '@adonis-agora/authkit-react'
function KeysPanel() {
return <KeyRotation />
}See Signing key rotation for what rotation does
server-side and the equivalent authkit:keys:rotate command.
Theming
Components carry static authkit-* class names and read their colors and metrics from CSS
variables (Adonis-violet defaults). Override the variables — typically on :root — to
theme every component at once. These are the variables styles.css defines:
:root {
--authkit-primary: #5a45ff;
--authkit-primary-contrast: #ffffff;
--authkit-danger: #e5484d;
--authkit-fg: #1a1523;
--authkit-muted: #6f6e77;
--authkit-bg: #ffffff;
--authkit-border: #e4e2e8;
--authkit-radius: 8px;
--authkit-gap: 0.5rem;
}You can also target the class names directly (.authkit-button, .authkit-card,
.authkit-userbutton__menu, .authkit-apps__item, …) for finer control, but the variables
cover the common cases.
For the provider, useAuth(), headless hooks, and the role helpers these components are
built on, see React.
React (Frontend)
useAuth, gating components, permission checks, headless hooks, and the passkey tiers of @adonis-agora/authkit-react.
Typed Client & TanStack Query
useResource, createAuthkitClient, AuthkitClientProvider, query/mutation hooks, query keys, and error handling for the admin and account APIs.