# Sign In From a Func | Talizen | Talizen

> Implement emailed-code sign-in and password sign-in with your own rules using ctx.auth.login in Talizen Func: the ref must come from a server-verified fact, and every issued session is auditable.

[Talizen API](/)

[EN](/api/auth-func-login.md)/ [中文](/api/auth-func-login.md?lang=zh)

### Overview

- [Talizen API for AI](/api.md)

### Discoverability

- [How to optimize llms.txt](/api/optimize-llms-txt.md)

### Site configuration

- [Configure talizen.config.ts](/api/talizen-config.md)
- [Implement domain-based locale routing](/api/domain-locale-routing.md)

### Backend

- [Calling external APIs on the server and managing the cache](/api/ssr-external-api-cache.md)
- [Build site backend workflows with Func](/api/func-backend.md)
- [JSON tables: definition, reads, and queries](/api/func-json-tables.md)
- [Uploads: signed direct upload and Func-generated files](/api/func-assets-upload.md)
- [Timeouts and streaming responses](/api/func-timeout-streaming.md)
- [Integrate Alipay PC Web Payment with Func](/api/func-alipay-payment.md)

### Auth

- [Require a Verified Email to Sign Up](/api/auth-verified-registration.md)
- [Reset and Change Passwords](/api/auth-password-reset.md)
- [Sign In From a Func](/api/auth-func-login.md)
- [Query users from a Func](/api/func-user-directory.md)

### Integrations

- [Send Email and Verification Codes with Integrations](/api/func-email-integration.md)
- [Take Alipay payments with an integration](/api/func-alipay-integration.md)

### Help center

[Talizen Help](/docs/start.md)

Product guides for building sites in the editor.

AuthSign In From a Func

# Sign In From a Func

ctx.auth.login issues a session for an existing user — for passwordless emailed-code sign-in that the SDK cannot express, or to apply your own ban / onboarding / tenant rules at sign-in. Full code, the one rule you must not break, and where the audit trail lives.

For most sites `useAuth().login()` is all you need. There are exactly two reasons to implement
sign-in as a Func: **passwordless login with an emailed code**, which the SDK cannot express,
or **server-side rules at sign-in** — ban lists, required onboarding, multi-tenant checks on
which accounts may enter which tenant.

The primitive is `ctx.auth.login(ref)`: it issues a session for an **existing**
user. It takes no password and no code — what makes the sign-in allowed is your code's decision.

**Scope**

Covers `ctx.auth.login` together with
`ctx.users.checkPassword`, `ctx.users.find` and `ctx.email.sendCode` /
`verifyCode`. For password changes see
[Reset and Change Passwords](/api/auth-password-reset.md); for signup see
[Require a Verified Email to Sign Up](/api/auth-verified-registration.md).

## One rule first

This goes first because it is the only way to get this capability badly wrong:

> **The argument to `ctx.auth.login` must come from a fact the server just**
> **verified.** Resolve the user with `ctx.users.find` or `ctx.users.checkPassword`
> first, then hand `user.id` to `login`. Writing
> `ctx.auth.login({ email: input.email })` means "whoever the browser claims to be" — that is not
> sign-in, it lets anyone in as any account.

Why single it out: a botched password change locks the real user out, so they notice.
**Impersonation is silent** — nobody finds out.

## Passwordless: sign in with an emailed code

Requires a connected email integration, see
[Send email and verification codes through an integration](/api/func-email-integration.md).

```typescript
// /backend/func/auth/login.ts
import type { TalizenFuncContext } from 'talizen/func-runtime'

const SCENE = 'login'

// Step 1: ask for a code. Same shape as password reset — look the user up first,
// and return the same thing either way
export function requestCode(input: { email?: string }, ctx: TalizenFuncContext) {
  const email = (input.email ?? '').trim()
  if (!email) throw new Error('email is required')

  const user = ctx.users.find({ email })
  if (user) {
    try {
      ctx.email.sendCode({ to: email, scene: SCENE })
    } catch (err) {
      console.error('send login code failed', String(err))
    }
  }
  return { ok: true }
}

// Step 2: verify the code and sign in
export function withCode(
  input: { email?: string; code?: string },
  ctx: TalizenFuncContext,
) {
  const email = (input.email ?? '').trim()
  const code = (input.code ?? '').trim()
  if (!email || !code) throw new Error('email and code are required')

  const user = ctx.users.find({ email })
  if (!user) throw new Error('the code is wrong or has expired')

  if (!ctx.email.verifyCode({ to: email, scene: SCENE, code }))
    throw new Error('the code is wrong or has expired')

  // Use the id the server resolved, not input.email
  return ctx.auth.login({ userId: user.id })
}
```

