netlify-identity

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Netlify Identity

Netlify Identity

Netlify Identity is a user management service for signups, logins, password recovery, user metadata, and role-based access control. It is built on GoTrue and issues JSON Web Tokens (JWTs).
Always use
@netlify/identity
.
Never use
netlify-identity-widget
or
gotrue-js
— they are deprecated.
@netlify/identity
provides a unified, headless TypeScript API that works in both browser and server contexts (Netlify Functions, Edge Functions, SSR frameworks).
Netlify Identity 是一款提供用户注册、登录、密码找回、用户元数据管理、基于角色访问控制能力的用户管理服务。它基于 GoTrue 构建,可签发 JSON Web Tokens (JWT)。
请始终使用
@netlify/identity
切勿使用
netlify-identity-widget
gotrue-js
——这两个库已被正式弃用。
@netlify/identity
提供了统一的无头 TypeScript API,可同时在浏览器和服务端环境(Netlify Functions、Edge Functions、SSR 框架)中运行。

Setup

安装配置

bash
npm install @netlify/identity
Identity is automatically enabled when a deploy created by a Netlify Agent Runner session includes Identity code. Otherwise, it must be manually enabled in the UI. These are the default settings:
  • Registration — Open (anyone can sign up). Change to Invite only in Project configuration > Identity if needed.
  • Autoconfirm — Off (new signups require email confirmation). Enable in Project configuration > Identity to skip confirmation during development.
bash
npm install @netlify/identity
当 Netlify Agent Runner 会话生成的部署包中包含 Identity 相关代码时,Identity 功能会自动启用。否则需要在 Netlify UI 中手动开启。默认配置如下:
  • 注册权限 —— 开放(任何人都可注册)。如有需要可在 项目配置 > Identity 中修改为仅邀请注册。
  • 自动确认 —— 关闭(新注册用户需要通过邮件验证身份)。开发阶段可在 项目配置 > Identity 中开启该选项,跳过验证步骤。

Local Development

本地开发

Identity does not currently work with
netlify dev
. You must deploy to Netlify to test Identity features. Use
npx netlify deploy
for preview deploys during development. This limitation may be resolved in a future release.
Identity 目前不支持
netlify dev
本地环境中运行,你必须将项目部署到 Netlify 才能测试 Identity 相关功能。开发过程中可使用
npx netlify deploy
命令创建预览部署进行测试,该限制可能会在后续版本中修复。

Quick Start

快速开始

Log in from the browser:
typescript
import { login, getUser } from '@netlify/identity'

const user = await login('user@example.com', '<password>')
console.log(`Hello, ${user.name}`)

// Later, check auth state
const currentUser = await getUser()
Protect a Netlify Function:
typescript
// netlify/functions/protected.mts
import { getUser } from '@netlify/identity'
import type { Context } from '@netlify/functions'

export default async (req: Request, context: Context) => {
  const user = await getUser()
  if (!user) return new Response('Unauthorized', { status: 401 })
  return Response.json({ id: user.id, email: user.email })
}
从浏览器端登录:
typescript
import { login, getUser } from '@netlify/identity'

const user = await login('user@example.com', '<password>')
console.log(`Hello, ${user.name}`)

// 后续校验登录状态
const currentUser = await getUser()
保护 Netlify Function 接口:
typescript
// netlify/functions/protected.mts
import { getUser } from '@netlify/identity'
import type { Context } from '@netlify/functions'

export default async (req: Request, context: Context) => {
  const user = await getUser()
  if (!user) return new Response('Unauthorized', { status: 401 })
  return Response.json({ id: user.id, email: user.email })
}

Core API

核心 API

Import and use headless functions directly:
typescript
import {
  getUser,
  handleAuthCallback,
  login,
  logout,
  signup,
  oauthLogin,
  onAuthChange,
  getSettings,
} from '@netlify/identity'
你可以直接导入并使用无头函数:
typescript
import {
  getUser,
  handleAuthCallback,
  login,
  logout,
  signup,
  oauthLogin,
  onAuthChange,
  getSettings,
} from '@netlify/identity'

Login

登录

typescript
import { login, AuthError } from '@netlify/identity'

async function handleLogin(email: string, password: string) {
  try {
    const user = await login(email, password)
    showSuccess(`Welcome back, ${user.name ?? user.email}`)
  } catch (error) {
    if (error instanceof AuthError) {
      showError(error.status === 401 ? 'Invalid email or password.' : error.message)
    }
  }
}
typescript
import { login, AuthError } from '@netlify/identity'

