System: Access authorization in Apache 2.4
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
Related Articles - Apache 2.4
- System Access authorization in Apache 2.4
- System Authenticating Apache Logins with PostgreSQL
- System Re-naming vhost files to *.conf for Apache 2.4
- System Blocking Fake Googlebot and bingbot spiders
- System Apache authorization with dynamic DNS
Ian 23 August, 2018
What about this declaration?
<Directory "/docs/">
Order allow,deny
<LimitExcept GET POST OPTIONS>
Deny from All
<LimitExcept>
Allow from All
</Directory>
The Deny, then Allow is awkward.....
Matthew 13 June, 2018
It's mid 2018, and some packages have updated config for 2.4.7 so that there are a multitude of servers in the wild with require in root, but order/allow/deny in other directories. Apache loads without an error but the allow/deny syntax is ignored ?
Is it true they cannot be mixed ?
For now both the old and new syntax seem to work equally well, but at some point you may start to see deprecation warnings
PP 20 April, 2018
Thanks for the excellent examples. I had a question if this situation is possible:
1. Require basic authentication if you visit URL X
2. If you have authenticated at X, don't require authentication if you visit Y else require authentication if you visit Y
Basically, we want to allow unauthenticated Y iff X was authenticated previously
Anna 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?
Paul 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?
Alfredo Alcaide 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