skip to content

System: Access authorization in Apache 2.4

 Tweet0 Shares0 Tweets

In Apache 2.4 the authorization configuration setup has changed from previous versions. Satisfy, Order, Deny and Allow have all been deprecated and replaced with new Require directives.

Below we've compiled some examples to guide you through the transition.

If you are upgrading a server using the legacy authorization directives you can make them work quickly by enabling (it should be activated by default) mod_access_compat in Apache:

sudo a2enmod access_compat

Apache Documentation

The documentation from Apache: Upgrading to 2.4 from 2.2 provides the following basic examples. The old configuration settings are on the left, and the new ones for Apache 2.4 on the right:

All requests are denied:

Order deny,allow Deny from all Require all denied

All requests are allowed:

Order allow,deny Allow from all Require all granted

Only hosts in the example.org domain are allowed access:

Order Deny,Allow Deny from all Allow from example.org Require host example.org

But this only scratches the surface of what's now available.

RequireAll and RequireAny

The most interesting new features are the RequireAll, RequireAny and RequireNone authorization containers. They promise to be both more powerful and more human-readable than the old syntax.

By default all Require directives are handled as though contained within a <RequireAny> container directive. In other words, if any of the specified authorization methods succeed, then authorization is granted.

Here is real world example where a website limits access by requiring a Basic Authentication login for certain directories:

AuthType Basic AuthName "Password Protected" AuthUserFile <path_to_your_htpasswd_file> SetEnvIf REQUEST_URI "^/(admin|secure)/" PROTECTED Deny from all Satisfy any Allow from env=!PROTECTED Require valid-user SetEnvIf REQUEST_URI "^/(admin|secure)/" PROTECTED <RequireAny> <RequireAll> Require not env PROTECTED Require all granted </RequireAll> <RequireAll> AuthType Basic AuthName "Password Protected" AuthUserFile <path_to_your_htpasswd_file> Require valid-user </RequireAll> </RequireAny>

In both cases we set an environmental variable PROTECTED when the request is for a file in the /admin/ or /secure/ directories. The syntax for this part hasn't changed. If this variable is set, then a password will be required for access.

While the old syntax works, it's not immediately clear how it works. Basically to get access the request has to meet (Satisfy) either the Allow or the Require directive.

In the new syntax this is more explicit. The request needs to pass at least one (RequireAny) of the two RequireAll container rulesets. The first container grants all users access to non-PROTECTED directories, while the second container requires a valid login.

You can keep nesting containers until all possible options are covered.

But seeing as the outer <RequireAny> is already implied, we should be able to remove it. Actually we can remove quite a bit now that we know what we're doing:

AuthType Basic AuthName "Password Protected" AuthUserFile <path_to_your_htpasswd_file> SetEnvIf REQUEST_URI "^/(admin|secure)/" PROTECTED <RequireAll> Require not env PROTECTED </RequireAll> Require valid-user

Note that any Require not directives must always be enclosed in a RequireAll directive. Otherwise you will see an alert logged:

[core:alert] ... negative Require directive has no effect in <RequireAny> directive

See further down the page a version of this example that does away with the ENV variable entirely by using an expr condition.

Require authorization providers

The Require directive comes with a number of build-in authorization providers, including some already demonstrated above. Different modules provide different methods.

The following are provided by the mod_authz_core module:

all

Replaces Allow from all and Deny from all in the old syntax:

Require all granted Require all denied

env

Require env safe_zone <RequireAll> Require not env PROTECTED </RequireAll>

method

This example allows only GET and HEAD requests unless you are logged in:

<RequireAny> Require method GET HEAD Require valid-user </RequireAny>

The <RequireAny> container is not necessary here, but included for clarity:

expr

Require expr %{HTTP_USER_AGENT} != 'BadBot'

The following options are provided by the mod_authz_host module:

local

Require local

ip

<RequireAll> Require ip 192.168.1.0/24 Require not ip 192.168.1.104 </RequireAll> Require ip 2001:db8:1:1::/64

host

<RequireAll> Require host example.org Require not host blocked.example.org </RequireAll>

Working with expressions

After a bit of messing about we were able to further simplify the previous example by removing the SetEnvIf clause and replacing it with a Require expr regular expression condition.

AuthType Basic AuthName "Password Protected" AuthUserFile <path_to_your_htpasswd_file> SetEnvIf REQUEST_URI "^/(admin|secure)/" PROTECTED <RequireAll> Require not env PROTECTED </RequireAll> Require valid-user AuthType Basic AuthName "Password Protected" AuthUserFile <path_to_your_htpasswd_file> Require expr %{REQUEST_URI} !~ m#^/(admin|secure)/# Require valid-user

