Contact Us

Access Control in Angie

Every project eventually needs to restrict access to certain parts of the application. The Angie web server provides a variety of ways to solve this problem using built-in and third-party modules, which this article walks through.

Video version

A video version of this article is available on YouTube.

Basic HTTP authentication

Start with the simplest method to configure — the standard Auth Basic module. It uses a file with user logins and passwords in .htpasswd format.

The first step is to create that password file. Use the htpasswd utility (part of the Apache utility suite) or build a password yourself with openssl passwd. Both methods are shown below:

htpasswd -c /etc/angie/htpasswd newuser
echo -n 'testuser:' >> /etc/angie/.htpasswd
openssl passwd -apr1 >> /etc/angie/.htpasswd

This example uses the standard MD5 hashing method in apr1 format, but other variants are supported. The output is a file with one user per line: the login and the password hash separated by a colon.

testuser:$apr1$lE18tH5l$hAaBMjBG7OwSOhdoqIdC.0
newuser:$apr1$DnNBSOwt$mLRu5H4izpPsvqxKEQgXo0

The file can now restrict access to a server or a location (valid contexts: http, server, location, limit_except). It is enough to point at the user file and name an access zone, as in the example below:

location / {
  auth_basic           "Identify yourself!";
  auth_basic_user_file /etc/angie/.htpasswd;
}

If the auth_basic directive (the password prompt) inherited from a parent configuration block needs to be turned off, there is a special value off. For example, a server can require authentication while a separate /static/ location stays open:

server {
  auth_basic           "Identify yourself!";
  auth_basic_user_file /etc/angie/.htpasswd;

  location /static/ {
    auth_basic off;
  }
}

Keep in mind that HTTP Basic authentication sends the login and password in plain text, so it should be paired with HTTPS to prevent credentials from being intercepted during authentication. There is also no way to manage access lists of users: everyone listed in the file gets access. To separate access by user groups, create several files — one per group.

IP-based access control

Access to sites is often restricted based on the client's IP address. For this case, use the standard Access module.

Access rules are processed in the order they appear in the config, up to the first match. So for correct behavior, list the more specific allow or deny rules first, then the general rule for everyone else. Consider this example:

location /sec/ {
  deny  192.168.0.100;
  allow 192.168.0.0/24;
  deny  all;
}

Here access is denied from IP 192.168.0.100, while every other address in the 192.168.0.0/24 network is allowed. The last rule then blocks access for all remaining client addresses (deny all).

User-list and address restrictions can be combined. For that, use the satisfy directive, which defines how the checks interact. With all (the default), every check must pass: the user list, Auth Request, and the address rules. The value any grants access when at least one check succeeds. An example of combining the methods:

location /sec/ {
  satisfy any;
  deny  192.168.0.100;
  allow 192.168.0.0/24;
  deny  all;
  auth_basic           "Identify yourself!";
  auth_basic_user_file /etc/angie/.htpasswd;
}

In the configuration above, access is granted to clients from the 192.168.0.0/24 subnet (except 192.168.0.100) or to clients that authenticate against the user list in /etc/angie/.htpasswd.

Authentication with Auth Request

For a flexible access control system, use the standard Auth Request module. It issues an internal HTTP request to determine access rights. Based on the response code of that request, access is either allowed (200) or denied (401 or 403). Any other response code is treated as an error.

The logic itself can be implemented in any programming language — there are no special restrictions. The only requirement is returning the correct response codes.

As a built-in solution, you can use an implementation based on NJS, a subset of the JavaScript language executed inside Angie when the corresponding module is loaded.

An example configuration that uses NJS:

js_path "/etc/angie/njs/";
js_import main from http/authorization/auth_request.js;

resolver 1.1.1.1;

upstream backend {
  server 127.0.0.1:8091;
}

server {
      listen 80;

      location /secure/ {
          auth_request /validate;

          proxy_pass http://backend;
      }

      location /validate {
          internal;
          js_content main.authorize;
      }
}

server {
      listen 127.0.0.1:8091;
      return 200 "BACKEND:$uri\n";
}

In this example, the /secure/ location is protected. An attempt to access it triggers an internal request to /validate, where the njs function authorize does the processing. The auth_request.js code:

function authorize(r) {
    var signature = r.headersIn.Signature;

    if (!signature) {
        r.error("No signature");
        r.return(401);
        return;
    }

    if (r.method != 'GET') {
        r.error(`Unsupported method: ${r.method}`);
        r.return(401);
        return;
    }

    var args = r.variables.args;

    var h = require('crypto').createHmac('sha1', 'foo');

    h.update(r.uri).update(args ? args : "");

    var req_sig = h.digest("base64");

    if (req_sig != signature) {
        r.error(`Invalid signature: ${req_sig}\n`);
        r.return(401);
        return;
    }

    r.return(200);
}

export default {authorize}

The authentication check builds an HMAC (Hash-based Message Authentication Code) signature using a secret key (foo) and the SHA1 hashing algorithm, then encodes the signature in Base64. These commands can be used to verify it (the third one should give a positive result):

curl http://localhost/secure/B
curl http://localhost/secure/B  -H Signature:fk9WRmw7Rl+NwVAA759+H2Uq
curl http://localhost/secure/B  -H Signature:fk9WRmw7Rl+NwVAA759+H2UqxNs=

