To illustrate the Hypermedia Authentication API (HAAPI), namely the Curity custom media type, we present in this section the requests and responses for a complete flow, using a username-password authenticator. To simplify the reading, only some of the request and response headers are shown.
We start the flow with an OAuth authorization request, containing the following additions to what is already defined by OAuth 2.0:
The Accept header contains the Curity media type (application/vnd.auth+json);
Accept
application/vnd.auth+json
The client needs to use the HAAPI access token, using DPoP for proof-of-possession:
GET /dev/oauth/authorize?client_id=client-one&response_type=code&... HTTP/1.1 Host: example.com Accept: application/vnd.auth+json Authorization: DPoP ey...Yw DPoP: ey..BA
The response is an API redirect to the authentication service:
HTTP/1.1 200 OK Content-Type: application/vnd.auth+json { "type": "redirection-step", "actions":[ { "template": "form", "kind":" redirect", "model": { "href": "https://example.com/dev/authn/authenticate?serviceProviderId...", "method": "GET" } } ] }
There are a few things to highlight about this response:
type
redirection-step
template
form
model
kind
redirect
Following this, the client performs the following request. Notice that this request needs to use a new DPoP proof token, namely with the htu claim set to the request’s target URI:
GET /dev/authn/authenticate?serviceProviderId... HTTP/1.1 Host: example.com Accept: application/vnd.auth+json Authorization: DPoP ey...Yw DPoP: ey...IQ
The response contains a selection representation:
HTTP/1.1 200 OK Content-Type: application/vnd.auth+json { "type": "authentication-step", "actions": [ { "template": "selector", "kind": "authenticator-selector", "title": "Select Authentication Method", "model": { "options": [ { "template": "form", "kind": "select-authenticator", "title": "A standard SQL backed authenticator", "properties": { "authenticatorType": "html-form" }, "model": { "href": "/dev/authn/authenticate/htmlSql", "method": "GET" } }, { "template": "form", "kind": "select-authenticator", "title": "A standard LDAP backed authenticator", "properties": { "authenticatorType": "html-form" }, "model": { "href": "/dev/authn/authenticate/htmlLdap", "method": "GET" } }, { "template": "form", "kind": "select-authenticator", "title": "bankid1", "properties": { "authenticatorType": "bankid" }, "model": { "href": "/dev/authn/authenticate/bankid1", "method": "GET" } } ] } } ] }
Notice:
authentication-step
selector
options
The selector title is UI-ready, meaning that it contains a string ready to be shown in the user interface. This string is computed by the server using the existing localization subsystem, which supports internationalization and string customization.
title
Each option item contains a nested action, describing the option and what to do if it is selected by the user.
option
properties
The client presents these authentication selection options to the user, using the title and authenticatorType to enrich the user experience. The user selects one of them and the client performs the HTTP request described by the form’s model, i.e., does a GET request to the URI defined in the href field:
authenticatorType
GET
href
GET /dev/authn/authenticate/htmlSql HTTP/1.1 Host: example.com Accept: application/vnd.auth+json Authorization: DPoP ey...Yw DPoP: ey...Mg
The response contains a username and password based authentication form:
HTTP/1.1 200 OK Content-Type: application/vnd.auth+json { "type": "authentication-step", "actions": [ { "template": "form", "kind": "login", "title": "Login", "model": { "href": "/dev/authn/authenticate/htmlSql", "method": "POST", "type": "application/x-www-form-urlencoded", "actionTitle": "Login", "fields": [ { "name": "userName", "type": "username", "label": "Username" }, { "name": "password", "type": "password", "label": "Password" } ] } } ], "links":[ { "href": "/dev/authn/authenticate/htmlSql/forgot-password", "rel": "forgot-password", "title": "Forgot your password?" }, { "href": "/dev/authn/authenticate/htmlSql/forgot-account-id", "rel": "forgot-account-id", "title": "Forgot your username?" }, { "href": "/dev/authn/register/create/htmlSql", "rel": "register-create", "title": "Create account" } ] }
The type is still authentication-step and there is still a single action. The action’s template is form, meaning that the action’s model describes a form. The returned representation also contains a links top-level field, informing the client that there are some related resources to this step, namely the ones to redefine the password, recover the username, or register a new account.
links
Each link is represented by:
rel
The form is described in the model field and has:
method
POST
application/x-www-form-urlencoded
actionTitle
fields
username
password
name
label
The client uses the HAAPI representation to drive an UI that requests the username and password from the user. After collecting this information, the client performs a POST, containing the userName and password fields, using the application/x-www-form-urlencoded media type, as described by the model field:
userName
POST /dev/authn/authenticate/htmlSql HTTP/1.1 Host: example.com Accept: application/vnd.auth+json Content-Type: application/x-www-form-urlencoded Authorization: DPoP ey...Yw DPoP: ey...Kg userName=...&password=...
If the provided username and password are correct and the authentication process is completed, then HAAPI responds with a POST-based redirect back to the authorization service:
HTTP/1.1 200 OK Content-Type: application/vnd.auth+json { "type": "redirection-step", "actions": [ { "template": "form", "kind": "redirect", "model": { "href": "/dev/oauth/authorize?...", "method": "POST", "type": "application/x-www-form-urlencoded", "fields": [ { "name": "token", "type": "hidden", "value": "mG...2q" }, { "name": "state", "type": "hidden", "value": "R_...jQ" } ] } } ] }
The client identifies this as a redirect because:
hidden
This means that the action can be performed without any user interaction:
POST /dev/oauth/authorize?... HTTP/1.1 Host: example.com Accept: application/vnd.auth+json Content-Type: application/x-www-form-urlencoded Authorization: DPoP ey...gw DPoP: ey...Kg token=...&state=...
The final response contains the authorization response information:
HTTP/1.1 200 OK Content-Type: application/vnd.auth+json { "type": "oauth-authorization-response", "properties": { "code": "bU...oQ", "state": "...", "iss": "https://example.com/dev/oauth/anonymous" }, "links": [ { "href": "https://client.example.net/client-callback?code\u003dbU...oQ\u0026state\u003d...", "rel": "authorization-response" } ] }
The client should detect that this represents an authorization response by the type equal to oauth-authorization-response. In this case the properties object contains the standard OAuth authorization response fields, in this case code (the authorization code), state (the state string sent by the client on the authorization request) and iss (the issuer identifier of the authorization server that created the authorization response).
oauth-authorization-response
code
state
iss
From now on, the client should proceed exactly as defined by the OAuth 2.0 specifications, namely by doing a standard token request to exchange the authorization code for an access token.
The previous flows are happy-path examples, where all requests were successfully fulfilled. However, that is not always the case.
If the request cannot be fulfilled by the server, then the response will have a non-success status code and the payload will contain an error representation, using the application/problem+json format defined by RFC 7807.
application/problem+json
For instance, if the submission of the username-password form is missing the password field, the response will be:
HTTP/1.1 400 Bad Request Content-Type: application/problem+json { "type": "https://curity.se/problems/invalid-input", "title": "Form Errors", "invalidFields": [ { "name": "password", "reason": "invalidValue", "detail": "You have to enter your password" } ] }
Both the type and the title fields follow the semantics defined in RFC 7807:
The invalidFields is a custom representation field, containing information about the invalid or missing request fields. The client will probably use this error representation to overlay some error information on the UI produced on the previous step, and collect newer information from the user to retry the form post.
invalidFields
The custom fields that may appear on an error representation, as well as their semantics, depends on the type value. In the previous example, the invalidFields field meaning is contextualized by the https://curity.se/problems/invalid-input type.
https://curity.se/problems/invalid-input
As another example, if the username and passwords are both provided and don’t match, the response will be:
HTTP/1.1 400 Bad Request Content-Type: application/problem+json { "type": "https://curity.se/problems/incorrect-credentials", "title": "Incorrect credentials" }
In this case, no field information is provided, namely for security reasons. However the different type provides information about this distinct error cause.
Finally, the following response illustrates yet a different error type, this one following an authorization request with an invalid response_type:
response_type
HTTP/1.1 400 Bad Request Content-Type: application/problem+json { "type": "https://curity.se/problems/error-authorization-response", "error": "unsupported_response_type", "error_description": "Unknown or invalid response_type requested.", "links": [ { "href": "https://client.example.net/client-callback?error\u003dunsupported_response_type\u0026error_description\u003dUnknown+or+invalid+response_type+requested.\u0026state\u003d...", "rel": "authorization-response" } ] }
In this case the type informs that this payload represents an authorization response. The error and error_description are custom fields that follow the OAuth 2.0 authorization response semantics.
error
error_description
During the execution of a flow, HAAPI may need to redirect the client to different resources. The use of the HTTP redirect mechanism (3xx status code and Location response header) is an obvious choice for this. However, the use of per-request DPoP proof tokens prevents this since every HTTP request requires a newly-created proof token bound to the request target URI, including the requests triggered by a redirect. Due to this, API redirects are made using a 200 OK response and an action containing the target of the redirect.
Location
During a flow, i.e. the sequence of requests and responses required to perform authorization and authentication, the client needs to always send the on Session-Id header the last identifier received via the Set-Session-Id header. See Flow state management for more information.
Session-Id
Set-Session-Id
Note that, for visualization simplicity reasons, the previous HTTP messages excerpts do not include these headers.
The HAAPI responses may also include the metadata top-level field, not shown in the previous examples, with extra information about the request processing context. A client doesn’t need to understand this information to be able to perform a successful flow, however it can be used to provide richer user experiences.
metadata
As an example, the representation for the username-password form can have the following metadata fields:
HTTP/1.1 200 OK Content-Type: application/vnd.auth+json { "metadata": { "templateArea": "html1", "viewName": "authenticator/html-form/authenticate/get" }, "type": "authentication-step", "actions": ... }
templateArea
viewName