The tricky part was working out how to include the forward slash / in the regular expression. The solution is instead of the default format which doesn't allow a forward slash in the match:

Require expr %{REQUEST_URI} !~ /expr/

To use the alternative syntax:

Require expr %{REQUEST_URI} !~ m#expr#

For details on other SERVER variables and comparison operators that can be used see the link under References below.

Granting local access

Another real world example is granting access only to the local network.

In this case we're defining the local network as the server itself (localhost), plus the 192.168.1.* subnet covering 192.168.1.0 - 192.168.1.255.

<Directory "/path/to/your/website"> Options FollowSymlinks AllowOverride None Order allow,deny Allow from 127.0.0.0/8 192.168.1 ::1 </Directory> <Directory "/path/to/your/website"> Options FollowSymlinks AllowOverride None Require local Require ip 192.168.1 </Directory>

The local Require'ment matches requests from the local host over IPv4 or IPv6 (so including 127.0.0.1/8 and ::1). We wrap this, along with Require ip 192.168.1, in a RequireAny authorization container because we want to accept connections that match either condition.

We could also write 192.168.1.0/24 instead of just 192.168.1, but they have the same effect.

If you want to also allow connections from outside the local network, but requiring authentication, the configuration becomes:

<Directory "/path/to/your/website"> Options FollowSymlinks AllowOverride None Require local Require ip 192.168.1 Require valid-user </Directory>

So we're now granting access from localhost and the local network without authentication, plus from all other locations, but then requiring authentication.

You can make this more secure by restricting outside access to only recognised locations:

<Directory "/path/to/your/website"> Options FollowSymlinks AllowOverride None Require local Require ip 192.168.1 <RequireAll> Require host example.org Require not host badhost.example.org Require valid-user </RequireAll> </Directory>

Now an external connection can only come from *.example.org and only in conjunction with a valid login. To specify more than one domain or ip address in addition to example.org they will need to be wrapped in yet another container:

<Directory "/path/to/your/website"> Options FollowSymlinks AllowOverride None Require local Require ip 192.168.1 <RequireAll> <RequireAny> Require host example.org example.com Require ip 8.8.8.8 </RequireAny> Require not host badhost.example.org Require valid-user </RequireAll> </Directory>

For those getting confused, RequireAll means that all the requirements in that container need to be met, while RequireAny means that only one or more of the contained requirements needs to be met:

Require (local) OR (ip 192.168.1) OR [ [ (host example.org) OR (host example.com) OR (ip 8.8.8.8) ] AND (NOT host badhost.example.com) AND (valid-user) ]

Public file in Private directory

Thank you to Alfredo for this question - how to have a password-protected directory (or website) but allow access to a specific file.

If you have a directory ~/private/ then you can make the entire directory secure by adding an .htaccess file ~/private/.htaccess with:

AuthType Basic AuthName "Password Required" AuthUserFile /path/to/.htpasswd Require valid-user

But what if there is a file ~/private/public.html that you want to make globally accessible? This wasn't possible in earlier versions of Apache, but can be done now quite simply:

AuthType Basic AuthName "Password Required" AuthUserFile /path/to/.htpasswd Require expr %{REQUEST_URI} = "/private/public\.html" Require valid-user

How does it work? Remember that there is an explicit <RequireAny> wrapped around the two Require statements, so it reads as: either the request is for the file public.html or require a password.

References

< System

Send a message to The Art of Web:


used only for us to reply, and to display your gravatar.

<- copy the digits from the image into this box

press <Esc> or click outside this box to close

User Comments

Post your comment or question

18 May, 2017

Hi, thanks for the article. Is it possible to restrict access to Domain1 to a handful IPs yet allow hotlinking of its images to Domain2?

1 February, 2017

Can Apache be set to try one OR another method for a required login?

For example - can it be made to ask for a login, and check the credentials first against LDAP, and if no success there, then check the credentials against a PHP/MySQL database?

31 July, 2015

Hello.

First of all, I'd like to thnk you for your wonderful tutorial on how to use the new Apache 2.4 authorization directives. I've found the explanation very easy to understand and with a clear example on how to implement them.

However, what I'm trying to achieve is just the contrary: to password-protect a whole website, EXCEPT a particular URI (or directory). I've made modifications to your code (after reading and searching a lot), but as I imagined, it doesn't work.

I was wondering if you could be so kind to give me a quick explanation or indication on how I could achieve this, or even better, add this case to your online tutorial.

Thank you very much in advance. Kind regards:
Alfredo Alcaide

top