mod_auth_pubtkt: a pragmatic Web Single Sign-On (SSO) solution - Documentation

## mod_auth_pubtkt: Documentation

### Deployment considerations

Since the "valid until" field in a ticket is necessarily in absolute time (UNIX timestamp), the clocks of the ticket-generating login server and the ticket-verifying web servers need to be more or less in sync. The longer the ticket lifetime, the less important this becomes. It's generally good practice to keep your servers' time synchronized (using NTP, for example).

### Downloading and installing the module (Unix)

Download the source code for the latest version of mod_auth_pubtkt here.

Decompress the downloaded archive and run the included "configure" script, specifying the path to apxs if necessary (use `where apxs` to find it). The Apache version should be detected automatically (but note that the configure/make scripts haven't been tested under anything but FreeBSD and Mac OS X):

    # tar xzfv mod_auth_pubtkt-0.x.tar.gz
    # cd mod_auth_pubtkt-0.x
    # ./configure
    # make
    # make install

### Downloading and installing the module (Windows)

The source tarball, which you can download in the Unix section above, also contains pre-compiled modules for Apache 2.0 and 2.2 (in the "bin" subdirectory):

Decompress the downloaded archive and copy the relevant module for the version of Apache you are using into the "modules" directory inside your Apache program directory, then follow the instructions below (which apply both to Unix and Windows machines). Make sure that you use an Apache version that is bundled with OpenSSL (even if you don't use HTTPS), as mod_auth_pubtkt needs it.

Note: Windows binaries for OpenSSL (you'll need the command-line openssl.exe to generate a key pair) can be found at http://www.slproweb.com/products/Win32OpenSSL.html.

### Generating a key pair

See the section below for a discussion on whether to use DSA or RSA.

#### DSA:

    # openssl dsaparam -out dsaparam.pem 1024
    # openssl gendsa -out privkey.pem dsaparam.pem
    # openssl dsa -in privkey.pem -out pubkey.pem -pubout

The dsaparam.pem file is not needed anymore after key generation and can safely be deleted.

#### RSA:

    # openssl genrsa -out privkey.pem 1024
    # openssl rsa -in privkey.pem -out pubkey.pem -pubout

### Module configuration

First of all, make sure that the module is loaded:

    LoadModule auth_pubtkt_module libexec/apache/mod_auth_pubtkt.so
    AddModule mod_auth_pubtkt.c		# Apache 1.3 only

Ensure that mod_authz_user is loaded/enabled as well.

Here's a simple VirtualHost configuration with mod_auth_pubtkt as a starting point; the configuration directives are explained below.

Note that the `AuthType mod_auth_pubtkt` statement is required!

    <VirtualHost *:80>
        ServerName myserver.mydomain.com
        DocumentRoot /path/to/my/htdocs

        TKTAuthPublicKey /etc/apache2/tkt_pubkey.pem

        <Directory /path/to/my/htdocs>
            Order Allow,Deny
            Allow from all

            AuthType mod_auth_pubtkt
            TKTAuthLoginURL https://sso.mydomain.com/login
            TKTAuthTimeoutURL https://sso.mydomain.com/login?timeout=1
            TKTAuthUnauthURL https://sso.mydomain.com/login?unauth=1
            TKTAuthToken "myserver"
            require valid-user
        </Directory>
    </VirtualHost>

#### Directives for use in server config, virtual hosts and directory/location/.htaccess scope

- `TKTAuthPublicKey`
  - Path to either a DSA or RSA public key file in PEM format
  - This public key will be used to verify ticket signatures
- `TKTAuthDigest`
  - String indicating what digest algorithm to use when verifying ticket signatures
  - Valid values are SHA1, DSS1, SHA224, SHA256, SHA384, and SHA512
  - If not specified, the old defaults of SHA1 (for an RSA public key) or DSS1 (for a DSA public key) will be used.

#### Directives for use in directory/location/.htaccess scope

- `TKTAuthLoginURL`
  - URL that users without a valid ticket will be redirected to
  - The originally requested URL will be appended as a GET parameter (normally named "back", but can be changed with `TKTAuthBackArgName`)
- `TKTAuthTimeoutURL`
  - URL that users whose ticket has expired will be redirected to
  - If not set, `TKTAuthLoginURL` is used
- `TKTAuthPostTimeoutURL`
  - Same as `TKTAuthTimeoutURL`, but in case the request was a POST
  - If not set, `TKTAuthTimeoutURL` is used (and if that is not set either, `TKTAuthLoginURL`)
- `TKTAuthUnauthURL`
  - URL that users whose ticket doesn't contain any of the required tokens (as set with `TKTAuthToken`) will be redirected to
  - If not set, `TKTAuthLoginURL` is used
- `TKTAuthBadIPURL` (since v0.7)
  - URL that users whose IP doesn't match the cip value in the ticket (if supplied) will be redirected to
  - If not set, `TKTAuthLoginURL` is used
- `TKTAuthRefreshURL` (since v0.3)
  - URL that users whose ticket is within the grace period (as set with the `graceperiod` key in the ticket) before the actual expiry will be redirected to. Only GET requests are redirected; POST requests are accepted normally. The script at this URL should check the ticket and issue a new one
  - If not set, `TKTAuthLoginURL` is used
- `TKTAuthHeader` (since v0.9)
  - A space separated list of headers to use for finding the ticket (case insensitive). If this header specified is `Cookie` then the format of the value expects to be a valid cookie (subject to the `TKTAuthCookieName` directive). Any other header assumes the value is a simple URL-encoded value of the ticket. The first header that has content is tried and any other tickets in other header(s) are ignored. example, use Cookie first, fallback to X-My-Auth: `TKTAuthHeader Cookie X-My-Auth`
  - Default: `Cookie`
- `TKTAuthCookieName`
  - Name of the authentication cookie to use
  - Default: `auth_pubtkt`
- `TKTAuthBackArgName`
  - Name of the GET argument with the originally requested URL (when redirecting to the login page)
  - Default: `back`
- `TKTAuthRequireSSL`
  - only accept tickets in HTTPS requests
  - Default: off
- `TKTAuthToken`
  - token that must be present in a ticket for access to be granted
  - Multiple tokens may be specified; only one of them needs to be present in the ticket (i.e. any token can match, not all tokens need to match)
- `TKTAuthFakeBasicAuth` (since v0.3)
  - if on, a fake Authorization header will be added to each request (username from ticket, fixed string "password" as the password). This can be used in reverse proxy situations, and to prevent PHP from stripping username information from the request (which would then not be available for logging purposes)
  - Default: off
- `TKTAuthPassthruBasicAuth` (since v0.8)
  - if on, the value from the ticket's "bauth" field will be added to the request as a Basic Authorization header. This can be used in reverse proxy situations where one needs complete control over the username and password (see also TKTAuthFakeBasicAuth, which should not be used at the same time).
  - Default: off
- `TKTAuthPassthruBasicKey` (since v0.8)
  - if set, the bauth value will be decrypted using the given key before it is added to the Authorization header.
  - see Ticket format for details on the encryption
  - length must be exactly 16 characters
- `TKTAuthDebug`
  - debug level (1-3, higher for more debug output)
  - default: 0
  - Note: setting TKTAuthDebug to > 0 will cause full ticket values to appear in your server's error log, which could be used to log in to other servers.

### Ticket format

Authentication tickets to be processed by mod_auth_pubtkt are composed of key/value pairs, with keys and values separated by '=' and individual key/value pairs separated by semicolons (';'). The following keys are defined; mod_auth_pubtkt silently ignores unknown keys:

- uid (required; 32 chars max.)
  - the user ID (username) that the ticket has been issued for
  - passed to the environment in REMOTE_USER
- validuntil (required)
  - a UNIX timestamp (the number of seconds since 00:00:00 UTC on January 1, 1970) that describes when this ticket will expire
- cip (optional; 39 chars max.)
  - the IP address of the client that this ticket was issued for
  - if present, mod_auth_pubtkt will only accept the ticket for requests that came from this IP address
  - this is usually fine for use on Intranets and is in fact recommended, but may have to be omitted in the presence of NAT or load-balancing proxy servers
- tokens (optional; 255 chars max.)
  - a comma-separated list of words (group names etc.)
  - the presence of a given token can be made mandatory in the per-directory configuration (using the TKTAuthToken directive), effectively giving a simple form of authorization
  - the contents of this field are available to the environment in REMOTE_USER_TOKENS
- udata (optional; 255 chars max.)
  - user data, for use by scripts; made available to the environment in REMOTE_USER_DATA
  - not interpreted by mod_auth_pubtkt
- graceperiod (optional; since v0.4)
  - a UNIX timestamp (should be before the ticket's expiration date) after which GET requests will be redirected to the refresh URL (or the login URL, if no refresh URL is set)
- bauth (optional; since v0.8)
  - Base64 encoded value for Authorization header (when TKTAuthPassthruBasicAuth is enabled).
  - Can optionally be encrypted (TKTAuthPassthruBasicKey option)
    - encryption is AES-128-CBC, zero padded (not PKCS7), IV in first 16 bytes
    - the plaintext username:password string should be encrypted, and the (binary) result after encryption Base64 encoded before being added to the ticket
    - for an encryption example using Mcrypt, see the included php-login/pubtkt.inc
- sig (required)
  - a Base64 encoded RSA or DSA signature over the digest of the content of the ticket up to (but not including) the semicolon before 'sig'
  - The default digest is SHA-1, unless TKTAuthDigest has specified a different algorithm.
  - RSA: raw result; DSA: DER encoded sequence of two integers – see Dss-Sig-Value in RFC 2459
  - must be the last item in the ticket string

Here's an example of how a real (DSA) ticket looks:

    uid=mkasper;cip=192.168.200.163;validuntil=1201383542;tokens=foo,bar;udata=mydata;
    sig=MC0CFDkCxODPml+cEvAuO+o5w7jcvv/UAhUAg/Z2vSIjpRhIDhvu7UXQLuQwSCF=

The ticket string is saved URL-encoded in a domain cookie, usually named `auth_pubtkt`, but this can be changed (using the `TKTAuthCookieName` directive).

If you would like to use a custom header instead of a cookie (or want to use both), see the `TKTAuthHeader` directive.

### Generating tickets

An example implementation of a login/ticket generating script in PHP is provided with the distribution (in the `php-login` subdirectory). It uses a simple flat-file user database by default, but can easily be extended to support LDAP (e.g. using adLDAP), RADIUS and other authentication methods.

The ticket-generating (and verifying) functions are in `pubtkt.inc`. They use the OpenSSL command-line binary directly, for two reasons:

- no dependency on PHP's OpenSSL extension
- DSA signatures can be generated as well (normally, using the PHP OpenSSL extension, only RSA is supported)

For Perl users, a module and example CGI script are provided in the `perl-login` subdirectory of the distribution.

If you use Ruby, there's a gem created by Matt Haynes that helps with generating tickets.

For Python users, Andrey Plotnikov has created a module for generating tickets.

### Whether to choose RSA or DSA

For digital signatures, two public-key schemes are commonly used: RSA and DSA. This module supports both, but you need to choose one over the other. Put simply, and assuming that both offer the same security at similar key sizes, it's mostly a decision between speed and signature (ticket/cookie) length.

- RSA
  - signature length: as long as the modulus
    - 1024-bit modulus: 128 bytes (~172 bytes after Base64 encoding)
  - signing speed (1024-bit): about 235 signatures/s on a 2.8 GHz P4
  - verification speed (1024-bit): about 4400 verifications/s on a 2.8 GHz P4
- DSA
  - signature length: constant (independent of key size)
    - always 2 x 160-bit, plus 6 byte DER encoding overhead = 46 bytes (sometimes 47 because of an extra leading zero byte with OpenSSL) – ~64 bytes after Base64 encoding
  - signing speed (1024-bit): about 477 signatures/s on a 2.8 GHz P4
  - verification speed (1024-bit): about 390 verifications/s on a 2.8 GHz P4

From a performance point of view, RSA is the clear winner, as each ticket only needs to be signed once, but usually verified many times on different servers. However, note that mod_auth_pubtkt caches tickets, so the verification only needs to be done once per server process and ticket (and not once per request).

If ticket size matters to you more than speed, then DSA is the better choice; otherwise, you're probably better off using RSA. In the end, it's mostly down to "religious" issues or what you're already using in your company.

### Generating a ticket signature on the command line

    # echo -n "uid=foobar;validuntil=123456789;tokens=;udata=" \
      | openssl dgst -dss1 -sign privkey.pem \
      | openssl enc -base64 -A

If TKTAuthDigest isn't being used, specify `-dss1` for DSA, and `-sha1` for RSA. Otherwise specify the TKTAuthDigest directive's algorithm (i.e. `-sha256` for SHA256).

### Verifying a ticket signature on the command line

Strip the signature off the ticket and Base64-decode it into a temporary file:

    # echo "MC0CFQC6c....=" | openssl enc -d -base64 -A > sig.bin

Pipe the ticket value through openssl to verify the signature using the public key in pubkey.pem:

    # echo "uid=foobar;validuntil=123456789;tokens=;udata=" \
      | openssl dgst -dss1 -verify pubkey.pem -signature sig.bin

### Security considerations for domain cookies

Note that if rogue servers under your domain are a concern, the domain cookies used by mod_auth_pubtkt may pose a problem, since a rogue server can steal a legitimate user's ticket. This can be mitigated by marking the ticket cookie as "secure", so that it is only transported via HTTPS, which means that only servers with a valid SSL certificate for your domain can see the user's ticket (unless the user overrides security warnings in the browser). Also, including the client IP address in the ticket (as is recommended whenever possible) makes it harder to use a stolen ticket.

Another way to solve this would be to change the login server to check the "back" URL and, instead of issuing cookies directly, include the ticket in the redirect back to the web server with the desired resource, which can then install the ticket as a cookie under its own server name. This would require adding support for parsing tickets in GET parameters to mod_auth_pubtkt (could be backported from mod_auth_tkt). Also, the login server would need to keep a copy of the ticket stored in a cookie under its own server name so that the user only has to log in once, of course. Finally, since there would now be a cookie for each server, it would be much more difficult to properly log out (without closing the browser).