---
id: zero-trust-iap-proxy-identity-access-proxy
title: Zero Trust with IAP Proxy
---

import useBaseUrl from '@docusaurus/useBaseUrl'
import Mermaid from '@theme/Mermaid'

The [Quickstart](../quickstart.mdx) covers a basic set up that uses client-side
routing in `SecureApp` to forward requests to Ory Kratos.

Systems that have more than a few components often use reverse proxies such as
Nginx, Envoy, or Kong to route and authorize traffic to applications. Ory Kratos
works very well in such environments. The purpose of this guide is to clarify
how to use an IAP (Identity and Access Proxy) to authorize incoming requests. In
this tutorial we will be using
[Ory Oathkeeper](https://github.com/ory/oathkeeper) to achieve this.

This guide expects that you have familiarized yourself with Ory Kratos' concepts
introduced in the [Quickstart](../quickstart.mdx).

To ensure that no one can access the dashboard without prior authentication, we
are making use of our reverse proxy
([Ory Oathkeeper](https://github.com/ory/oathkeeper)) denying all
unauthenticated traffic to `http://secure-app/dashboard` and redirecting the
user to the login page at `http://secure-app/auth/login`. Further, we will
configure access to `http://secure-app/auth/login` in such a way that access
only works if one is not yet authenticated.

## Running Ory Kratos and the Ory Oathkeeper Identity and Access Proxy

Clone the Ory Kratos repository and fetch the latest images:

```shell script
git clone https://github.com/ory/kratos.git
cd kratos
git checkout v0.7.2-alpha.1

```

Next, run the quickstart and add the Ory Oathkeeper config:

```shell script
docker-compose \
  -f quickstart.yml \
  -f quickstart-oathkeeper.yml \
  up --build --force-recreate
```

This might take a minute or two. Once the output slows down and logs indicate a
healthy system you're ready to roll! A healthy system will show something along
the lines of (the order of messages might be reversed):

```
kratos_1      | time="2020-01-20T14:52:13Z" level=info msg="Starting the admin httpd on: 0.0.0.0:4434"
kratos_1      | time="2020-01-20T14:52:13Z" level=info msg="Starting the public httpd on: 0.0.0.0:4433"
oathkeeper_1  | {"level":"info","msg":"TLS has not been configured for api, skipping","time":"2020-01-20T09:22:09Z"}
oathkeeper_1  | {"level":"info","msg":"Listening on http://:4456","time":"2020-01-20T09:22:09Z"}
oathkeeper_1  | {"level":"info","msg":"TLS has not been configured for proxy, skipping","time":"2020-01-20T09:22:09Z"}
oathkeeper_1  | {"level":"info","msg":"Listening on http://:4455","time":"2020-01-20T09:22:09Z"}
```

:::note There are two important factors to get a fully functional system:

- You need to make sure that ports `4433`, `4434`, `4436`, `4455`, and
  `4456`&nbsp;
  [are free](https://serverfault.com/questions/309052/check-if-port-is-open-or-closed-on-a-linux-server).
- Make sure to always use `127.0.0.1` as the hostname; never use `localhost`!
  This is important because browsers treat these two as separate domains and
  will therefore have issues with setting and using the right cookies.

:::

### Network Architecture

This demo makes use of several services:

1. [Ory Kratos](https://github.com/ory/kratos)

- Public ("Browser") API (port 4433)
- Admin API (port 4434) - This is made public only so we can test via the CLI.

2. [SecureApp](http://github.com/ory/kratos-selfservice-ui-node)

- An example application written in NodeJS that implements the login,
  registration, logout, dashboard, and other UIs. Because we are accessing this
  via a proxy, its port (4435) is not publicly exposed.

3. [Ory Oathkeeper](https://github.com/ory/oathkeeper)

- Reverse proxy (port 4455) - a reverse proxy to protect the **SecureApp**.
- API (port 4456) - Oathkeeper's API. This is made public only so we can test
  via the CLI.

4. [MailSlurper](https://github.com/mailslurper)

- Public (port 4436) - a development SMTP server with which Ory Kratos sends
  emails.

To better understand the application architecture, let's take a look at the
network configuration. This assumes that you have at least some understanding of
how Docker networks work:

<Mermaid
  chart={`
graph TD
subgraph hn[Host Network]
    B[Browser]
    B-->|Can access URLs via 127.0.0.1:4455|OKPHN
    B-->|Can access UI via 127.0.0.1:4436|SMTPUI
    OKPHN([Reverse Proxy exposed at :4455])
    SMTPUI([MailSlurper UI exposed at :4436])
end
subgraph dn["Internal Docker Network (intranet)"]
    OKPHN-->OO
    SMTPUI-->SMTP
    OO-->|Proxies URLss /.ory/kratos/public/* to|OK
    OO-->|"Proxies /auth/login, /auth/registration, /dashboard, ... to"|SA
    SA-->|Talks to|OK
    OK-->|Sends mail via|SMTP
    OO-->|Validates auth sessions using|OK
    OK[Ory Kratos]
    OO["Reverse Proxy (Ory Oathkeeper)"]
    SA["SecureApp (Ory Kratos SelfService UI Node Example)"]
    SMTP["SMTP Server (MailSlurper)"]
end
`}
/>

As you can see, all requests except for our demo mail server are proxied through
Ory Oathkeeper.

The next diagram shows how we've configured the routes in Ory Oathkeeper:

<Mermaid
  chart={`
graph TD
subgraph pi[Public Internet]
    B[Browser]
end
subgraph vpc[VPC / Cloud / Docker Network]
subgraph "Demilitarized Zone / DMZ"
    OK[Ory Oathkeeper :4455]
    B --> OK
end
    OK -->|"Forwards {/,/dashboard} to"| SAD
    OK -->|"Forwards /auth/logout to"| SALU
    OK -->|"Forwards /auth/login to"| SALI
    OK -->|"Forwards /auth/registration to"| SAR
    OK -->|"Forwards /auth/* to"| SAA
    OK -->|"Forwards /.ory/kratos/public/* to"| KP
    subgraph "Private Subnet / Intranet"
    K[ Ory Kratos ]
    KP([ Ory Kratos Public API ])
    KA([ Ory Kratos Admin API ])
    SA --> KA
    KA -.belongs to.-> K
    KP -.belongs to.-> K
    subgraph sa["SecureApp / kratos-selfservice-ui-node Example"]
        SA[SecureApp]
        SAD -.belongs to.-> SA
        SALU -.belongs to.-> SA
        SALI -.belongs to.-> SA
        SAR -.belongs to.-> SA
        SAA -.belongs to.-> SA
        subgraph "Has active login session"
            SAD([Route /dashboard])
            SALU([Route /auth/logout])
        end
        subgraph "No active login session"
            SALI([Route /auth/login])
            SAR([Route /auth/registration])
            SAA([Route /auth/...])
        end
    end
    end
end
`}
/>

In order to avoid common cross-domain issues with cookies, we're using Ory
Oathkeeper to proxy requests to Ory Kratos' Public API so that all requests come
from the same hostname.

## Perform Registration, Login, and Logout

Enough theory! Let's start by opening the dashboard: go to
[127.0.0.1:4455/dashboard](http://127.0.0.1:4455/dashboard).

Check the [Quickstart](../quickstart.mdx) for the other flows!

## Configuration

You can find all configuration files used for this quickstart guide in
[`./contrib/quickstart/kratos`](https://github.com/ory/kratos/tree/v0.3.0-alpha.1/contrib/quickstart/kratos/email-password)
,
[`./quickstart.yml`](https://github.com/ory/kratos/blob/v0.3.0-alpha.1/quickstart.yml),
and
[`./quickstart-oathkeeper.yml`](https://github.com/ory/kratos/blob/v0.3.0-alpha.1/quickstart-oathkeeper.yml).

### Ory Oathkeeper: Identity and Access Proxy

All configuration for [Ory Oathkeeper](https://www.ory.sh/oathkeeper/) resides
in
[`./contrib/quickstart/oathkeeper`](https://github.com/ory/kratos/blob/v0.3.0-alpha.1/contrib/quickstart/oathkeeper).

#### Configuration

We define several configuration options for Ory Oathkeeper such as the port for
the proxy and where to load the access rules from.

#### Cookie Session Authenticator

The
[Cookie Session Authenticator](https://www.ory.sh/docs/oathkeeper/pipeline/authn#cookie_session)
is enabled and points to [Ory Kratos' `/sessions/whoami` API](../reference/api).
It uses the `ory_kratos_session` cookie to identify if a request contains a
session or not:

```yaml title="contrib/quickstart/oathkeeper/.oathkeeper.yml"
# ...
authenticators:
  cookie_session:
    enabled: true
    config:
      check_session_url: http://kratos:4433/sessions/whoami
      preserve_path: true
      extra_from: '@this'
      subject_from: 'identity.id'
      only:
        - ory_kratos_session
# ...
```

It's doing what the `needsLogin` function did in the
[Quickstart](../quickstart.mdx).

#### Anonymous Authenticator

The
[Anonymous Authenticator](https://www.ory.sh/docs/oathkeeper/pipeline/authn#anonymous)
is useful for endpoints that do not need login, such as the registration screen:

```yaml title="contrib/quickstart/oathkeeper/.oathkeeper.yml"
# ...
authenticators:
  anonymous:
    enabled: true
    config:
      subject: guest
# ...
```

#### Allowed Authorizer

The
[Allowed Authenticator](https://www.ory.sh/docs/oathkeeper/pipeline/authz#allowed)
simply allows all users to access the URL. Since we don't have Role-based access
control (RBAC) or an Access Control list (ACL) in place for this example, this
will be enough.

```yaml title="contrib/quickstart/oathkeeper/.oathkeeper.yml"
# ...
authorizers:
  allowed:
    enabled: true
# ...
```

### ID Token Mutator

The
[ID Token Mutator](https://www.ory.sh/docs/oathkeeper/pipeline/mutator#id_token)
takes all the available session information and puts it into a JSON Web Token
(JWT). The protected `SecureApp` will now receive

`Authorization: bearer <jwt...>`

in the HTTP Header instead of

`Cookie: ory_kratos_session=...`

The JWT is signed using a RS256 key. To verify the JWT we can use the public key
provided by Ory Oathkeeper's JWKS API:

`http://127.0.0.1:4456/.well-known/jwks.json`

You can generate the RS256 key yourself by running
`oathkeeper credentials generate --alg RS256 > id_token.jwks.json`.

We also enabled the
[NoOp Mutator](https://www.ory.sh/docs/oathkeeper/pipeline/mutator#) for the
various other endpoints such as login and registration:

```yaml title="contrib/quickstart/oathkeeper/.oathkeeper.yml"
mutators:
  noop:
    enabled: true

  id_token:
    enabled: true
    config:
      issuer_url: http://127.0.0.1:4455/
      jwks_url: file:///etc/config/oathkeeper/id_token.jwks.json
      claims: |
        {
          "session": {{ .Extra | toJson }}
        }
```

You could obviously also use other mutators such as the
[Header Mutator](https://www.ory.sh/docs/oathkeeper/pipeline/mutator#header) and
use headers such as `X-User-ID` instead of the JWT.

### Error Handling

We configure the error handling in such a way that a missing or invalid login
session (when accessed from a browser) redirects to `/auth/login`:

```yaml title="contrib/quickstart/oathkeeper/.oathkeeper.yml"
errors:
  fallback:
    - json

  handlers:
    redirect:
      enabled: true
      config:
        to: http://127.0.0.1:4455/auth/login
        when:
          - error:
              - unauthorized
              - forbidden
            request:
              header:
                accept:
                  # We don't want this for application/json requests, only browser requests!
                  - text/html
    json:
      enabled: true
      config:
        verbose: true
```

### Access Rules

We use [glob matching](https://github.com/gobwas/glob) to match the HTTP
requests for our access rules:

```yaml title="contrib/quickstart/oathkeeper/.oathkeeper.yml"
access_rules:
  matching_strategy: glob
  repositories:
    - file:///etc/config/oathkeeper/access-rules.yml
```

In `access-rules.yml` we define three rules. The first rule forwards all traffic
matching `http://127.0.0.1:4455/.ory/kratos/public/` to Ory Kratos' Public API:

```yaml title="contrib/quickstart/oathkeeper/access-rules.yml"
- id: 'ory:kratos:public'
  upstream:
    preserve_host: true
    url: 'http://kratos:4433'
    strip_path: /.ory/kratos/public
  match:
    url: 'http://127.0.0.1:4455/.ory/kratos/public/<**>'
    methods:
      - GET
      - POST
      - PUT
      - DELETE
      - PATCH
  authenticators:
    - handler: noop
  authorizer:
    handler: allow
  mutators:
    - handler: noop
```

The second rule allows anonymous requests to the error page, website assets,
login, registration, and the page for resending the verification email:

```yaml title="contrib/quickstart/oathkeeper/access-rules.yml"
# ...
- id: 'ory:kratos-selfservice-ui-node:anonymous'
  upstream:
    preserve_host: true
    url: 'http://kratos-selfservice-ui-node:4435'
  match:
    url: 'http://127.0.0.1:4455/<{error,verify,auth/*,**.css,**.js}{/,}>'
    methods:
      - GET
  authenticators:
    - handler: anonymous
  authorizer:
    handler: allow
  mutators:
    - handler: noop
```

The final rule requires a valid session before allowing requests to the
dashboard and user settings:

```yaml title="contrib/quickstart/oathkeeper/access-rules.yml"
# ...
- id: 'ory:kratos-selfservice-ui-node:protected'
  upstream:
    preserve_host: true
    url: 'http://kratos-selfservice-ui-node:4435'
  match:
    url: 'http://127.0.0.1:4455/<{,debug,dashboard,settings}{/,}>'
    methods:
      - GET
  authenticators:
    - handler: cookie_session
  authorizer:
    handler: allow
  mutators:
    - handler: id_token
  errors:
    - handler: redirect
      config:
        to: http://127.0.0.1:4455/auth/login
```

## Cleaning up Docker

To clean everything up, you need to bring down the Docker Compose environment
and remove all mounted volumes.

```shell script
docker-compose -f quickstart.yml -f quickstart-oathkeeper.yml down -v
docker-compose -f quickstart.yml -f quickstart-oathkeeper.yml rm -f -s -v
```
