Example - Username and password based authentication

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:

  1. The Accept header contains the Curity media type (application/vnd.auth+json);

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

  • The top level type has the redirection-step value, informing the client that this in an intermediate step in the flow, more specifically an redirection step.
  • There is a single action, making it clear for the client what needs to be done next.
  • The action has template set to form, meaning that the model object will contain a form, i.e., information on how to perform a request.
  • The kind is set to redirect meaning that this response represents a redirect, i.e, the client can perform the next request without requesting information from the user.

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:

  • The type is authentication-step, informing the client application that we are in the middle of authenticating the user.
  • Again, there is a single top-level action, making it clear to the client what needs to be done next.
  • The action’s template is now selector and not form as in the previous response. This informs the client that the model object describes a sequence of nested actions and not a form. The action’s kind provides additional information about the selection, namely that it is an authenticator selector.
  • As any other action, the selector has a title providing information about the action’s purpose.
  • The selector is represented by a model object with an options array, describing each one of the available 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.

Each option item contains a nested action, describing the option and what to do if it is selected by the user.

  • The template field states that it is a form, i.e., a representation of a request to be made to the server.
  • The kind field states that performing this action will select an authenticator.
  • The title field has an UI-ready description of the authenticator.
  • The properties field has additional information about the action. In this case, it contains a string with the authenticator type.

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:

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.

Each link is represented by:

  • An UI-ready title string that can be used in the UI when the link is shown.
  • The rel identifier, defining the relationship between the current resource (i.e. the username-password form) and the target resource (e.g. the forgot password resource).
  • The href field with the target resource URI.

The form is described in the model field and has:

  • The href field with the URI to where the form should be submitted to.
  • The method field with the HTTP method to use (e.g. POST or GET).
  • The type field with the identifier of the media type to use on the submission. A value of application/x-www-form-urlencoded means that the format to use is the same as the one used by browsers when submitting an HTML form by default.
  • The actionTitle provides an additional UI-ready string that can be used when presenting the form in the UI. The title describes the overall form, while actionTitle can be used on the submit button.
  • Finally, the fields array describes the form fields, where each one is described by:
    • The field’s type (e.g. username or password).
    • The field’s name to use on the HTTP request when submitting the form.
    • The field’s UI-ready label to use when presenting the field input control on the UI.

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:

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:

  • The representation type is redirection-step and there is a single action.
  • The action’s template is form and the kind is redirect.
  • All the form’s fields are of hidden type, meaning that all the information required to submit the form is already available to client.

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).

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.

Error representation

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.

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 type field contains an unique identifier for the error type, in this case an input validation error. This type is an URI, however it may not be dereferenciable.
  • The title contains an UI-ready string describing the error type.

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.

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.

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:

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.

API-based redirects

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.

Flow-state management

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.

Note that, for visualization simplicity reasons, the previous HTTP messages excerpts do not include these headers.

Metadata

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.

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": ...
}
  • The templateArea field contains the name of template area selected by the server for the current client and authenticator.
  • The viewName field contains the template name set by the request handler. For HAAPI requests, this uniquely identifies the representation function, i.e., the function that maps the response model defined by the handler into the HAAPI representation.