Services/Authentication: Difference between revisions

From MozillaWiki
Jump to navigation Jump to search
No edit summary
No edit summary
Line 71: Line 71:
     from services.baseapp import set_app, SyncServerApp
     from services.baseapp import set_app, SyncServerApp
     from services.whoauth import WhoAuthentication
     from services.whoauth import WhoAuthentication
 
   
     #  ...define urls and controllers here...
     #  ...define urls and controllers here...
 
   
     make_app = set_app(urls, controllers,
     make_app = set_app(urls, controllers,
                       klass=MyCustomServerApp,
                       klass=MyCustomServerApp,

Revision as of 01:59, 20 December 2011

Draft-template-image.png THIS PAGE IS A WORKING DRAFT Pencil-emoji U270F-gray.png
The page may be difficult to navigate, and some information on its subject might be incomplete and/or evolving rapidly.
If you have any questions or ideas, please add them as a new topic on the discussion page.

Goals

To allow services apps to authenticate users in a flexible and secure fashion.

To let us swap in different authentication schemes without changing application code.

To ensure apps don't take the "easy way out" and use insecure schemes like HTTP Basic Auth.

Data Model

Every user of a services app has the following unique identifiers:

  • an email address by which they identify themselves;
  • a username, formed by taking the sha1 hash of the email address; and
  • a numeric userid by which they are identified in persistent storage.

The email address may be mutable and is selected by the user, while the userid is immutable and is generated automatically by the registration system.

In code, the user is represented by a dict of their user data. It will always include the keys "username" and "userid". Applications may also arrange for other keys to be loaded into this dict.


User Backends

User account information is stored in a generic "backend" with the following interface:

XXX TODO describe the api


Application API

The authentication stack is build on the repoze.who framework.

Using Pyramid

Applications built using the new pyramid-based framework should activate authentication by including the "mozsvc.user" package into their pyramid config. This will set up authentication via repoze.who and the internal users database, as well as providing some handy shortcuts:

   def main(global_config, **settings):
       config = get_configurator(global_config, **settings)
       
       # add Mozilla default views
       config.include("mozsvc")
       
       # add the Mozilla users database
       config.include("mozsvc.user")
       
       ...etc...
       return config.make_wsgi_app()

Each request object will then have a "user" attribute that provides the identity dict for a successfully authenticated user. If the user is not authenticated then request.user will be something falsey (currently an empty dict, possibly None in the future):

   def some_view(request):
       if not request.user:
           return Response("You're not logged in")
       return Response("Hello %s" % (request.user["username"],))

Permission checking is performed via the standard pyramid permissions system. To force the user to provide credentials, you can either assign permissions to your views or explicitly raise a Forbidden exception.

The active user backend is available in the pyramid registry under the key "auth", and can be used to perform user-maintenance tasks like so:

   request.registry["auth"].create_user("testuser", "testpass", "test@example.com")

If no backend is in use (e.g. because users are being authenticated against a third-party service) then request.registry["auth"] will be None.


Using server-core

Applications using the server-core framework should use services.whoauth.WhoAuthentication as their auth management class. In the wsgiapp.py file use something like:

   from services.baseapp import set_app, SyncServerApp
   from services.whoauth import WhoAuthentication
   
   #  ...define urls and controllers here...
   
   make_app = set_app(urls, controllers,
                      klass=MyCustomServerApp,
                      auth_class=WhoAuthentication)

Each request object will then have a "user" attribute that provides the identity dict for a successfully authenticated user. If the user is not authenticated then request.user will be None:

   def some_view(request):
       if not request.user:
           return Response("You're not logged in")
       return Response("Hello %s" % (request.user["username"],))

To force authentication for a particular URL, configure Routes to add {"auth": True} into the matched variables. Typically this would look something like:

   urls = [('GET', '/{username}/info'), "controller_name", "action_name", {"auth": True})]

The active user backend is available as the "auth" attribute on the application object, and can be used to perform user-maintenance tasks like so:

   self.auth.create_user("testuser", "testpass", "test@example.com")

If no backend is in use (e.g. because users are being authenticated against a third-party service) then self.auth will be None.


Configuration

To use the default authentication setup, you need only configure a user backend. In your application config file, create an "auth" action like this:

   [auth]
   backend = services.user.sql.SQLUser
   sqluri = sqlite:////tmp/account.db

The default configuration will interrogate the backend to see what auth schemes it supports, and will provide all of them.

Finer control over the different stages of authentication can be achieved by configuring individual repoze.who plugins. TODO link to config description. For example the following configuration with authenticate against the user backend using *only* digest authentication.

   [who.plugin.digest]
   use = repoze.who.plugins.digestauth:make_plugin
   realm = 'Sync'
   
   [who.plugin.backend]
   use = services.whoauth.backendauth:make_plugin
   
   [who.identifiers]
   plugins = digest
   
   [who.challengers]
   plugins = digest
   
   [who.authenticators]
   plugins = backend



Workflow

Authentication in services apps is loosely based on the workflow of the repoze.who framework, and is explicitly divided into four stages: identify, authenticate, challenge and acknowledge.

The details of each stage in this workflow are hidden from the application code, but they exist as distinct extension and configuration points as described below.

Identify

The first step is to extract credentials from an incoming request. For an interactive application this might mean gathering the information that was submitted from a login form. For automation APIs this will most likely mean parsing credentials from the HTTP "Authorization" header in conformance with RFC 2617.

The outcome of this stage is a dict of authentication credentials. The precise contents of the dict will depend on the authentication scheme being used, but it must contain at least the following:

  • username: the string username by which the user identifies themselves
  • scheme: a string naming the authentication scheme to use

Authenticate

The extracted credentials dict is then checked for authenticity according to the particular authentication scheme in use. If successfully authenticated, this produces the dict of user information for use by the application.

Challenge

If the client provides invalid credentials (or no credentials) then the application will respond with one or more authentication challenges. For interactive applications a challenge would normally be a HTML form for them to submit their credentials. For automation APIs the challenge would be a "401 Authorization Required" response.

Acknowledge

If the client provides valid credentials, the server may include headers in its response to acknowledge the successful authentication. For example, it may set a session cookie.

Authentication Schemes

Basic

HTTP Basic Access Auth, aka password auth. The credentials dict contains "username" and "password".

Digest

HTTP Digest Access Auth, as described in RFC 2617. The credentials dict contains all of the parameters from the Authorization header, along with "request-method" and "content-md5". This makes the credentials self-contained so they can be verified by an aribitrary backend that may not have access to the metadata about the request.

SRP-MAC

A custom protocol designed to be better than Digest Auth, using the Secure Remote Password Protocol.

A combination of this SRP-HMAC proposal from bugzilla: https://bugzilla.mozilla.org/show_bug.cgi?id=356855

And the MAC access protocol for OAuth2: http://tools.ietf.org/html/draft-hammer-oauth-v2-mac-token

BrowserID

A custom protocol based on BrowserID/VEP. The credentials dict contains "username" and "assertion", where "assertion" is a valid browserid assertion.

Bearer Token

Something like signed cookies. For login sessions etc.

Implementation Plan

User Backend API

Change the user backend API to support generic dicts of credentials, and to allow interrogation of the supported auth schemes.

Bug #TODO

Add repoze.who to server-core

Replace the custom Authenticator class in server-core with one based on repoze.who. It will load a default configuration by default, and look in the config file for overrides.

Bug #TODO

Add shortcuts to mozsvc

Provide the mozsvc.user package to make things easier for pyramid apps.

Bug #TODO

Add Auth Schemes to Sync Client

Patch the sync client in mozilla-central to support different authentication schemes. Currently targeting just basic and digest.

Bug #TODO

Add auth schemes to LDAP backend? =

Make the necessary changes to let the LDAP backend authenticate with different schemes, e.g. digest-auth or browserid.

Get Rid of LDAP?

Replace LDAP with something better, what has native support for these various auth schemes.