# Protect a web app

Put your project's sign-in in front of a service people open in a browser, choose who may open it, and read who is signed in from request headers.

Zero-Code gives a service that has no sign-in of its own, such as a monitoring tool or an internal tool, your project's sign-in. A person who opens the service is sent to your project's sign-in page and brought back with a session, with no change to the service's code.

## Before you begin

You need:

- A cluster with Zero-Code installed. See [Install Zero-Code on a cluster](/zero-code/install).
- A hostname for the service, and the ability to change its DNS. This guide uses `grafana.acme.com`.
- The groups that should be able to open the service. See [Roles, groups and grants](/concepts/access).

<Steps>

### Open the workload

In the AuthFI console, open **Zero-Code > Kubernetes** and select the cluster. On the **Workloads** tab, find the workload and choose **Classify** on its row.

### Classify it as an App that serves pages

1. The step asks what the workload is. Choose **App**.
2. Under **What does it serve?**, turn on **Pages**.
3. Choose **Continue to Protect**.

The console confirms that the workload is classified and opens the Protect step, which asks who may reach the workload.

### Require sign-in on a hostname

Turn on **Pages · require sign-in** and fill in the two fields.

| Field                       | Enter                                                            |
| --------------------------- | ---------------------------------------------------------------- |
| **Hostname**                | The hostname people use, for example `grafana.acme.com`          |
| **Signed-in session lasts** | **1 hour**, **8 hours** or **24 hours**. The default is 8 hours. |

With **Pages** alone, every page needs a signed-in person. Rules for individual routes are part of [protecting an API](/zero-code/protect-api#route-rule-reference).

### Choose where the service may connect

Under **What it may reach**, the console lists each destination it saw the service connect to. Tick the destinations the service needs.

To add a destination the service will need, enter its address or network and port, for example `10.0.0.0/8:5432`, and choose **Allow destination**.

### Save the draft

Choose **Save draft**. The console confirms with **Draft saved**: the rules are recorded, not enforced, until you turn protection on.

Saving registers an [application](/applications/types) for this hostname, so you can choose who may open it.

### Point the hostname at your cluster

Point the hostname's DNS at your cluster's ingress, as you would for any service.

### Assign the application to groups

On the workload's page, under **How it is reached**, the **Pages** card has the link **Choose who may use it**. On the application's page, choose **Assign a group**.

In the dialog that opens, choose the group in **Group** and choose **Assign**.

Until the application is assigned to a group, anyone who can sign in to the project can open it. The first assignment closes it to everyone else, and the dialog says so with **This closes the app**. A person who signs in and is not assigned sees a `403` page that says so.

### Turn on protection

While the draft is saved and protection is off, the [audit record](/security/audit) shows who would have been denied and who would have been sent to sign in.

When the record reads right, open the Protect step again and choose **Turn on protection…**, then confirm with **Turn on protection**. The button is available after 24 hours of watching.

The console confirms that protection is turned on. It applies on the nodes running the workload within 30 seconds. From then on, a request with no session does not reach your service, and the **Pages** card shows **sign-in required**.

</Steps>

## Check that it works

In a browser with no session, open a page on the service:

```text
https://grafana.acme.com/dashboards
```

The browser is sent to your project's sign-in page. Sign in as a person assigned to the application. The browser returns to `/dashboards`, the page you asked for.

## What a person experiences

A person opening the service goes through this sequence.

1. They open `https://grafana.acme.com/dashboards`.
2. With no session, the browser is sent to your project's sign-in page.
3. They sign in, with multi-factor authentication (MFA) if your project requires it.
4. The browser returns to the service, at the page they asked for.
5. They have a session for this service, for the time you chose in **Signed-in session lasts**.

A person already signed in to your project passes straight through steps 2 to 4.

## How the session works

A session is for one service. It is not accepted at another service, so each service is opened only by the people assigned to it.

| Setting        | Value                                                             |
| -------------- | ----------------------------------------------------------------- |
| Session length | 1 hour, 8 hours or 24 hours. 8 hours by default.                  |
| Session cookie | `authfi_session`, for this hostname only, `Secure` and `HttpOnly` |
| Sign-out       | Send the browser to `/__authfi/logout` on the service's hostname  |

To end a person's access at once, open **Security > Sessions**, select their session and choose **End session**. Suspending the person has the same effect.

## Read who is signed in

Every request that reaches your service carries who is making it. Your service reads these headers and needs no token library.

| Header                 | Value                                  |
| ---------------------- | -------------------------------------- |
| `X-AuthFI-User-ID`     | The person                             |
| `X-AuthFI-User-Email`  | Their email                            |
| `X-AuthFI-Groups`      | Their groups, separated by commas      |
| `X-AuthFI-Roles`       | Their roles, separated by commas       |
| `X-AuthFI-Permissions` | Their permissions, separated by commas |
| `X-AuthFI-Session-ID`  | The session, for your logs             |
| `X-AuthFI-Verified-By` | `AuthFI eBPF agent`                    |

```python
@app.get("/reports")
def reports(request):
    email = request.headers["X-AuthFI-User-Email"]
    groups = request.headers.get("X-AuthFI-Groups", "").split(",")
    if "finance" not in groups:
        return Response(status=403)
    return render_reports(for_user=email)
```

Any header beginning `X-AuthFI-` that a caller sends is removed before the request reaches your service. What you read was set by the node, never by the caller.

When an AI agent calls instead of a person, the request also carries `X-AuthFI-Agent-ID` and `X-AuthFI-Agent-Capabilities`. It carries `X-AuthFI-On-Behalf-Of` when the AI agent acts for someone.

## Troubleshooting

| What you see                                                                | Cause                                                        | Fix                                                            |
| --------------------------------------------------------------------------- | ------------------------------------------------------------ | -------------------------------------------------------------- |
| A `403` page saying the person is not assigned                              | The application is assigned, and this person is not included | Assign the application to one of the person's groups           |
| **Turn on protection…** is unavailable, with **Watched for under 24 hours** | The workload has been watched for less than 24 hours         | Save the draft. It records what it would deny in the meantime. |
| **Turn on protection…** asks you to save your draft first                   | The draft has unsaved changes                                | Choose **Save draft**, then **Turn on protection…**            |

To remove protection from a service, see [Hand a service back](/zero-code/protect-api#hand-a-service-back).

## Next steps

<Cards>
  <Card href="/zero-code/protect-api" title="Protect an API">
    Require an access token, and write a rule for each route.
  </Card>
  <Card href="/zero-code/enforcement" title="How enforcement works">
    Where each decision is made, and what monitor mode records.
  </Card>
  <Card href="/concepts/access" title="Roles, groups and grants">
    How assigning an application to a group decides who can sign in.
  </Card>
</Cards>