async function handleLogin(email: string, password: string) {
  try {
    const user = await login(email, password)
    showSuccess(`欢迎回来,${user.name ?? user.email}`)
  } catch (error) {
    if (error instanceof AuthError) {
      showError(error.status === 401 ? '邮箱或密码错误' : error.message)
    }
  }
}

Signup

注册

After signup, check
user.emailVerified
to determine if the user was auto-confirmed or needs to confirm their email.
typescript
import { signup, AuthError } from '@netlify/identity'

async function handleSignup(email: string, password: string, name: string) {
  try {
    const user = await signup(email, password, { full_name: name })
    if (user.emailVerified) {
      // Autoconfirm ON — user is logged in immediately
      showSuccess('Account created. You are now logged in.')
    } else {
      // Autoconfirm OFF — confirmation email sent
      showSuccess('Check your email to confirm your account.')
    }
  } catch (error) {
    if (error instanceof AuthError) {
      showError(error.status === 403 ? 'Signups are not allowed.' : error.message)
    }
  }
}
注册完成后,可通过
user.emailVerified
字段判断用户是否已自动确认,还是需要验证邮箱。
typescript
import { signup, AuthError } from '@netlify/identity'

async function handleSignup(email: string, password: string, name: string) {
  try {
    const user = await signup(email, password, { full_name: name })
    if (user.emailVerified) {
      // 自动确认已开启:用户直接登录成功
      showSuccess('账号创建成功,你已自动登录')
    } else {
      // 自动确认已关闭:验证邮件已发送
      showSuccess('请查收邮件完成账号验证')
    }
  } catch (error) {
    if (error instanceof AuthError) {
      showError(error.status === 403 ? '当前暂不开放注册' : error.message)
    }
  }
}

Logout

登出

typescript
import { logout } from '@netlify/identity'

await logout()
typescript
import { logout } from '@netlify/identity'

await logout()

OAuth

OAuth

OAuth is a two-step flow:
oauthLogin(provider)
redirects away from the site, then
handleAuthCallback()
processes the redirect when the user returns.
typescript
import { oauthLogin } from '@netlify/identity'

// Step 1: Redirect to provider (navigates away — never returns)
function handleOAuthClick(provider: 'google' | 'github' | 'gitlab' | 'bitbucket') {
  oauthLogin(provider)
}
Enable providers in Project configuration > Identity > External providers before using OAuth.
OAuth 是两步流程:首先调用
oauthLogin(provider)
跳转到第三方授权页,用户授权返回后调用
handleAuthCallback()
处理回调结果。
typescript
import { oauthLogin } from '@netlify/identity'

// 第一步:跳转到第三方授权页(会离开当前站点,无返回值)
function handleOAuthClick(provider: 'google' | 'github' | 'gitlab' | 'bitbucket') {
  oauthLogin(provider)
}
使用 OAuth 前需要在 项目配置 > Identity > 外部提供商 中开启对应服务商的授权。

Handling Callbacks

回调处理

Always call
handleAuthCallback()
on page load in any app that uses OAuth, password recovery, invites, or email confirmation. It processes all callback types via the URL hash.
typescript
import { handleAuthCallback, AuthError } from '@netlify/identity'

async function processCallback() {
  try {
    const result = await handleAuthCallback()
    if (!result) return // No callback hash — normal page load

    switch (result.type) {
      case 'oauth':
        showSuccess(`Logged in as ${result.user?.email}`)
        break
      case 'confirmation':
        showSuccess('Email confirmed. You are now logged in.')
        break
      case 'recovery':
        // User is authenticated but must set a new password
        showPasswordResetForm(result.user)
        break
      case 'invite':
        // User must set a password to accept the invite
        showInviteAcceptForm(result.token)
        break
      case 'email_change':
        showSuccess('Email address updated.')
        break
    }
  } catch (error) {
    if (error instanceof AuthError) showError(error.message)
  }
}
所有使用 OAuth、密码找回、邀请注册、邮箱验证的应用,都需要在页面加载时调用
handleAuthCallback()
,它会通过 URL hash 处理所有类型的回调请求。
typescript
import { handleAuthCallback, AuthError } from '@netlify/identity'