It returns the signed-in user. The session cookie is written onto that response, so a resolved call means
the visitor is signed in. **Func code never sees the session token** and cannot mint one.

## Password sign-in with your own rules

```typescript
export function withPassword(
  input: { account: string; password: string },
  ctx: TalizenFuncContext,
) {
  // Failures share the platform's /auth/login allowance: 5 wrong attempts locks
  // for an hour, so this is not a way around the login lockout
  if (!ctx.users.checkPassword({ account: input.account, password: input.password }))
    throw new Error('wrong account or password')

  const user = ctx.users.find({ account: input.account })
  if (!user) throw new Error('wrong account or password')

  // This is the actual reason to sign in through a Func: rules the platform
  // knows nothing about
  if (ctx.db.get('banned_users', user.id)) throw new Error('this account is suspended')
  if (!user.profile?.onboarded) return { ok: false, next: '/onboarding' }

  return ctx.auth.login({ userId: user.id })
}
```

After too many wrong attempts `checkPassword` throws **429** instead of returning
`false`. Give it its own message ("too many attempts, try again later"), or the visitor will keep
retrying until fully locked out.

## Page code

```tsx
import { invoke } from 'talizen/func'
import { useAuth } from 'talizen/auth'

async function loginWithCode(email: string, code: string) {
  await invoke('auth/login.withCode', { email, code })
  // The session cookie arrived with that response; refresh the hook's state
  await useAuth().refresh()
  window.location.href = '/account'
}
```

Signing out still uses `useAuth().logout()`; no Func needed.

## Every sign-in is recorded

The platform records **every session it issues**: time, source, IP, device. The source is one
of `Direct`, `OAuth` (with the provider), `Sign-up`, or `Func`
(with **which Func file** issued it).

In the editor under **Backend → Users**, open a user's row menu →
**Login history**.

> This is not only a convenience feature. Sign-in through Func is the one path where site code
> decides who is signed in, so which file issued a session for which account has to be traceable — if the rule
> at the top of this page is ever broken, this is the only place it shows up.

## Other things to know

| Case | Behaviour |
| --- | --- |
| Direct `/auth/login` from the browser | **Keeps working.** There is no switch that routes login exclusively through Func; the two coexist, so you can add a Func for one entry point only |
| Unknown user | `login` throws 404. Do not pass that to the browser — answer with one undistinguished message like "wrong account or password" |
| Disabled account | Throws 403, matching direct login |
| The three ref fields | Pass **exactly one** of `email` / `account` / `userId`; two is a 400 |
| Accounts that only use a provider | `login` still issues a session (it does not care whether a password exists); `checkPassword` returns `false` for them |
| OAuth sign-in | Still handled by the platform. Do not write callbacks or token exchange in Func |

See also: [Reset and Change Passwords](/api/auth-password-reset.md),
[Require a Verified Email to Sign Up](/api/auth-verified-registration.md),
[Send email and verification codes through an integration](/api/func-email-integration.md).

[Previous\
\
Reset and Change Passwords](/api/auth-password-reset.md) [Next\
\
Query users from a Func](/api/func-user-directory.md)

On this page

- [One rule first](#rule)
- [Passwordless: sign in with an emailed code](#code-login)
- [Password sign-in with your own rules](#password-login)
- [Page code](#page)
- [Every sign-in is recorded](#audit)
- [Other things to know](#notes)

Looking for the help center?

[Talizen Help](/docs/start.md)

> 全站页面清单：[/llms.txt](/llms.txt)
