Services/Authentication: Difference between revisions
(Created page with "{{draft}} = Authentication = == Goal == To allow services apps to authenticate users in a flexible and secure fashion. To ensure apps don't take the "easy way out" and use in...") |
No edit summary |
||
Line 6: | Line 6: | ||
To allow services apps to authenticate users in a flexible and secure fashion. | 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. | To ensure apps don't take the "easy way out" and use insecure schemes like HTTP Basic Auth. | ||
Line 14: | Line 16: | ||
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. | 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. | ||
== Workflow == | == Workflow == | ||
Line 92: | Line 92: | ||
If no backend is in use (e.g. because users are being authenticated against a third-party service) then these objects will be None. | If no backend is in use (e.g. because users are being authenticated against a third-party service) then these objects 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 | |||
Revision as of 22:39, 28 November 2011
Authentication
Goal
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 two unique identifiers: a username by which they identify themselves, and a userid by which they are identified in persistent storage. The username may be mutable and is selected by the user; the userid is immutable and is generated automatically.
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.
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.
Application API
Loading the Framework
Applications built using the new pyramid-based framework can hook into this system by including the "mozsvc.user" package into their pyramid config:
def main(global_config, **settings): config = get_configurator(global_config, **settings) # adds Mozilla default views config.include("mozsvc") # adds the Mozilla auth framework config.include("mozsvc.user") ...etc... return config.make_wsgi_app()
Applications using the old server-core framework will have authentication loaded automatically.
Getting the User
Each request object will 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 false.
def some_view(request): if not request.user: return Response("You're not logged in") return Response("Hello %s" % (request.user["username"],))
Demanding Credentials
For pyramid applications, you can force the user to provide valid credentials using the standard permission system, either by assigning permissions to your views or by explicitly raising Forbidden when the user is not authenticated.
For server-core applications, you must configure your route match to include the key "auth" with value "True".
User Backends
User account information is stored in a generic "backend" with the following interface:
XXX TODO describe the api
For pyramid applications, the active user backend can be accessed through the pyramid registry:
request.registry["auth"].create_user("testuser", "testpass", "test@example.com")
For server-core applications, the active user backend is available as an attribute of the controller:
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 these objects 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
Authentication Schemes
Basic
Digest
SRP-MAC
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