AnthonyDeroche / mod_authnz_jwt

An authentication module for Apache httpd using JSON Web Tokens
Other
79 stars 47 forks source link
apache2 auth authentication authorization httpd iam jwt stateless token

mod_authnz_jwt

Authentication module for Apache httpd with JSON web tokens (JWT).

Build Status

More on JWT : https://jwt.io/

Supported algorithms : HS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384, ES512

Built-in checks : iss, aud, exp, nbf

Configurable checks : every claims contained in the token (only string and array)

This module is able to deliver JSON web tokens containing all public fields (iss, aud, sub, iat, nbf, exp), and the private field "user". Authentication process is carried out by an authentication provider and specified by the AuthJWTProvider directive.

On the other hand, this module is able to check validity of token based on its signature, and on its public fields. If the token is valid, then the user is authenticated and can be used by an authorization provider with the directive "Require valid-user" to authorize or not the request.

Although this module is able to deliver valid tokens, it may be used to check tokens delivered by a custom application in any language, as long as a secret is shared between the two parts. This feature is possible because token-based authentication is stateless.

Build Requirements

Quick start

Installation using Docker

See Dockerfile

Installation from sources

sudo apt-get install libtool pkg-config autoconf libssl-dev check libjansson-dev
git clone https://github.com/benmcollins/libjwt
cd libjwt
git checkout tags/v1.12.1
autoreconf -i
./configure
make
sudo make install
cd ..
sudo apt-get install apache2 apache2-dev libz-dev
git clone https://github.com/AnthonyDeroche/mod_authnz_jwt
cd mod_authnz_jwt
autoreconf -ivf
./configure
make
sudo make install

Generate EC keys

openssl ecparam -name secp256k1 -genkey -noout -out ec-priv.pem
openssl ec -in ec-priv.pem -pubout -out ec-pub.pem

Generate RSA keys

openssl genpkey -algorithm RSA -out rsa-priv.pem -pkeyopt rsa_keygen_bits:4096
openssl rsa -pubout -in rsa-priv.pem -out rsa-pub.pem

Authentication

The common workflow is to authenticate against a token service using for instance username/password. Then we reuse this token to authenticate our next requests as long as the token remains valid.

Using username/password

You can configure the module to deliver a JWT if your username/password is correct. Use "AuthJWTProvider" to configure which providers will be used to authenticate the user.

Authentication modules are for instance:

The delivered token will contain your username in a field named "user" (See AuthJWTAttributeUsername to override this value) as well as public fields exp, iat, nbf and possibly iss and aud according to the configuration.

A minimal configuration might be:

AuthJWTSignatureAlgorithm HS256
AuthJWTSignatureSharedSecret Q0hBTkdFTUU=
AuthJWTIss example.com
<Location /demo/login>
    SetHandler jwt-login-handler
    AuthJWTProvider file
    AuthUserFile /var/www/jwt.htpasswd
</Location>

Using a JWT

A secured area can be accessed if the provided JWT is valid. JWT must be set in Authorization header. Its value must be "Bearer ".

If the signature is correct and fields are correct, then a secured location can be accessed.

Token must not be expired (exp), not processed too early (nbf), and issuer/audience must match the configuration.

A minimal configuration might be:

AuthJWTSignatureAlgorithm HS256
AuthJWTSignatureSharedSecret Q0hBTkdFTUU=
AuthJWTIss example.com
<Directory /var/www/html/demo/secured/>
    AllowOverride None
    AuthType jwt
    AuthName "private area"
    Require valid-user
</Directory>

Authorization

You can use the directive Require jwt-claim key1=value1 key2=value2. Putting multiple keys/values in the same require results in an OR. You can use RequireAny and RequireAll directives to be more precise in your rules.

In case your key is an array, you can use the directive Require jwt-claim-array key1=value1 to test that "value1" is contained in the array pointed by the key "key1".

Examples:

AuthJWTSignatureAlgorithm HS256
AuthJWTSignatureSharedSecret Q0hBTkdFTUU=
AuthJWTIss example.com
<Directory /var/www/html/demo/secured/>
    AllowOverride None
    AuthType jwt
    AuthName "private area"
    Require jwt-claim user=toto
    Require jwt-claim-array groups=group1
