Authentication Authorization

Johannes Rudolph edited this page Aug 14, 2013 · 7 revisions

. . .

Deprecation Note

This documentation is for release 0.9.0 (from 03/2012), which is built against Scala 2.9.1 and Akka 1.3.1 (see Requirements for more information). Most likely, this is not the place you want to look for information. Please turn to the main spray site at http://spray.io for more information about other available versions.

. . .

Many web services need to apply some authentication and maybe authorization logic to some or even all parts of their API. spray supports this with some dedicated directives: authenticate and authorize. In order to be able to use them effectively one should understand the difference between 'authentication' and 'authorization'.

Authentication vs. Authorization

Authentication is the process of establishing a known identity for the user, whereby 'identity' is defined in the context of the application. This may be done with a username/password combination, a cookie, a pre-defined IP or some other mechanism. After authentication the system believes that it knows who the user is.

Authorization is the process of determining, whether a given user is allowed access to a given resource or not. In most cases, in order to be able to authorize a user (i.e. allow access to some part of the system) the users identity must already have been established, i.e. he/she must have been authenticated. Without prior authentication the authorization would have to be very crude, e.g. "allow access for all users" or "allow access for noone". Only after authentication will it be possible to, e.g., "allow access to the statistics resource for admins, but not for regular members".

Authentication and authorization may happen at the same time, e.g. when everyone who can properly be authenticated is also allowed access (which is often a very simple and somewhat implicit authorization logic). In other cases the system might have one mechanism for authentication (e.g. establishing user identity via an LDAP lookup) and another one for authorization (e.g. a database lookup for retrieving user access rights).

The authenticate Directive

The authenticate takes one argument and extracts one value. Its argument is a GeneralAuthenticator which is defined like this:

type GeneralAuthenticator[U] = RequestContext => Future[Authentication[U]]
type Authentication[U] = Either[Rejection, U]

The GeneralAuthenticator does the actual job of either establishing a user identity (represented by an instance of U) or providing a Rejection instance. The only implementation currently predefined in spray is the HttpAuthenticator, which specializes the GeneralAuthenticator in that it uses the HTTP Authorization header to authenticate the user and extract a user object. A further specialization, the BasicHttpAuthenticator implements HTTP Basic Auth. spray comes with a convenience method called httpBasic, which simplifies the creation of a BasicHttpAuthenticator. It takes two arguments:

  • A String used for the authentication 'realm'
  • A UserPassAuthenticator instance, which is defined like this
type UserPassAuthenticator[U] = Option[(String, String)] => Future[Option[U]]

and is responsible for creating (or not creating) a user instance given a username/password combination

The implementation of the httpBasic methods is this:

def httpBasic[U](realm: String = "Secured Resource",
                 authenticator: UserPassAuthenticator[U] = FromConfigUserPassAuthenticator)
                 : BasicHttpAuthenticator[U] =
  new BasicHttpAuthenticator[U](realm, authenticator)

If you don't specify your own UserPassAuthenticator implementation the FromConfigUserPassAuthenticator will be used, which looks up usernames and passwords stored in plaintext in the spray configuration (see Configuration for more on this).

You can use the authenticate directive like this:

authenticate(httpBasic(realm = "myResource")) { user =>
  ...
}

Only requests with proper HTTP Authorization header containing an existing user/pass combination are passed through to the inner route. If no Authorization header is present an AuthenticationRequiredRejection is created, which results in the generation of a proper WWW-Authenticate challenge response header. If an Authorization header is present but the user could not be authenticated the request is rejected with an AuthenticationFailedRejection triggering a 403 Forbidden reponse by default.

The authorize Directive

The authorize directive comes in two flavors:

  • authorize(check: => Boolean)
  • authorize(check: RequestContext => Boolean)

Both variants take a function determining whether the request is authorized. If it is the request is passed on to the inner route unchanged, otherwise an AuthorizationFailedRejection is created, triggering a 403 Forbidden reponse (the same as in the case of an AuthenticationFailedRejection) by default.

The authorize directive could for example be used like this:

path("somePath") {
  authenticate(httpBasic(authenticator = MyAuthenticator)) { user =>
    get {
      authorize(user.hasReadingAccess) {
         ...
      }
    } ~
    put {
      authorize(user.hasWritingAccess) {
         ...
      }
    }
  }
}

This example uses a custom UserPassAuthenticator which created custom user objects supporting things like hasReadingAccess and hasWritingAccess.

Authenticating against LDAP

As an alternative to the FromConfigUserPassAuthenticator spray also comes with the LdapAuthenticator. This UserPassAuthenticator implements verification of username/password combinations against an LDAP server, optionally over SSL/TLS. Check out the API documentation for more information on how to do this.