# Authentication

HostEdge Auth adds sign-in to an app that runs on HostEdge. Your users live in **your** project database, so they never leave your project and you can query them with SQL. It works in a Worker or the server side of a framework.

It covers:

- email and password, with sessions
- magic links (sign in from an emailed link)
- password reset
- optional email confirmation
- sign-in with Google or GitHub
- rate limits that slow down guessing and email abuse

## Set it up

1. Open **Project › Storage** and open a SQL database (add one if you have none).
2. In the **Authentication** panel choose **Set up authentication**. This creates the `auth_*` tables.
3. Download `hostedge-auth.ts` from the panel, or fetch it from `https://hostedge.app/auth-kit/hostedge-auth.ts`, and put it in your project. It is one file with no dependencies.
4. Use it in your Worker, then redeploy:

```js
import { createAuth } from './hostedge-auth'

export default {
  async fetch(request, env) {
    const auth = createAuth(env.DB, { env })
    const response = await auth.handle(request) // everything under /auth/…
    if (response) return response

    const user = await auth.getUser(request) // { id, email } or null
    if (!user) return new Response('Sign in first', { status: 401 })
    return new Response(`Hello ${user.email}`)
  },
}
```

Pass `env` so HostEdge can send your emails (see below). The panel also lists the people who signed up, with **Sign out everywhere**, **Disable** and **Delete** for each.

## Endpoints

`auth.handle(request)` answers these. POSTs take a JSON body and must send `content-type: application/json`, which also protects them from cross-site forms.

| Request                                           | What it does                                                |
| ------------------------------------------------- | ----------------------------------------------------------- |
| `POST /auth/sign-up` `{ email, password }`        | Creates the account and signs in (or sends a confirmation)  |
| `POST /auth/sign-in` `{ email, password }`        | Signs in                                                    |
| `POST /auth/sign-out`                             | Ends this browser's session                                 |
| `GET /auth/me`                                    | `{ user }`, or `{ user: null }`                             |
| `POST /auth/magic-link` `{ email, next? }`        | Emails a one-time sign-in link (creates the account if new) |
| `GET /auth/verify?token=…&next=/`                 | The link in that email: signs in and redirects to `next`    |
| `POST /auth/reset-request` `{ email }`            | Emails a password reset link                                |
| `POST /auth/reset` `{ token, password }`          | Sets the new password, signs out other devices, signs in    |
| `GET /auth/oauth/github` and `/auth/oauth/google` | Starts sign-in with the provider                            |

Passwords need at least 8 characters. `next` only accepts paths on your own site.

The reset email links to `/reset-password?token=…` on your site. Build that page: a form that sends the token and a new password to `POST /auth/reset`.

## Emails

HostEdge sends the magic-link, reset and confirmation emails for you, from its own sender and with your project's name, so there is nothing to set up. Every deploy adds `HOSTEDGE_PROJECT_ID`, `HOSTEDGE_MAIL_URL` and `HOSTEDGE_MAIL_SECRET` to your Worker; passing `env` to `createAuth` is all it takes.

| Plan    | Auth emails per day, per project |
| ------- | -------------------------------- |
| Free    | 50                               |
| Starter | 500                              |
| Pro     | 2,000                            |
| Agency  | 10,000                           |

One address receives at most 5 emails an hour from a project, and links can only point at your project's own addresses.

To send them yourself (Resend, Postmark, anything), pass a function instead:

```js
createAuth(env.DB, {
  sendEmail: async ({ to, kind, url }) => {
    // kind is 'magic_link', 'reset' or 'confirm'
    await myMailer.send({ to, subject: 'Your link', text: url })
  },
})
```

## Confirm emails

Add `confirmEmail: true` to make people click an emailed link before they can sign in with a password. Until then, `sign-in` answers `email_not_verified`.

## Google and GitHub

Create an OAuth app with the provider and give it this redirect address:

```
https://your-site.example/auth/oauth/github/callback
https://your-site.example/auth/oauth/google/callback
```

Store the client ID and secret as [environment variables](https://hostedge.app/docs/environment-variables) and pass them in:

```js
createAuth(env.DB, {
  env,
  oauth: {
    github: { clientId: env.GITHUB_ID, clientSecret: env.GITHUB_SECRET },
    google: { clientId: env.GOOGLE_ID, clientSecret: env.GOOGLE_SECRET },
  },
})
```

Link people to `/auth/oauth/github?next=/dashboard`. Only a provider's verified email is accepted, and signing in with a provider uses the same account as a magic link or password for that email.

## Security notes

- Passwords are hashed with PBKDF2 (SHA-256, 100,000 iterations, the most a Worker allows). Session tokens are stored hashed, in an HttpOnly, Secure, SameSite=Lax cookie.
- After 10 wrong passwords for an email, or 50 from one address, sign-in is locked for 15 minutes. Requests for links, resets and sign-ups are limited per email and per address.
- Password reset gives the same answer and timing whether or not the account exists, so the form can't be used to find out who has an account.
- If someone registers an email with a password and its real owner later proves the address (magic link, Google or GitHub), the earlier password is discarded.

## Errors

Failures return JSON like `{ "error": "invalid_credentials" }` with a matching status: `invalid_email` and `weak_password` (400), `email_taken` (409), `invalid_credentials` (401), `too_many_attempts` (429), `disabled` and `email_not_verified` (403), `email_unavailable` (503), `invalid_token` and `oauth_failed` (400).

## Limits

HostEdge Auth keeps its tables in your project database, so users count toward your storage and request allowances like any other data. It suits apps up to a few thousand concurrent users; see [Databases and storage](https://hostedge.app/docs/storage) for connecting your own Postgres when you outgrow that.
