Protocol‎ > ‎Design Documents‎ > ‎

Authentication Design Doc

    Goals:

    • Users can securely login to their wave-in-a-box user account
    • Easy to later add different authentication methods, including:
      • LDAP
      • XMPP [1]
      • Smartcard-based authentication
      • Authentication via yubikey, etc.
    • Simple password authentication works out-of-the-box.
    • Good documentation
    • Servlets can easily fetch the currently logged in user
    • Server admins can choose which authentication method(s) are valid

    And for bonus points:

    • Easy to scale across multiple machines
    • Supports Gmail-like 'log out all active sessions'


    Desired User Flow


    [This is pretty standard for web services]

    1. The user opens https://waveinabox.example.com/ in their browser
    2. The user browses public waves
    3. The user clicks a 'Sign In' link.
    4. The user's browser displays a login page, prompting the user for their credentials (By default, their username & password).
    5. The user enters their information and submits the form.
    6. The server authenticates the provided information using the configured login module.
    7. The server generates a new session token (unguessable string) which it associates with the user's address.
    8. The server responds to the client. It sets a cookie in the user's browser with the session token and redirects the user back to the URL they were at before they clicked 'Sign In'
    9. The user's browser reopens the wave they were looking at before they hit login. Their browser sends the session token when it opens the fetch request, websocket connection, etc and the server knows their identity.


    Components

    JAAS


    To make it easy to add authentication systems to wave-in-a-box, we use Java's JAAS service to do authentication. [
    Java Authentication and Authorization Service - Wikipedia]

    People with custom authentication needs should be able to configure their own JAAS LoginModule and easily hook it up to wave-in-a-box. A password based JAAS login module is provided and used by default. This stores a password digest in each user's account information object.

    Session data

    Like any other web service, once a user is authenticated, they are provided with a session token in an HTTP cookie. The user provides this cookie during each subsequent HTTP request. The server uses this token to evaluate the user's identity.

    Authentication servlet

    The wave in a box server contains a servlet which performs authentication. Calling GET on this servlet's url (by default /auth) presents the user with a login form. The form POSTs authentication information to the server. The server uses its JAAS configuration to check the provided credentials and, if valid, generate a session token and provide it to the user.

    JAAS login modules provide no standard method for generating a web form, so the form must be configured manually. A standard form which asks for a login address and password is configured by default.

    An optional redirect URL can be provided. When the user authenticates, the server redirects the user's browser to this URL.


    Authenticating the websocket connection

    As of websockets draft v76, websockets support cookie-based authentication like any other HTTP service. However, websockets don't have differentiated error messages. We will add an Unauthenticated error message to the protocol, which tells the client that they cannot access information without re-authenticating.


    Implementation

    Interesting new classes:

    org.waveprotocol.---.authentication.
    SessionManager - Helper code for sessions, which allows any servlet to discover which user is logged in.
    AccountStoreLoginModule - A JAAS LoginModule which does password-based authentication
    ConfigurationProvider - A simple class which generates a JAAS configuration if JAAS has not been configured.
    HttpRequestBasedCallbackHandler - A JAAS CallbackHandler which uses HTTP POST data to configure a login request.

    org.waveprotocol.---.rpc.
    AuthenticationServlet - Servlet for authenticating users. GET fetches the login HTML page, POST sends authentication information to JAAS.


    Considered, and rejected approaches


    Use Jetty's JAAS module

    Jetty provides a JAAS authentication container (Jetty/Feature/JAAS). This does much of what is described above, which could simplify the code involved. However:

    • The code is very poorly documented
    • There is no documentation explaining how to configure Jetty/JAAS in an embedded context
    • This would deeply tie wave-in-a-box to Jetty

    If we want people to be able to provide their own authentication modules, a lack of documentation is actually a pretty big problem.


    Use stateless, signed session tokens

    Instead of generating an unguessable string and using that as the session token, its possible to create a session token by signing the user's session data (username, signed in date, ..?) with a certificate. Doing this means the server doesn't have to store any state for the user's session, which should make scaling a bit easier and sessions would persist across server restarts.

    It also bloats out the size of the session token from ~20 bytes to ~200 bytes, and requires a bunch more crypto code. (It is dangerous to implement this yourself.)

    The standard authentication token system should be fine. We could revisit this technique later.


    Use OpenID, Google Friend Connect, Facebook Connect, or something similar

    OpenID is a standard authentication system which allows a user to authenticate to a single host, and then direct other sites (like wave in a box) to use the authentication information at that site to log them in.

    Wave in a box could use OpenID as a way to authenticate users. All wave users will have a wave address anyway ('joseph@example.com'), but the wave in a box frontend could authenticate users to their wave address using OpenID authentication.

    The same is true of Facebook Connect, Google Friend Connect, etc.

    This would be really nice for users, because they wouldn't to need to remember a new password for wave in a box.

    However, even if we used OpenID for authentication, we would still need a local user database (to associate the OpenID URLs with local usernames). We would also still need a simple password-based authentication system so the system works out of the box.

    The best way to implement OpenID-based authentication would (architecturally) be almost identical to the authentication system described above. We would just need to add an OpenID-based login module, and a login form which allows the user to elect to login via OpenID. The same is true of FacebookConnect, or any of the other similar systems.

    The architecture described above will support any of these other authentication systems if anyone wants to write an authenticator for them. Other authenticators are welcome in the source tree.


    An aside: XMPP-based Authentication

    Unfortunately, the XMPP component API doesn't provide any way to use the XMPP server to authenticate users. This would be ideal for installations which already use XMPP, as all the XMPP users would magically become valid wave users out-of-the-box.

    There is a nasty workaround which can be implemented to make this work: We could create a custom LoginModule that takes the user's authentication information and makes a login attempt as that user on the connected XMPP server. If the XMPP server allows the login attempt, we disconnect and allow the wave login attempt.

    This is nasty architecturally, but should be secure and stable. It would also put pressure on the XMPP standards body to provide a method to do authentication through the component API.

    It would be great for somebody to add this to wave in a box as a custom authenticator, if XMPP authentication is important enough to your users.
     
Comments