</Directory>

How to get authenticated user in your apps?

If your app is directly hosted by the same Apache than the module, then you can read the environment variable "REMOTE_USER".

If the apache instance on which the module is installed acts as a reverse proxy, then you need to add a header in the request (X-Remote-User for example). We use mod_rewrite to do so. For your information, rewrite rules are interpreted before authentication. That's why why need a "look ahead" variable which will take its final value during the fixup phase.

RewriteEngine On
RewriteCond %{LA-U:REMOTE_USER} (.+)
RewriteRule . - [E=RU:%1]
RequestHeader set X-Remote-User "%{RU}e" env=RU

Configuration examples

This configuration is given for tests purpose. Remember to always use TLS in production.

With HMAC algorithm:

<VirtualHost *:80>
    ServerName example.com
    DocumentRoot /var/www/html/

    # default values
    AuthJWTFormUsername user
    AuthJWTFormPassword password
    AuthJWTAttributeUsername user

    AuthJWTSignatureAlgorithm HS256
    AuthJWTSignatureSharedSecret Q0hBTkdFTUU=
    AuthJWTExpDelay 1800
    AuthJWTNbfDelay 0
    AuthJWTIss example.com
    AuthJWTAud demo
    AuthJWTLeeway 10

    <Directory /var/www/html/demo/secured/>
        AllowOverride None
        AuthType jwt
        AuthName "private area"
        Require valid-user
    </Directory>

    <Location /demo/login>
        SetHandler jwt-login-handler
        AuthJWTProvider file
        AuthUserFile /var/www/jwt.htpasswd
    </Location>

    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

With EC algorithm:

<VirtualHost *:80>
    ServerName example.com
    DocumentRoot /var/www/html/

    # default values
    AuthJWTFormUsername user
    AuthJWTFormPassword password
    AuthJWTAttributeUsername user

    AuthJWTSignatureAlgorithm ES256
    AuthJWTSignaturePublicKeyFile /etc/pki/auth_pub.pem
    AuthJWTSignaturePrivateKeyFile /etc/pki/auth_priv.pem
    AuthJWTExpDelay 1800
    AuthJWTNbfDelay 0
    AuthJWTIss example.com
    AuthJWTAud demo
    AuthJWTLeeway 10

    <Directory /var/www/html/demo/secured/>
        AllowOverride None
        AuthType jwt
        AuthName "private area"
        Require valid-user
    </Directory>

    <Location /demo/login>
        SetHandler jwt-login-handler
        AuthJWTProvider file
        AuthUserFile /var/www/jwt.htpasswd
    </Location>

    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

With Cookie:

<VirtualHost *:80>
    ServerName example.com
    DocumentRoot /var/www/html/

    # default values
    AuthJWTFormUsername user
    AuthJWTFormPassword password
    AuthJWTAttributeUsername user

    AuthJWTSignatureAlgorithm HS256
    AuthJWTSignatureSharedSecret Q0hBTkdFTUU=
    AuthJWTExpDelay 1800
    AuthJWTNbfDelay 0
    AuthJWTIss example.com
    AuthJWTAud demo
    AuthJWTLeeway 10

    AuthJWTDeliveryType Cookie

    <Directory /var/www/html/demo/secured/>
        AllowOverride None
        AuthType jwt-cookie
        AuthName "private area"
        Require valid-user
    </Directory>

    <Location /demo/login>
        SetHandler jwt-login-handler
        AuthJWTProvider file
        AuthUserFile /var/www/jwt.htpasswd
    </Location>

    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

Documentation

Directives

AuthType
AuthJWTProvider
AuthJWTSignatureAlgorithm
AuthJWTSignatureSharedSecret
AuthJWTSignaturePublicKeyFile
AuthJWTSignaturePrivateKeyFile
AuthJWTIss
AuthJWTAud
AuthJWTExpDelay
AuthJWTNbfDelay
AuthJWTLeeway
AuthJWTFormUsername
AuthJWTFormPassword
AuthJWTAttributeUsername
AuthJWTDeliveryType
AuthJWTTokenName
AuthJWTCookieName
AuthJWTCookieAttr
AuthJWTRemoveCookie