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 examplenoop
) 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: [
// ...
]
}
}