Skip to main content

Zero trust with IAP proxy

The Quickstart covers a basic set up that uses client-side routing in SecureApp to forward requests to Ory Kratos.

Systems that have more than one component 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 to achieve this.

This guide expects that you have familiarized yourself with Ory Kratos' concepts introduced in the Quickstart.

To ensure that no one can access the dashboard without prior authentication, we are making use of our reverse proxy (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 isn't yet authenticated.

Running Ory Kratos and the Ory Oathkeeper identity and access proxy

Clone the Ory Kratos repository and fetch the latest images:

git clone https://github.com/ory/kratos.git
cd kratos
git checkout v0.8.0-alpha.3

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

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 hasn't 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 hasn't 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"}
There are two important factors to get a fully functional system:
  • You need to make sure that ports 4433, 4434, 4436, 4455, and 4456 are free.
  • 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
  • Public ("Browser") API (port 4433)
  • Admin API (port 4434) - This is made public only so we can test via the CLI.
  1. SecureApp
  • An example application written in Node.js that implements the login, registration, logout, dashboard, and other UIs. Because we're accessing this via a proxy, its port (4435) isn't publicly exposed.
  1. 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.
  1. 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:

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:

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/welcome.

Check the Quickstart for the other flows!

Configuration

You can find all configuration files used for this quickstart guide in ./contrib/quickstart/kratos , ./quickstart.yml, and ./quickstart-oathkeeper.yml.

Ory Oathkeeper: Identity and Access Proxy

All configuration for Ory Oathkeeper resides in ./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.

The Cookie Session Authenticator is enabled and points to Ory Kratos' /sessions/whoami API. It uses the ory_kratos_session cookie to identify if a request contains a session or not:

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.

Anonymous Authenticator

The Anonymous Authenticator is useful for endpoints that don't need login, such as the registration screen:

contrib/quickstart/oathkeeper/oathkeeper.yml
# ...
authenticators:
anonymous:
enabled: true
config:
subject: guest
# ...

Allowed Authorizer

The Allowed Authenticator 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.

contrib/quickstart/oathkeeper/oathkeeper.yml
# ...
authorizers:
allowed:
enabled: true
# ...

ID Token Mutator

The ID Token Mutator 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 for the various other endpoints such as login and registration:

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 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:

contrib/quickstart/oathkeeper/oathkeeper.yml
errors:
fallback:
- json

handlers:
redirect:
enabled: true
config:
to: http://127.0.0.1:4455/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 to match the HTTP requests for our access rules:

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:

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:

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/<{registration,welcome,recovery,verification,login,**.css,**.js,**.png}>"
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:

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/login

Cleaning up Docker

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

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