async function processCallback() {
  try {
    const result = await handleAuthCallback()
    if (!result) return // 无回调 hash,为普通页面加载

    switch (result.type) {
      case 'oauth':
        showSuccess(`登录成功,账号为 ${result.user?.email}`)
        break
      case 'confirmation':
        showSuccess('邮箱验证成功,你已自动登录')
        break
      case 'recovery':
        // 用户已通过验证,需要设置新密码
        showPasswordResetForm(result.user)
        break
      case 'invite':
        // 用户需要设置密码来接受邀请
        showInviteAcceptForm(result.token)
        break
      case 'email_change':
        showSuccess('邮箱地址已更新')
        break
    }
  } catch (error) {
    if (error instanceof AuthError) showError(error.message)
  }
}

Auth State

认证状态

typescript
import { getUser, onAuthChange, AUTH_EVENTS } from '@netlify/identity'

// Check current user (never throws — returns null if not authenticated)
const user = await getUser()

// Subscribe to auth state changes (returns unsubscribe function)
const unsubscribe = onAuthChange((event, user) => {
  switch (event) {
    case AUTH_EVENTS.LOGIN:
      console.log('Logged in:', user?.email)
      break
    case AUTH_EVENTS.LOGOUT:
      console.log('Logged out')
      break
    case AUTH_EVENTS.TOKEN_REFRESH:
      break
    case AUTH_EVENTS.USER_UPDATED:
      console.log('Profile updated:', user?.email)
      break
    case AUTH_EVENTS.RECOVERY:
      console.log('Password recovery initiated')
      break
  }
})
typescript
import { getUser, onAuthChange, AUTH_EVENTS } from '@netlify/identity'

// 校验当前登录用户(不会抛出异常,未登录时返回 null)
const user = await getUser()

// 订阅认证状态变化(返回取消订阅的函数)
const unsubscribe = onAuthChange((event, user) => {
  switch (event) {
    case AUTH_EVENTS.LOGIN:
      console.log('用户登录:', user?.email)
      break
    case AUTH_EVENTS.LOGOUT:
      console.log('用户登出')
      break
    case AUTH_EVENTS.TOKEN_REFRESH:
      break
    case AUTH_EVENTS.USER_UPDATED:
      console.log('用户资料更新:', user?.email)
      break
    case AUTH_EVENTS.RECOVERY:
      console.log('已发起密码找回请求')
      break
  }
})

Settings-Driven UI

配置驱动 UI

Fetch the project's Identity settings to conditionally render signup forms and OAuth buttons.
typescript
import { getSettings } from '@netlify/identity'

const settings = await getSettings()
// settings.autoconfirm — boolean
// settings.disableSignup — boolean
// settings.providers — Record<AuthProvider, boolean>

if (!settings.disableSignup) showSignupForm()

for (const [provider, enabled] of Object.entries(settings.providers)) {
  if (enabled) showOAuthButton(provider)
}
你可以拉取项目的 Identity 配置,来动态渲染注册表单和 OAuth 登录按钮。
typescript
import { getSettings } from '@netlify/identity'

const settings = await getSettings()
// settings.autoconfirm — 布尔值,是否开启自动确认
// settings.disableSignup — 布尔值,是否关闭注册
// settings.providers — 记录各个 OAuth 提供商是否开启的对象

if (!settings.disableSignup) showSignupForm()

for (const [provider, enabled] of Object.entries(settings.providers)) {
  if (enabled) showOAuthButton(provider)
}

Minimal React Example

最小 React 示例

tsx
import { useEffect, useState } from 'react'
import {
  getUser,
  handleAuthCallback,
  login,
  logout,
  oauthLogin,
  onAuthChange,
} from '@netlify/identity'

function App() {
  const [user, setUser] = useState(null)
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    ;(async () => {
      await handleAuthCallback()
      setUser(await getUser())
      setLoading(false)
    })()
    return onAuthChange((_event, currentUser) => setUser(currentUser))
  }, [])

  const handleLogin = async (email, password) => {
    const currentUser = await login(email, password)
    setUser(currentUser)
  }

  const handleGoogleLogin = () => oauthLogin('google')

  const handleSignOut = async () => {
    await logout()
    setUser(null)
  }

  if (loading) return <p>Loading...</p>
  // Render login form or user details based on `user` state
}
tsx
import { useEffect, useState } from 'react'
import {
  getUser,
  handleAuthCallback,
  login,
  logout,
  oauthLogin,
  onAuthChange,
} from '@netlify/identity'

function App() {
  const [user, setUser] = useState(null)
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    ;(async () => {
      await handleAuthCallback()
      setUser(await getUser())
      setLoading(false)
    })()
    return onAuthChange((_event, currentUser) => setUser(currentUser))
  }, [])

  const handleLogin = async (email, password) => {
    const currentUser = await login(email, password)
    setUser(currentUser)
  }

  const handleGoogleLogin = () => oauthLogin('google')

  const handleSignOut = async () => {
    await logout()
    setUser(null)
  }

  if (loading) return <p>加载中...</p>
  // 根据 `user` 状态渲染登录表单或用户信息
}

