# Authentication - SSR websites

> Visitors of an SSR website sign in with the auth providers of the account - sign-in, sign-up, sign-out, Google, Azure AD and AWS Cognito tokens, who may open a page (access, groups, providers, code), default access, and the rows each visitor reaches.

Source: https://docs.apimaker.dev/v4/docs/ssr/authentication.html

The visitors of a website sign in with the **auth providers** of the account : the token generators of the Auth Providers page (a table of users, or a custom one), and the tokens of Google, Azure AD and AWS Cognito the APIs take. Nothing new to set up : a website names the providers it takes, the server checks tokens as the APIs check them.

**site.config.ts**

```ts
export default defineSite({
    auth: {
        // The auth providers the visitors sign in with.
        providers: ['customers'],
        // Where a visitor who is not signed in goes for a page which asks for one : /login?next=/account
        loginPath: '/login',
        // How long a session lasts.
        sessionDays: 30,
    },
});
```

## Sign in

**pages/login.tsx**

```tsx
import { HttpError, redirect } from 'am-ssr';
import type { PageConfig, PageContext, PageProps } from 'am-ssr';

export const config: PageConfig = { title: 'Sign in', index: false };

/** A path of the website only : `?next=//evil.example` goes home. */
function nextOf(ctx: PageContext) {
    const next = String(ctx.form.next ?? ctx.query.next ?? '/');
    return next.startsWith('/') && !next.startsWith('//') ? next : '/';
}

export function loader(ctx: PageContext) {
    if (ctx.user) redirect(nextOf(ctx));
    return { next: nextOf(ctx) };
}

export async function action(ctx: PageContext) {
    try {
        await ctx.auth.login({ provider: 'customers', username: String(ctx.form.email ?? ''), password: String(ctx.form.password ?? '') });
    } catch (error) {
        if (error instanceof HttpError && error.status < 500) {
            ctx.status(401);
            return { error: 'The email or the password is wrong.' };
        }
        throw error;
    }
    redirect(nextOf(ctx), 303);
}

export default function Login({ data, actionData }: PageProps<typeof loader, { error: string }>) {
    return (
        <form method="post">
            {actionData?.error && <p class="error">{actionData.error}</p>}
            <input type="hidden" name="next" value={data?.next ?? '/'} />
            <input type="email" name="email" autocomplete="username" required />
            <input type="password" name="password" autocomplete="current-password" required />
            <button type="submit">Sign in</button>
        </form>
    );
}
```

`ctx.auth.login({ provider, username, password, ... })` asks the provider for a token (as the token API of the account does), checks it, and keeps it in the **session** of the visitor :

- a cookie of the website, sealed with AES-256-GCM (a key of the installation and of the website) : nothing in it can be read or changed by the browser ;
- `HttpOnly`, `SameSite=Lax`, `Secure` over HTTPS, for the path of the website ;
- it ends after `sessionDays`, and a token which stands for nobody any more (expired, user removed) leaves it at the next request.

A wrong name or password is a `401` : say the same words for both, so the page does not tell which emails exist.

## Sign up

A visitor who signs up adds a row to the table of users of the provider. The website may create such a row and never change nor remove one, and the new user gets its groups from the rule, whatever the page sent :

**site.config.ts**

```ts
data: {
    write: [{ table: 'mongodb/shop/customers', allow: ['create'], set: { groups: ['customer'] } }],
},
```

```ts
await ctx.db.saveSingleOrMultiple({ instance: 'mongodb', database: 'shop', table: 'customers', saveData: { fullName, email, password } });
await ctx.auth.login({ provider: 'customers', username: email, password });
```

A column of the schema with hashing stores the password hashed ; a unique index on `email` refuses a second account of the same address.

## Sign out

A sign-out is a `POST` (a link of another site can not sign someone out) :

**pages/logout.ts**

```ts
import { redirect } from 'am-ssr';
import type { PageContext } from 'am-ssr';

export async function POST(ctx: PageContext) {
    await ctx.auth.logout();
    redirect('/', 303);
}
```

```tsx
<form method="post" action="/logout"><button type="submit">Sign out</button></form>
```

## Tokens from elsewhere

A visitor who signed in with Google (Google Identity Services), Azure AD (MSAL) or AWS Cognito in the browser hands its token to the website, which checks it as the APIs check it :

```ts
const user = await ctx.auth.setToken('google', String(ctx.form.credential));
```

The kinds are `db`, `custom`, `google`, `azure` and `aws` ; the provider of the account which takes such tokens is listed in `auth.providers`.

## The visitor

| | |
|---|---|
| `ctx.user` | The visitor who signed in, or `null` : the row of the user for a table of users (without its password), what the token says for the others, with `id`, `name`, `email`, `groups`, `provider`, `kind` and `claims` beside. |
| `ctx.auth.isLoggedIn` | Whether a visitor signed in. |
| `ctx.auth.groups`, `ctx.auth.hasGroup('admin', 'staff')` | The groups of the user : the column of groups of the provider, or what its token says. |

## Who may open a page

`access` says who may open a page, read from its code when it is saved : the server refuses a visitor **before any code of the page runs**.

```ts
export const config: PageConfig = { access: 'authenticated' };                 // a signed-in visitor
export const config: PageConfig = { access: { groups: ['admin', 'staff'] } };  // in one of these groups
export const config: PageConfig = { access: { providers: ['staff'] } };        // signed in with one of these
export const config: PageConfig = { access: 'public' };                        // everyone
export const config: PageConfig = {
    // Code which decides, run before the loaders.
    access: { authenticated: true, check: async (ctx) => (await ctx.db.count({ … })) > 0 },
};
```

- `access` of a **layout** applies to every page inside it : `pages/admin/_layout.tsx` with `{ groups: ['admin'] }` closes the whole folder.
- A page which says nothing gets `defaultAccess` of `site.config.ts` (`'public'` unless it says otherwise). `defaultAccess: 'authenticated'` closes the whole website but the pages which say `'public'` (the sign-in page).
- A visitor who is not signed in goes to `auth.loginPath?next=<the page>` for a page ; an endpoint answers `401`. A signed-in visitor who may not gets the page of the error `403`.
- Endpoints say it the same way.

## The rows of each visitor

`access` says who opens a page ; the row rules of `site.config.ts` say which rows each visitor reaches, whatever a page asks :

```ts
data: {
    read: [{ table: 'mongodb/shop/orders', where: { customer_id: '$user._id' }, requireUser: true }],
},
```

`ctx.db.query({ ..., table: 'orders', find: {} })` gives the orders of the visitor and no one else's ; `getById` of the order of another visitor finds nothing. See [data](https://docs.apimaker.dev/v4/docs/ssr/data.html#ctxdb-the-tables-a-website-lists).
