Skip to content

Authentication design

Malcolm Sparks edited this page Jul 26, 2019 · 22 revisions

yada authentication

Introduction

Objective

To establish an improved design for yada’s authentication/authorization, which can be validated in the open prior to working on implementation.

Background

Feedback from some projects using 1.3-alpha has led us to think that the new authentication design needs further thought.

Design goals

Dimensions

@dmc: I think there’s a few dimensions here, listed below

  • Interaction behavior - how do multiple authentications interact?

    • merging them together can cause "ghost" entities which are mixes of multiple ones

    • Running only one can be confusing to explain why yours didn’t run

      • You also have to either decide to run in linear order (like a sequence) or whether to do provide a lookup-style match which only fires one

        • If you have predicates separate from action, yada can run all predicates meaning that all println checks get a chance to run.

  • Extensibility support - How do you define new authenticators?

    • They should be inspectable to find out:

      • What auth is supported on this resource?

      • e.g. for cookies, what cookie name must be used

      • e.g. for basic, what realms are available

    • It should be possible to support cookie/static authentication under the same key. This allows e.g. the authzn middleware to identify that authentication is present and activate the default authzn. Currently the default authzn doesn’t work for cookies.

    • Custom header support: Bearer tokens need fine-grained control over the headers which are set in response to failure in order to set the correct WWW-Authenticate error to indicate what failure has happened - which comes from a defined set.

Design

This is a write-up of the current proposed straw-man design.

There are a number of steps (interceptors) involved in the process commonly referred to as 'authentication and authorization' (perhaps more than two!):

  • Establishment of truthfulness of the claimed identity of the person/machine making the request (veracity). This is what we mean by authentication.

  • Optionally, given an identity, the establishment (temporary grants) of roles and entitlements to the request. This might involve a lookup in an LDAP database. This might be done as part of the previous step, or may be separate. This is a pre-requisite to authorization.

    This is optional because you could imagine a system where there are no roles, just individual identities and their access rights over resources.

  • A resource’s metadata is established prior to authorization, because the capabilities of a request might depend on which resource is involved - for example. a user’s bank account.

  • Given a resource and a set of roles and entitlements, what are the capabilities of the request -how should the request be handled?

Given the above, the term authorization is slightly ambiguous.

Authenticator map collections

A yada resource declares a collection of authenticator-maps:

{:yada.authentication/authenticators [{…} {…} {…}]}

If necessary, the value can be returned from a single-arity function, taking the yada context, and called on each request as shown in Example 1.

Example 1. A dynamic authenticator-map collection
{:yada.authentication/authenticators (fn [ctx] [{…} {…} {…}])}
Note
This is a common idiom in yada which encourages a static declarative definition while permitting dynamic per-request definitions where necessary.

If necessary, this function may also return a deferred value as shown in Example 2.

Example 2. Deferred dynamic authenticator-map collection
{:yada.authentication/authenticators (fn [ctx] (future [{…} {…} {…}]))}

Each item may be a function returning an authenticator map as show in Example 3.

Example 3. Dynamic authenticator-map
{:yada.authentication/authenticators [{…} {…} (fn [ctx] {…})]}

Also, a function may return a deferred authenticator map as shown in Example 4.

Example 4. Deferred dynamic authenticator-map
{:yada.authentication/authenticators [{…} {…} (fn [ctx] (future {…}))]}

Authenticator maps

An authenticator-map itself is just a Clojure map.

Each authenticator-map is required to have an entry of :yada.authentication/authenticate which provides a function that will be called with the yada context and the authenticator map. The function should return a map containing credible credentials, as shown in Example 5.

Example 5. A minimal authenticator-map
{
 ::yada.authentication/authenticate
 (fn [ctx auth-map] {::user "Alice"})
}

In non-trivial applications it is expected that authentication functions will require some I/O (e.g. to check the claims against a remote database). Therefore, it is possible to return a deferred value as shown in Example 6.

Example 6. A deferred authenticator
{
 ::yada.authentication/authenticate
 (fn [ctx auth-map] (future (check-db auth-map)))
}

An authenticator-map may contain a number of custom entries to provide information to the authenticator function. However, built-in entries include the following:

::yada.authentication/authenticate

Function to establish the verified credentials of a request, according to the authenticator. Required.

::yada.authentication.http/scheme

If the authenticator supports HTTP Authentication, indicates the authentication scheme, e.g. "Basic". Optional.

::yada.authentication.http/attributes

If the authenticator supports HTTP Authentication, a map of the attributes used in the challenge. For example: {"realm" "Wally World"}. This is where the realm of a protection space can be specified. Optional.

::yada.authentication.http/challenge

If the authenticator supports HTTP Authentication, the attributes are used, by default, to compose an entry in the WWW-Authenticate response header. If this is not desirable, the authenticator can provide a function returning the challenge token as a string (this is appended to scheme to form the challenge).

::yada.authentication.http/challenge-order

The relative position of the challenge against other challenges in the WWW-Authenticate. This is to support the workaround noted in RFC 7235 section 2.1 and defaults to the maximum integer value. Optional and intended only for us by well-known schemes.

HTTP Basic Authentication

When explaining HTTP Authentication, the classic example is HTTP Basic Authentication. This is implemented in yada.authentication.http-basic.

Let’s discuss in detail how this authenticator works.

Note
TODO: Show how basic authentication is achieved and disect the implementation.

The authenticate function for yada.authentication.http-basic will extract the user and password encoded with base64 encoding from the Authorization header and expect the map to contain a 3-arity function for it to call.

Note
How do HTTP Authentication authenticators issue WWW-Authenticate challenges?

Authentication algorithm

Each authenticator (specified in the :yada.authentication/authenticate entry) is called with the yada context and the authenticator map, as described in Authenticator maps.

Challenges

Note
TODO: Explain how challenges work

Authorizers

Authorizers are functions that take the yada context and can veto the request. An authorizer may be a clojure spec, for example.

Note
Can we compose authorizers?
  • is the user logged in?

  • is the user the owner of this resource?

  • does the user have a role that gives them access to this resource?

  • given the method, does the user have read or write or both privileges?

  • am I allowed to see a 403? (rather than hiding with a 404?)

  • am I allowed to see stack traces?

Note
Pre-authorization of other resources (menu-items, etc.)

It may be beneficial for the :response function to be able to call authorized? against another resource in order to perform a pre-flight check for links. e.g. for generating links in a menubar.