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]

Reply via email to