You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by "Benoit Tellier (Jira)" <se...@james.apache.org> on 2021/03/24 05:14:00 UTC

[jira] [Created] (JAMES-3522) Allow authentication discovery on top of JMAP

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: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org