netlify-identity
Add authentication and user management to a Netlify site with @netlify/identity — signup/login/logout, OAuth social login (Google/GitHub/GitLab/Bitbucket), server-side user verification in Functions, role-based access control (RBAC), admin user management, and Identity event hooks. Use when adding a
By netlify · 2,205 installs
npx skills add netlify/context-and-tools --skill netlify-identity
Source repository · Upstream listing
Netlify Identity
Auth and user management for a Netlify site without requiring visitors to be Netlify users. Package: @netlify/identity .
Reach for @netlify/identity . Do NOT use the legacy netlify identity widget or gotrue js for new work — same capabilities, simpler API, built in server side support.
Footguns — read first
Identity does not work under netlify dev . Test auth flows on a deploy — Deploy Previews work. Local netlify dev cannot exercise /.netlify/identity/ .
Never build a from scratch third party OAuth flow beside Identity — no provider app registration in code, no client id / secret in code, no custom callback token exchange. Use oauthLogin() + handleAuthCallback() . Raw OAuth beside Identity is the single most common source of rework.
Identity config has no public API — dashboard only. Never curl api.netlify.com to flip/inspect Identity settings, never read tokens from ~/Library/Preferences/netlify/config.json , never probe undocumented endpoints.
RBAC redirects without a fallback = raw 404. A visitor lacking the role gets a bare 404 with no way to log in. Always add a fallback rule.
Server side login() / signup() / logout() need CSRF protection. Call verifyRequestOrigin(req) first, or an attacker can log a victim into the attacker's account.
Site gating ("lock this site to my company", employees only) → route to netlify access control first. Identity is the app level user layer only.
On failure (callback 404s, /.netlify/identity/ unreachable, OAuth doesn't return): surface the error, the dashboard URL, and the setting to check — then stop. Do not invent recovery commands.
Setup
Identity must be enabled in the dashboard first (no API): Project configuration Identity ( https://app.netlify.com/projects/{site name}/configuration/identity ) → Enable Identity .
HTTPS is required. On a custom domain, get HTTPS/SSL working before integrating Identity.
Client / universal auth
Callback handling is mandatory. Call handleAuthCallback() on your landing page. It processes ALL token types in the URL hash — OAuth redirect, email confirmation, password recovery, invite. Without it, confirmation links and OAuth redirects never complete.
Other client functions:
recoverPassword() — complete a password reset (alternative to letting handleAuthCallback() handle the recovery token ).
acceptInvite() — complete invite acceptance (alternative to handleAuthCallback() handling invite token ).
refreshSession() — refresh token/session so newly assigned roles take effect.
Don't hard code which providers exist. Call getSettings() at startup and render the signup form and OAuth buttons from what it returns.
Server side (Functions / Edge Functions)
Handlers are modern v2 functions: export default async (req, context) = {} . v1 export { handler } is not supported for getUser() / login() / admin. .
getUser() works in browser, Netlify Functions, and Edge Functions.
CSRF — always guard exposed login / signup / logout endpoints:
admin — Netlify Functions ONLY
admin. uses a short lived admin token and runs only in Netlify Functions — NOT browser, NOT Edge Functions.
admin.listUsers() — array of users.
admin.updateUser() — update a user (e.g. roles). Full API: https://www.npmjs.com/package/@netlify/identity
Session cookies
JWT stored in cookie nf jwt , sent automatically. Server side login / signup / logout read/write nf jwt and nf refresh via the runtime, so the browser gets the session in the response.
The User object
id , email , roles (array from app metadata.roles , included in the JWT).
Identity event functions
Functions the platform invokes automatically on Identity events (you don't call them).
Modern typed handler syntax — export a default object with a method per event. Typed handlers require @netlify/functions ≥ 5.2.0.
Handlers and triggers:
Handler Fires when
userValidate User attempts signup, before account creation — block by email domain, rate limit, custom validation.
userSignup Signup completes (email or external). Fires after email confirmation if confirmation is enabled. Assign roles, sync, notify.
userLogin User logs in — track logins, sync, block a user.
userModified Profile updated.
userDeleted User deleted (notification only).
Deny an action: call event.deny() from userValidate / userSignup / userLogin / userModified (NOT userDeleted ). User gets a 401 ; no observability error. With multiple subscribers, the first event.deny() aborts the chain.
Assign roles at signup — return { user: {...} } to mutate the persisted record. Payload fields are camelCase ( appMetadata , userMetadata , confirmedAt ).
Background mode — action completes immediately, handler runs async:
Event types from @netlify/functions : UserValidateEvent , UserSignupEvent , UserLoginEvent , UserModifiedEvent , UserDeletedEvent , Config .
Registration & providers (dashboard)
Registration preferences — Open (default: any visitor signs up via signup() ) or Invite only (all new users, including external provider logins, must be invited first).
Confirmation: open registration sends a confirmation email; skip via Emails Confirmation template Configure (allow signup without verifying email / autoconfirm).
External providers — enable Google/GitHub/GitLab/Bitbucket under Registration External providers . Set your own client ID/secret for branded OAuth (your app name shows on the provider screen). No email confirmation for external provider signup, but Invite only still requires an invite.
Invitations — Project configuration Identity Users ; Netlify team users with any role can invite. Invite link carries an invite token → process with handleAuthCallback() or acceptInvite() .
Roles & metadata
Stored on the User object; edit in Identity Users Edit settings :
Name — user editable: user metadata.full name .
Email — user editable; triggers email change confirmation; changes login credentials: user metadata.email .
Roles — NOT user editable: app metadata.roles . Read via getUser() .
Set roles: at signup via userSignup handler returning { user: {...} } ; for existing users via admin.updateUser() in a Function. Role changes take effect on next login or token refresh , not immediately (they don't invalidate the current JWT — client can refreshSession() ).
Role based access control (redirect rules)
Enforced at the CDN edge (no origin round trip). Add a Role parameter to redirect rules.
Netlify Identity roles resolve at app metadata.roles .
External JWT provider (Enterprise; alternative to Identity)
You may use Identity OR an external JWT provider, not both — you cannot authenticate third party JWT tokens while Identity is enabled. Set the secret at Project configuration Access & security Visitor access JWT secret (project level overrides team level default).
Tokens must be HS256 ; header requires "alg": "HS256" , "typ": "JWT" .
Payload requires exp (future Unix Epoch); other fields optional.
External provider roles resolve at app metadata.authorization.roles . Different path → contact support for a custom role path (support configured, not self service).
Emails (Pro+ for customization)
Default sender no reply@netlify.com . Custom sender (Pro+): set SMTP hostname/port/username/password under Emails Outgoing email address (use SendGrid/Mailjet/etc. for volume).
Custom templates (Pro+): publish HTML to a path on your deployed project, set the path (relative to domain, starting / ) under Emails . Rules: inline CSS only, absolute image links, NO <html / <head / <body tags. Keep template variables intact — don't let your build rewrite them.
Go template variables: {{ .Email }} , {{ .NewEmail }} (email change only), {{ .SiteURL }} , {{ .ConfirmationURL }} , {{ .Token }} . Custom link form: {{ .SiteURL }}/path/ confirmation token={{ .Token }} (also invite token , recovery token , email change token ).
Audit log (Pro+)
Project configuration Identity Identity audit log . Search with a required scope prefix: author:[string] or action:[string] . Action names: login , logout , user signedup , user deleted , user modified , token revoked , token refreshed , user recovery requested , user invited .
Plan gating
Identity itself: all credit based plans, no extra cost. Unlimited active + invite only users, custom OAuth credentials, Functions integration — all plans.
Pro+ only: custom outgoing email, custom email templates, Identity audit log.
Enterprise only: external JWT providers.
Deep guides
references/advanced patterns.md — SSR / session hydration.
references/authorization and sessions.md .
Legacy (avoid for new work)
netlify identity widget / gotrue js — superseded by @netlify/identity .
Legacy event function filenames ( identity validate.ts , identity signup.ts , identity login.ts , background suffix) still work but prefer typed handlers. Legacy denial = return non 2xx status; new code uses event.deny() .
<! getSettings() referenced in house rules but not documented in sources; its return shape/signature is not specified in the intermediate.
<! system: agent context/identity/system.md — human owned, merged by ctx gen; edit system.md, not this section
Netlify house rules (identity)
These are org conventions, not docs facts — merged into the rendered skill by
ctx gen and never generated. Owned by the skills maintainer.
1. Deep guides live in this skill: references/advanced patterns.md
(SSR/session hydration) and references/authorization and sessions.md .
2. Identity does not work under netlify dev — test auth flows on deploys
(Deploy Previews work).
3. Identity configuration has no public API — it is dashboard only. Never curl
api.netlify.com to flip or inspect Identity settings, never read auth
tokens from ~/Library/Preferences/netlify/config.json , never probe for
undocumented endpoints.
4. On failure (callback 404s, /.netlify/identity/ unreachable, OAuth flow
doesn't return), surface the error, the dashboard URL, and the setting to
check — then stop. Do not invent recovery commands.
5. Never build a from scratch third party OAuth flow when Identity is in play —
no provider app registration, no client id / secret in code, no custom
callback token exchange. Use oauthLogin() + handleAuthCallback() ;
raw OAuth beside Identity is the single most common source of rework.
6. Server side getUser() / login() / admin. require modern v2 functions
( export default ) — v1 export { handler } is not supported. Typed
Identity event handlers ( UserSignupEvent , event.deny() ) require
@netlify/functions ≥ 5.2.0; older installs use the legacy filenames.
7. Don't hard code which auth providers exist — call getSettings() at
startup and render the signup form and OAuth buttons from what it returns.
8. Site gating requests ("lock this site to my company", employees only)
route to the netlify access control skill first — Identity is the
app level user layer only.