---
title: Sign-in and users
description: Accounts, passwords, sign-in links, providers, two-factor codes, invitations and who may do what.
section: Data
order: 5
---

# Sign-in and users

<p class="lead">People are a collection like any other, with sign-in built in. There is no auth service to add: 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;   // who is signed in
sluurp.logout();
```

A sign-in answers a token that lasts 14 days; `authRefresh()` swaps it for a fresh one. Passwords are at least 8 characters and stored as Argon2 hashes.

| | |
|---|---|
| **A link by mail** | `requestSigninLink(email)`: no password at all. The link works once, and expires |
| **Password reset** | `requestPasswordReset(email)` mails a link; `confirmPasswordReset(token, password)` sets the new one |
| **Other providers** | Google, GitHub, Microsoft, GitLab, or any OpenID Connect provider, set up in the admin UI under **Sign-in**. `location.href = users.oauthUrl("google")`, and `captureOAuthToken(sluurp)` on the page it comes back to |
| **Two-factor codes** | Six digits from an authenticator app, after the first factor. Five wrong tries and the attempt is over |

There are no recovery codes: someone who loses their phone asks an administrator, who turns the second factor off from their record and signs them out everywhere.

## Who may join

A setting on the app, enforced by the server:

- `invite`, the default: only with an invitation.
- `open`: anyone may sign up, under the collection's create rule.
- `closed`: nobody, for accounts that come from a directory.

An invitation is filled in twice. Whoever invites decides the fields that are theirs to decide, such as the organisation and the role, and the sign-up can't change them. The person joining fills in the rest: their name, a password.

```ts title="app.ts"
await users.invite("parent@example.com", {
  fields: { org: school.id, roles: ["parent"] },
  collect: ["first_name", "last_name"],
});
```

## Roles

A person's `roles` is a list that [rules](/docs/rules) can ask about: `@request.auth.roles ~ "teacher"`. It matches whole roles only, so "admin" never matches "superadmin".

## For administrators

- **Impersonate** someone from their record in the admin UI, to see the app as they do.
- **Sign out everywhere**: revoking a person's tokens ends every session they have, at once.
- **Superusers** are separate from the app's people: `sluurp superuser EMAIL PASSWORD` makes one.
