I was doing some tests while trying to provide an answer to mod-rewrite redirect but prevent direct access.
Original question goal is basically mask
example.com/public/foo
asexample.com/foo
while forbidding access to the real URLexample.com/public/foo
.
Once I found a possible solution appending a token to the rewritten URL's Query String,
RewriteCond %{REQUEST_URI} !/public/
RewriteRule ^(.*)$ /public/$1?token=SECRET_TOKEN [L]
RewriteCond %{REQUEST_URI} /public/
RewriteCond %{QUERY_STRING} !token=SECRET_TOKEN
RewriteRule ^(.*)$ / [R=403,L]
I realized (with Chrome's DevTools) that I really can't see this token in the Request Headers.
I understand that's because the first RewriteRule
doesn't trigger a redirect on the client and all this rewrite process takes place in the server.
If I am right, this SECRET_TOKEN
is secure, isn't it? (By secure, I mean it can't be known by the client in any way)
Ordinarily, the client should never see the SECRET_TOKEN
- as you say, the rewrite is internal to the server.
However, it's possible the SECRET_TOKEN
could get exposed under certain conditions, depending on the server config.
For example, using the code you posted, the SECRET_TOKEN
could get exposed by requesting either example.com/public
or example.com/subdir
(where subdir
is a subdirectory inside /public
) - note the omission of the trailing slash.
example.com/public - If you were to request /public
(no trailing slash) then mod_dir "corrects" this by appending a trailing slash, which is achieved with a 301 external redirect. However, the redirect does not occur immediately and your RewriteRule
directive ends up rewriting this request (since the REQUEST_URI
variable is still /public
at this point - no trailing slash) to /public/?token=SECRET_TOKEN
(note the trailing slash now on the directory). (The URL-path does not appear to be updated by the RewriteRule
for some reason, as you might expect this to be /public/public
, however, the query string is still appended. Maybe something to do with the subrequest that mod_dir issues? Not sure why?) Since a 301 has already been triggered by mod_dir, this response (containing the SECRET_TOKEN
) is now sent back to the user as an external redirect and the SECRET_TOKEN
is exposed.
example.com/subdir - A similar (but slightly different) thing would happen if there was a subdirectory /public/subdir
and you requested /subdir
(no trailing slash). Your RewriteRule
directive kicks in first this time and internally rewrites the request to /public/subdir?token=SECRET_TOKEN
. Ok so far. But then mod_dir kicks in and wants to append a trailing slash to /public/subdir
- it does this with an external 301 redirect. So the request is now redirected to /public/subdir/?token=SECRET_TOKEN
and the SECRET_TOKEN
is exposed to the user (as well as the /public
subdirectory, which was previously unknown).
However, using the query string in this way does have some other caveats:
As it stands, you are overwriting any other query string on the request.
To merge an existing query string on the request, you would need to specify the QSA
flag on the RewriteRule
. However, if a user then appended ?token=
on the request then they would override your token
URL param if you wanted to read this using the $_GET
superglobal in a PHP script. You could manually change the order of the query string parameters by explicitly including the QUERY_STRING
server variable in the substitution instead of using the QSA flag - but this is a bit more messy (eg. what if there is no query string?).