Skip to main content

Error handlers

A error handler is responsible for executing logic after, for example, authentication or authorization failed. Ory Oathkeeper supports different error handlers and we will add more as the project progresses.

A error handler can be configured to match on certain conditions, for example, it's possible to configure the json error handler to only be executed if the HTTP Header Accept contains application/json.

Each error handler has two keys:

  • handler (string, required): Defines the handler (for example noop) to be used.
  • config (object, optional): Configures the handler. Configuration keys vary per handler. The configuration can be defined in the global configuration file, or per access rule.

Example

{
"errors": [
{
"handler": "json",
"config": {}
}
]
}

You can define more than one error handler in the Access Rule. Depending on their matching conditions (see next chapter), the appropriate error handler will be chosen.

Please be aware that defining error handlers with overlapping matching conditions will cause errors, because Ory Oathkeeper won't know which error handler to execute!

Error matching

You can configure the error handlers in such a way, that - for example - Ory Oathkeeper responds, in the case of an error, with

  • a JSON response, such as {"error":{"code":403,"status":"Forbidden","message":"Access credentials aren't sufficient to access this resource"}}, when the client that expects JSON (Accept: application/json);
  • an XML response when the API Client expects XML (Accept: application/xml);
  • a HTTP Redirect (HTTP Status Found - 302) to /login when the endpoint is directly (no AJAX) accessed from a browser (Accept: text/html,application/xhtml+xml).

There are also other possible matching strategies - such as defining a response per error type (unauthorized, forbidden, internal_server_error, ...) , per HTTP Content-Type Header (similar to Accept), or based on the Remote IP Address.

All match definitions are set in the handler's config, using the when key. This is the same for all handlers!

{
handler: "json", // or redirect, www_authenticate, ...
config: {
when: [
{
error: ["unauthorized", "...", "..."],
},
],
},
}

If when is empty, then no conditions are applied and the error handler is always matching! In fact, this is also true for all subkeys. If left empty, the matching condition won't be applied and is thus always true!

Fallback

Error handling can be set globally and per access rule. Ory Oathkeeper will first check for any access rule specific error handling before falling back to the globally defined error handling.

Similar to other pipeline handlers (authentication, authorization, mutation), you must enable the error handlers in the global Ory Oathkeeper config, except for the json error handler which is always enabled by default:

# .oathkeeper.yaml
errors:
handlers:
json:
enabled: true # this is true by default
# config:
# when: ...
redirect:
enabled: true # this is false by default
# config:
# when: ...

As discussed in the previous section, when config.when is empty, the error handler will always match. This of course is a problem because Ory Oathkeeper now doesn't know if it should redirect or send a JSON error!

Therefore, an additional configuration - called fallback - is available:

# .oathkeeper.yaml
errors:
# `["json"]` is the default!
fallback:
- json

handlers:
json:
enabled: true # this is true by default
# config:
# when: ...
redirect:
enabled: true # this is false by default
config:
to: http://mywebsite/login
# when: ...

This feature tells Ory Oathkeeper that the json error handler should be used as fallback. You could also define multiple fallback handlers - the first matching handler will be the one and only executed! This makes sense if you configure the when section as well:

# .oathkeeper.yaml
errors:
fallback:
- redirect
- json

