-
Notifications
You must be signed in to change notification settings - Fork 98
Authentication design
To establish an improved design for yada’s authentication/authorization, which can be validated in the open prior to working on implementation.
Feedback from some projects using 1.3-alpha has led us to think that the new authentication design needs further thought.
-
Improved conformance with HTTP RFCs
-
Retain declarative resources (data for introspection)
-
Compatible with OpenApi, notably security requirements and possibly json-api
@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.
-
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.
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.
{: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.
{:yada.authentication/authenticators (fn [ctx] (future [{…} {…} {…}]))}
Each item may be a function returning an authenticator map as show in Example 3.
{:yada.authentication/authenticators [{…} {…} (fn [ctx] {…})]}
Also, a function may return a deferred authenticator map as shown in Example 4.
{:yada.authentication/authenticators [{…} {…} (fn [ctx] (future {…}))]}
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.
{
::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.
{
::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.
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?
|
Each authenticator (specified in the :yada.authentication/authenticate entry) is called with the yada context and the authenticator map, as described in Authenticator maps.
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.