Other examples of NJS applied to authentication and beyond can be found in this third-party repository.

The Auth JWT module

Now for the third-party authentication modules. The first one is the Auth JWT module. It provides authentication with JSON Web Token (JWT) tokens. The module supports JSON Web Signature (JWS) and can be used with the OpenID Connect protocol. Full module documentation is on the project page. The module installs from the Angie repository:

apt|dnf install angie-module-auth-jwt

Load the module in the main context:

load_module modules/ngx_http_auth_jwt_module.so;

A minimal configuration looks roughly like this:

location / {
  auth_jwt          "closed site";
  auth_jwt_key_file conf/keys.json;
}

The auth_jwt directive turns on signature verification provided by the Auth JWT module. The second directive, auth_jwt_key_file, points at the file with the keys used for signature verification. The file supports the jwks (JSON Web Key Set — the default) and keyval formats.

The Auth SPNEGO module

The third-party Auth SPNEGO module lets you use the SPNEGO protocol (Simple and Protected GSS API Negotiation Mechanism) for authentication with Kerberos and GSS API. The idea of the protocol is to negotiate the authentication methods supported under GSS API between the client and the server. Module documentation is available in the project's repository.

The module installs from the Angie repository:

apt|dnf install angie-module-auth-spnego

Load the module in the main context:

load_module modules/ngx_http_auth_spnego_module.so;

Required directives:

  • auth_gss: (on/off) — enables the module;
  • auth_gss_keytab — path to the keytab file.

If the client fails to negotiate an authentication method, it falls back to basic authentication. To disable that behavior, use this directive:

auth_gss_allow_basic_fallback off;

The Auth LDAP module

Another access control option is integrating Angie with an LDAP server. LDAP (Lightweight Directory Access Protocol) support is provided by the third-party Auth LDAP module.

The module installs from the Angie repository:

apt|dnf install angie-module-auth-ldap

Load the module in the main context:

load_module modules/ngx_http_auth_ldap_module.so;

An example configuration of two LDAP servers with user and group requirements:

http {
    ldap_server test1 {
        url ldap://192.168.0.1:3268/DC=test,DC=local?sAMAccountName?sub?(objectClass=person);
        binddn "TEST\\LDAPUSER";
        binddn_passwd LDAPPASSWORD;
        group_attribute uniquemember;
        group_attribute_is_dn on;
        require valid_user;
    }

    ldap_server test2 {
        url ldap://192.168.0.2:3268/DC=test,DC=local?sAMAccountName?sub?(objectClass=person);
        binddn "TEST\\LDAPUSER";
        binddn_passwd LDAPPASSWORD;
        group_attribute uniquemember;
        group_attribute_is_dn on;
        require valid_user;
    }

    server {
        listen       8000;
        server_name  localhost;

        auth_ldap "Forbidden";
        auth_ldap_servers test1;
        auth_ldap_servers test2;

        location / {
            root   html;
            index  index.html index.htm;
        }
    }
}

The configured LDAP servers can now be used for authentication within a server:

server {
  listen       8000;
  server_name  localhost;

  auth_ldap "Forbidden";
  auth_ldap_servers test1;
  auth_ldap_servers test2;

  location / {
    root   html;
    index  index.html index.htm;
  }
}

In the example above, the LDAP module is enabled with the auth_ldap directive, and the authentication servers are set with the auth_ldap_servers directives. Unfortunately, the documentation on the module's project page is rather sparse.

The Auth PAM module

Angie can authenticate using the standard capabilities of the PAM modular system in Linux. For this, use the third-party Auth PAM module. The module installs from the Angie repository:

apt|dnf install angie-module-auth-pam

Load the module in the main (main) context:

load_module modules/ngx_http_auth_pam_module.so;

This unlocks additional directives; the full module description is in the documentation. For example, PAM can set up authentication through LDAP. Here the setup uses simple authentication with local system users on Linux. The example assumes Ubuntu 24.04, so the configuration may differ on other systems. Create the PAM config (/etc/pam.d/angie) with this content:

auth    required     pam_unix.so
account required     pam_unix.so

This binds authentication to checking the user's login and password in /etc/passwd and /etc/shadow. Since the server runs as an unprivileged user (www-data in this example), it has no access to the /etc/shadow file by default:

ls -al /etc/shadow
-rw-r----- 1 root shadow 1167 Sep  4 12:53 /etc/shadow

To grant access, add the server user to the shadow group:

usermod -aG shadow www-data

System preparation is done. All that's left is to configure a location with PAM authentication:

location /secure/ {
  auth_pam              "Secure Zone";
  auth_pam_service_name "angie";
}

The auth_pam directive enables authentication through the Auth PAM module, and auth_pam_service_name points at the service name (the config in pam.d). If everything is configured correctly, accessing this location brings up a prompt for a username and password — enter a local user to get access. When testing, keep in mind that successful authentication is cached by the browser, so check changes in an incognito window.

Summary

This article covered several ways to solve the access control problem with authentication in the Angie web server. The set of options is fairly broad: from basic authentication with built-in modules to integration with LDAP and Kerberos using third-party modules. The article also touched on extending the server's functionality with the NJS module, which can handle authentication tasks as well.