# Sign In From a Func | Creght

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

[![Creght](https://ugc.talizen.com/_assets/site/2061660904709165056/1780797461299__creght_logo.png)API for AI](/)

[View llms.txt](/llms.txt)

Overview

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

Discoverability

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

Site configuration

- [Implement domain-based locale routing](/api/domain-locale-routing.md)

Backend

- [Build site backend workflows with Func](/api/func-backend.md)
- [Integrate Alipay PC Web Payment with Func](/api/func-alipay-payment.md)

Integrations

- [Send Email and Verification Codes with Integrations](/api/func-email-integration.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)

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)

Auth/Sign 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.

Copy Markdown link

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).

![Creght](https://ugc.talizen.com/_assets/site/2061660904709165056/1780797461299__creght_logo.png)

This website is built with [Creght](/)

[Discord](https://discord.gg/Qvq2nmZNnb)

## Links

- [Pricing](/price.md)
- [Solutions](/solution.md)
- [Customers](/customers.md)
- [Help Center](/help.md)
- [Contact Us](/contact.md)
- [Update Logs & Blogs](/blogs.md)
- [Refund Policy](/tuikuan.md)

## Resources

- [All Resources](/resources.md)
- [Templates](/templates.md)
- [Components](https://creghtlib.site.creght.com)
- [Animations](/effects.md)
- [Figma to Creght](/figma2creght.md)
- [API for AI](/api.md)

## Terms

- [Terms of Service](/legal/terms.md)
- [Privacy Policy](/legal/privacy.md)
- [Acceptable Use Policy](/legal/acceptable-use.md)

## Social Media

- [Twitter](https://twitter.com)
- [Facebook](https://www.facebook.com)
- [LinkedIn](https://www.linkedin.com)

[蜀ICP备2023038192号-2](https://beian.miit.gov.cn)