handlers:
json:
enabled: true
redirect:
enabled: true
config:
when:
- request:
header:
accept:
- text/*

In this configuration example, Ory Oathkeeper would first check if the HTTP Request Header contains Accept: text/html (or text/xhtml, text/text, ...) and if not, would return a JSON Error Message.

Matchers

All matchers are defined under the config.when key of the error handler, both in the global config and in the access rule:

// access-rule.json
{
handler: "json",
config: {
when: [
{
error: ["unauthorized", "...", "..."],
},
],
},
}
# .oathkeeper.yaml
errors:
handlers:
redirect:
enabled: true
config:
when:
- error:
- unauthorized
- ...
- ...

You can define multiple when clauses which allows you to differentiate between error types and HTTP Requests. The when sections are combined with OR while the subkeys (error, request.header.accept, request.header.content_type, ...) are matched with AND. Keys that have arrays as values (error, request.header.accept, request.header.content_type, ...) are usually matched with OR:

# .oathkeeper.yaml
errors:
handlers:
redirect:
enabled: true
config:
when:
- error:
- unauthorized
# OR
- internal_server_error

# AND
request:
remote_ip:
match:
- 192.168.1.0/24
# OR
- 192.178.1.0/24

# OR
- error:
- forbidden
# OR
- not_found

# AND
request:
header:
accept:
- text/html
# OR
- text/xhtml

# AND
content_type:
- application/x-www-form-urlencoded
# OR
- multipart/form-data

Error

The config.when.#.error key may contain zero, one, or multiple error names that must match for this matching condition to be true. The error names are derived (lowercase and whitespaces replaced with _) from the well-defined HTTP Status messages such as Not Found, Forbidden, Internal Server Error, and so on.

Here are some examples:

  • Internal Server Error (500) -> {"errors": ["internal_server_error"]}
  • Forbidden (403) -> {"errors": ["forbidden"]}
  • Not Found (404) -> {"errors": ["not_found"]}
  • Bad Request (400) -> {"errors": ["bad_request"]}

Keep in mind that these errors must be emitted by Ory Oathkeeper itself, not by the upstream API. Therefore, most HTTP Status Codes won't have any effect because Ory Oathkeeper - as of now - mostly returns 401, 403, 500 error codes.

As discussed previously, if this configuration key is left empty, then all error types will match!

HTTP Request: Remote IP

The HTTP Remote IP is the IP of the Client that initially made the request. The Remote Address is matched using CIDR Notation:

config:
when:
- request:
remote_ip:
match:
- 192.168.1.0/24

This configuration would match a HTTP Request coming directly from 192.168.1.1, 192.168.1.2, and so on.

If Ory Oathkeeper runs behind a Load Balancer or any other type of Reverse Proxy, you can configure Ory Oathkeeper to check the X-Forwarded-For HTTP Header header as well:

config:
when:
- request:
remote_ip:
respect_forwarded_for_header: true # defaults to false
match:
- 192.168.1.0/24

As discussed previously, if this configuration key is left empty, then all remote IPs will match!

HTTP Requests that include one of the matching IP Addresses in the X-Forwarded-For HTTP Header, for example X-Forwarded-For: 123.123.123.123, ..., 192.168.1.1, ..., now match this error handler.

HTTP Request Header: Accept

The HTTP Accept Header is the most common way to tell an HTTP API what MIME content type is expected. For example, FireFox sends Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 for all regular requests for example when opening www.ory.sh. And a REST API Client usually sends Accept: application/json.

Therefore, using the Accept header is one of the most common ways to distinguish between "regular" browser traffic, REST API traffic, and other types of HTTP traffic.

In Ory Oathkeeper, you can specify the matching conditions for the Accept header as follows:

config:
when:
- request:
header:
accept:
- text/html
- text/*

The defined matching condition would apply if a client sends one of the following Accept headers:

  • Accept: text/html
  • Accept: text/xhtml
  • Accept: text/xhtml+xml
  • Accept: text/...
  • Accept: text/*

Most browsers (see the FireFox example) also send wildcard Accept headers such as */*. To prevent multiple conditions to match, HTTP Accept Headers from the client are interpreted literally, meaning that wildcards aren't interpreted.

Assuming the client sends Accept: */* and the error condition is set to accept: ["text/text"], the error condition would not match. If however the client sends Accept: text/text and the error condition is set to accept: ["*/*"], then the condition would match.

To match against wildcards in the Accept header, you have to explicitly define them in the error condition. Setting the configuration to accept: ["*/*"] will match Accept: */* and of course any other type such as Accept: text/* Accept: text/html, and so on.

As discussed previously, if this configuration key is left empty, then all Accept headers will match!

HTTP Request Header: Content-Type

The HTTP Content Type matcher works similar to the Accept header. The HTTP Content Type Header however is much less common, as it's only used in POST, PUT, PATCH requests (or any other requests that send a HTTP Body).

The main difference however is that the client never (unless it sends malformed data) sends wildcard MIME types, as the MIME type needs to be deterministic. It's typically something like multipart/form-data, application/x-www-form-urlencoded, or application/json.

In Ory Oathkeeper, you can specify the matching conditions for the Content-Type header as follows:

config:
when:
- request:
header:
content_type:
- multipart/form-data
# OR
- application/x-www-form-urlencoded
# OR
- application/json

As discussed previously, if this configuration key is left empty, then all Content-Type headers will match!

Error Handlers

json

The json Error Handler returns an application/json response type. Per default, error messages are stripped of their details to reduce OSINT attack surface. You can enable more detailed error messages by setting verbose to true. As discussed in the previous section, you can define error matching conditions under the when key.

json Example

// access-rule.json
{
handler: "json",
config: {
verbose: true, // defaults to false
when: [
// ...
],
},
}

redirect

The redirect Error Handler returns a HTTP 302/301 response with a Location Header. As discussed in the previous section, you can define error matching conditions under the when key.

If you want to append the current url (where the error happened) to address redirected to, You can specify return_to_query_param to set the name of parameter that will hold the url. The information about the current url is taken either from the URL, or from the X-Forwarded-Method, X-Forwarded-Proto, X-Forwarded-Host, X-Forwarded-Uri headers (if present) of the incoming request.

redirect Example

// access-rule.json
{
handler: "json",
config: {
to: "http://my-website/login", // required!!
return_to_query_param: "return_to",
code: 301, // defaults to 302 - only 301 and 302 are supported.
when: [
// ...
],
},
}

When the user accesses a protected url http://my-website/settings, they will be redirected to http://my-website/login?return_to=http%3A%2F%2Fmy-website%2Fsettings. The login page can use the return_to parameter to return user to intended page after a successful login.

www_authenticate

The www_authenticate Error Handler responds with HTTP 401 and a WWW-Authenticate HTTP Header.

You can configure the realm the browser will display. The realm is a message that will be displayed by the browser. Most browsers show a message like "The website says: <realm>". Using a real message is thus more appropriate than a Realm identifier.

This error handler is "exotic" as WWW-Authenticate isn't a common pattern in today's web. As discussed in the previous section, you can define error matching conditions under the when key.

www_authenticate example

// access-rule.json
{
handler: "json",
config: {
realm: "Please enter your username and password", // Defaults to `Please authenticate.`
when: [
// ...
],
},
}