Benoit Tellier created JAMES-3522:
-------------------------------------
Summary: Allow authentication discovery on top of JMAP
Key: JAMES-3522
URL: https://issues.apache.org/jira/browse/JAMES-3522
Project: James Server
Issue Type: New Feature
Components: JMAP
Reporter: Benoit Tellier
Assignee: Antoine Duprat
Authentication discovery is a major interoperability concern on top of JMAP.
As a third-party client, I have no prior knowledge of the authentication
schemes supported by the JMAP server. As a client, I need a way to discover
supported authentication scheme (to ease end user configuration options).
Luckily, RFC-2617 (https://tools.ietf.org/html/rfc2617#section-3.2.1) about
HTTP authentication introduce such a mechanism through `WWW-Authenticate`
header. A failed 401 authentication would result in this header being
positioned, allowing discovery for the client of the supported authentication
mechanisms. The client can then set up the authentication mechanism it supports
and prefer, without requiring user input.
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/WWW-Authenticate
further
RFC-7235 updates RFC-2617 (https://tools.ietf.org/html/rfc7235#section-4.1)
{code:java}
A server generating a 401 (Unauthorized) response MUST send a
WWW-Authenticate header field containing at least one challenge.
{code}
So far James JMAP implementation only sends a 401 without setting
WWW-Authenticate header field - and is thus not best practice compliant.
We should infer the WWW-Authenticate from the AuthenticationStrategy James
relies on. Ideally, the Authenticator class should aggregate supported
challenges into a single header line. JMAP RFC-8620 endpoints should then add
the WWW-Authenticate headers upon 401 errors.
Here is an example of multi-challenge WWW-Authenticate header (RFC-7235):
{code:java}
WWW-Authenticate: Newauth realm="apps", type=1, title="Login to \"apps\"",
Basic realm="simple"
{code}
Please note that this header also can be used, if desired, to give hints toward
why auth failed... Example (https://tools.ietf.org/html/rfc6750#section-3):
{code:java}
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="example",
error="invalid_token",
error_description="The access token expired"
{code}
A full list of standard auth shemes is available here:
http://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml
Sadly, RFC-8620 do not mention the existence of RFC-7235 nor the existence of
WWW-Authenticate header. I think we should start a thread about this topic
within the IETF work-group. Maybe we should mandate "Basic" auth as a
pre-requisite to ensure inter-operability. I propose myself to start this
thread.
h4. Proposed code changes
As one might wish to customize authentication methods (eg: support OpenID
connect or other custom auth mechanisms) we need to dynamicaly generate the
WWW-Authenticate header for JMAP.
First let's have a POJO representing an authentication challenge:
{code:scala}
trait AuthenticationScheme {
val value: String
}
trait AuthenticationChallenge {
val scheme: AuthenticationScheme
val parameters: Map[String, String]
}
{code}
Note that I propose to not strong type parameters (all defined in RFC-2617) for
simplicity...
Here is what basic authentication authentication challenge would look like:
{code:scala}
case object BasicAuthenticationScheme extends AuthenticationScheme {
val value: String = "Basic"
}
case object BasicAuthenticationChallenge extends AuthenticationChallenge {
val scheme: AuthenticationScheme = BasicAuthenticationScheme
val parameters: Map[String, String] = Map("realm" -> "simple")
}
{code}
Then we can advertise AuthenticationChallenges as part of
AuthenticationStrategy:
{code:java}
public interface AuthenticationStrategy {
// reminder: allow authentication
Mono<MailboxSession> createMailboxSession(HttpServerRequest httpRequest);
AuthenticationChallenges getChallenge();
/* omitted as unrelevant here */
}
{code}
All existing authentication strategies will need to be updated accordingly...
Then the Authenticator class would aggregate AuthenticationChallenges into an
AuthenticateHeader, attached to the UnauthorizedException (why => it allows
authentication strategies to individually give hints on why auth failed, if
desired...):
{code:java}
case class AuthenticateHeader(challenges: Seq[ AuthenticationChallenge]) {
def headerValue: String = ???
}
case class UnauthorizedException(message: String, authenticateHeader:
AuthenticateHeader) extends RuntimeException
{code}
We then can adapt all JMAPRoutes to position `WWW-Authenticate` header upon 401
- and adapt corresponding tests.
--
This message was sent by Atlassian Jira
(v8.3.4#803005)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]