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"}
- You need to make sure that ports
4433
,4434
,4436
,4455
, and4456
are free. - Make sure to always use
127.0.0.1
as the hostname; never uselocalhost
! 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:
- Public ("Browser") API (port 4433)
- Admin API (port 4434) - This is made public only so we can test via the CLI.
- 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.
- 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.
- 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.
Cookie Session Authenticator
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:
# ...
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:
# ...
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.
# ...
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:
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
:
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:
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:
- 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:
# ...
- 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:
# ...
- 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