> ## Documentation Index
> Fetch the complete documentation index at: https://docs.formal.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Web

> Put browser-based applications behind the Formal Connector with Formal sign-in

## Overview

A Web Resource puts a browser-based application, such as an internal dashboard or admin portal, behind the [Connector](/docs/guides/core-concepts/connectors/introduction). Users sign in to Formal in their browser. The Connector then proxies their requests with their Formal identity, logs each request, and evaluates HTTP policies.

## How Sign-In Works

1. A user opens the Resource's address on the Connector.
2. If the browser has no Formal session, the Connector redirects it to `https://app.formal.ai/connector-auth`.
3. The user signs in to Formal. Formal redirects the browser back to the Connector with a one-time code.
4. The Connector exchanges the code and sets two session cookies: `formalconnector` and `formalconnector_username`. It then redirects to the page the user first asked for.
5. Each later request carries the cookies. The Connector removes them before forwarding the request, and it drops any attempt by the application to set them.

When a session expires, the Connector sends the browser back to sign in. One-time codes expire after 5 minutes and work once.

## Requirements

* **Connector hostname:** Give the Connector a hostname. See [Hostname and TLS Configuration](/docs/guides/core-concepts/connectors/tls). Without one, the Connector answers `hostname not configured for resource`.
* **Address:** Users reach the Resource at the Connector hostname, or at `<resource-name>.<connector-hostname>`. Other host names are rejected. To serve several Web Resources, use wildcard DNS and a wildcard TLS certificate, as for [HTTP Resources](/docs/guides/core-concepts/resources/http#multiple-resources).
* **Listener:** Link the Resource to a Connector [listener](/docs/guides/core-concepts/connectors/listeners), usually on port 443.
* **People:** Users sign in with their own human Formal account.

If your [Permissions](/docs/guides/core-concepts/permissions) deny by default, allow the browser sign-in step:

```rego theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
package formal.app

import future.keywords.if

allow if {
  input.app.command.name == "connector-callback"
}
```

## Create a Web Resource

<Tabs>
  <Tab title="Control Plane">
    Go to [Resources](https://app.formal.ai/resources) and click **Create Resource**. Set **Technology** to **Web**. Set **Hostname** to the application's upstream hostname, such as `grafana.internal.example.com`. The **Port** defaults to `443`.
  </Tab>

  <Tab title="Terraform">
    ```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
    resource "formal_resource" "grafana" {
      name       = "grafana"
      technology = "web"
      hostname   = "grafana.internal.example.com"
      port       = 443
    }
    ```
  </Tab>
</Tabs>

**Verify:** Open `https://grafana.<connector-hostname>` in a browser. Sign in to Formal when prompted. The application loads, and the requests appear in [Logs](https://app.formal.ai/logs).

### Use the Application's Usual Address

Users who run the [Formal Endpoint](/docs/guides/client-apps/desktop-app) in Transparent Mode can keep the application's usual URL. A [network rule](/docs/guides/network-rules) with `forward_to_connector` and `resource_name` sends that traffic to the Web Resource.

## Upstream Credentials

A Native User is optional. Without one, the Connector forwards requests as the browser sent them, and the application handles its own sign-in.

To inject a credential for the application, add a [Native User](/docs/guides/core-concepts/resources/native-users) of type **HTTP Basic**, **HTTP Bearer**, or **API Key**. See [HTTP authentication](/docs/guides/core-concepts/resources/http#authentication).

## Policy Evaluation

Web Resources support the same stages and actions as [HTTP Resources](/docs/guides/core-concepts/resources/http#policy-evaluation). Policies read the request from `input.http`, and `input.resource.technology` is `web`.

This policy blocks the admin area for everyone outside the `platform-admins` group:

```rego theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
package formal.v2

import future.keywords.if
import future.keywords.in

request := {
  "action": "block",
  "type": "block_with_custom_message",
  "message": "The admin area is limited to platform administrators"
} if {
  input.resource.technology == "web"
  startswith(input.http.path, "/admin")
  not "platform-admins" in input.user.groups
}
```

## Troubleshooting

<AccordionGroup>
  <Accordion title="The browser shows hostname not configured for resource">
    **Cause:** The Connector has no hostname. **Fix:** Set the Connector's hostname, then reload.
  </Accordion>

  <Accordion title="The browser shows invalid callback authority">
    **Cause:** The address isn't the Connector hostname or `<resource-name>.<connector-hostname>`. **Fix:** Open the Resource at one of those addresses.
  </Accordion>

  <Accordion title="Sign-in returns 401 Unauthorized">
    **Cause:** The user isn't a human Formal user, a permission denies the sign-in step, or the one-time code expired. **Fix:** Check the user and your permissions, then open the application again.
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="HTTP" icon="globe" href="/docs/guides/core-concepts/resources/http">
    Learn how HTTP-based Resources work
  </Card>

  <Card title="Listeners" icon="ear-listen" href="/docs/guides/core-concepts/connectors/listeners">
    Expose Resources on Connector ports
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.