Authentication module for Apache httpd with JSON web tokens (JWT).
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.
See Dockerfile
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
openssl ecparam -name secp256k1 -genkey -noout -out ec-priv.pem
openssl ec -in ec-priv.pem -pubout -out ec-pub.pem
openssl genpkey -algorithm RSA -out rsa-priv.pem -pkeyopt rsa_keygen_bits:4096
openssl rsa -pubout -in rsa-priv.pem -out rsa-pub.pem
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.
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>
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>
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>
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
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>
jwt
and jwt-bearer
will allow only the Authorization header. jwt-cookie
allows only Cookie usage. jwt-both
accepts Authorization header and cookie. Cookie value will be ignored if Authorization header is set.