Error Handling

错误处理

@netlify/identity
throws two error classes:
  • AuthError
    — Thrown by auth operations. Has
    message
    , optional
    status
    (HTTP status code), and optional
    cause
    .
  • MissingIdentityError
    — Thrown when Identity is not configured in the current environment.
getUser()
and
isAuthenticated()
never throw — they return
null
and
false
respectively on failure.
StatusMeaning
401Invalid credentials or expired token
403Action not allowed (e.g., signups disabled)
422Validation error (e.g., weak password, malformed email)
404User or resource not found
@netlify/identity
会抛出两类错误:
  • AuthError
    —— 认证操作相关错误,包含
    message
    字段,可选的
    status
    (HTTP 状态码)和
    cause
    字段。
  • MissingIdentityError
    —— 当前环境未配置 Identity 功能时抛出。
getUser()
isAuthenticated()
永远不会抛出异常,失败时会分别返回
null
false
状态码含义
401凭证无效或 token 已过期
403无操作权限(例如注册功能已关闭)
422参数校验失败(例如密码强度不足、邮箱格式错误)
404用户或资源不存在

Identity Event Functions

Identity 事件函数

Special serverless functions that trigger on Identity lifecycle events. These use the legacy named
handler
export
(not the modern default export).
Event names:
identity-validate
,
identity-signup
,
identity-login
typescript
// netlify/functions/identity-signup.mts
import type { Handler, HandlerEvent, HandlerContext } from '@netlify/functions'

const handler: Handler = async (event: HandlerEvent, context: HandlerContext) => {
  const { user } = JSON.parse(event.body || '{}')

  return {
    statusCode: 200,
    body: JSON.stringify({
      app_metadata: {
        ...user.app_metadata,
        roles: ['member'],
      },
    }),
  }
}

export { handler }
The response body replaces
app_metadata
and/or
user_metadata
on the user record — include all fields you want to keep.
这是一类特殊的 Serverless 函数,会在 Identity 生命周期事件触发时执行。它们使用传统的命名
handler
导出
(而非现代的默认导出)。
事件名称:
identity-validate
identity-signup
identity-login
typescript
// netlify/functions/identity-signup.mts
import type { Handler, HandlerEvent, HandlerContext } from '@netlify/functions'

const handler: Handler = async (event: HandlerEvent, context: HandlerContext) => {
  const { user } = JSON.parse(event.body || '{}')

  return {
    statusCode: 200,
    body: JSON.stringify({
      app_metadata: {
        ...user.app_metadata,
        roles: ['member'],
      },
    }),
  }
}

export { handler }
返回的 body 会替换用户记录中的
app_metadata
和/或
user_metadata
——请包含所有你需要保留的字段。

Roles and Authorization

角色与授权

  • app_metadata.roles
    — Server-controlled. Only settable via the Netlify UI, admin API, or Identity event functions. Never let users set their own roles.
  • user_metadata
    — User-controlled. Users can update via
    updateUser({ data: { ... } })
    .
  • app_metadata.roles
    —— 服务端控制的角色,仅可通过 Netlify UI、管理 API 或 Identity 事件函数修改,绝对不允许用户自行设置角色。
  • user_metadata
    —— 用户可控的元数据,用户可通过
    updateUser({ data: { ... } })
    更新。

Role-Based Redirects

基于角色的重定向

toml
undefined
toml
undefined

netlify.toml

netlify.toml

[[redirects]] from = "/admin/*" to = "/admin/:splat" status = 200 conditions = { Role = ["admin"] }
[[redirects]] from = "/admin/*" to = "/" status = 302

Rules are evaluated top-to-bottom. The `nf_jwt` cookie is read by the CDN to evaluate role conditions.
[[redirects]] from = "/admin/*" to = "/admin/:splat" status = 200 conditions = { Role = ["admin"] }
[[redirects]] from = "/admin/*" to = "/" status = 302

重定向规则从上到下依次匹配,CDN 会读取 `nf_jwt` cookie 来校验角色条件。

References

参考资料

  • Advanced patterns — password recovery, invite acceptance, email change, session hydration, SSR integration
  • 高级用法 —— 密码找回、邀请接受、邮箱修改、会话恢复、SSR 集成