---
title: Sign-in and users
description: Accounts, passwords, sign-in links, providers, two-factor codes, invitations and who may do what.
section: Data
order: 6
---

# Sign-in and users

<p class="lead">Users are a regular collection with auth built in. No separate auth service: accounts, sessions and permissions live in the same binary as the data they protect.</p>

## Signing in

```ts title="app.ts"
const users = sluurp.collection("users");
await users.authWithPassword(email, password);

sluurp.authStore.record;   // the signed-in user
sluurp.logout();
```

Sign-in returns a token valid for 14 days; `authRefresh()` exchanges it for a fresh one. Passwords need at least 8 characters and are stored as Argon2 hashes.

| | |
|---|---|
| **Magic link** | `requestSigninLink(email)`: passwordless. Single-use, and expires |
| **Password reset** | `requestPasswordReset(email)` emails a link; `confirmPasswordReset(token, password)` sets the new password |
| **OAuth** | Google, GitHub, Microsoft, GitLab, or any OpenID Connect provider, configured under **Sign-in** in the admin UI. Redirect with `location.href = users.oauthUrl("google")` and call `captureOAuthToken(sluurp)` on the return page |
| **Two-factor** | A 6-digit TOTP code after the first factor. Five wrong attempts end the attempt |

There are no recovery codes: a user who loses their phone asks an admin, who disables 2FA on their record and signs them out everywhere.

## Who may join

An app-level setting, enforced by the server:

- `invite` (default): invitation only.
- `open`: anyone can sign up, subject to the collection's create rule.
- `closed`: no sign-ups, for accounts synced from a directory.

An invitation has two parts. The inviter sets fields like organisation and role, which the invitee can't change. The invitee fills in the rest: name, password.

```ts title="app.ts"
await users.invite("parent@example.com", {
  fields: { org: school.id, roles: ["parent"] },
  collect: ["first_name", "last_name"],
});
```

## Roles

A user's `roles` is a list that [rules](/docs/rules) can check: `@request.auth.roles ~ "teacher"`. Only whole roles match, so "admin" never matches "superadmin".

## For administrators

- **Impersonate** a user from their record in the admin UI to see the app as they do.
- **Sign out everywhere**: revoking a user's tokens ends all their sessions immediately.
- **Superusers** are separate from app users: create one with `sluurp superuser EMAIL PASSWORD`.
