Skip to content

Authentication Authorization

Mathias edited this page Jul 26, 2011 · 6 revisions

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 many times 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 => Either[Rejection, U]

The GeneralAuthenticator does the actual job of either establishing a user identity (represented by an instance of U) or rejecting the request. 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)] => 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.

Clone this wiki locally