Panel: invalid login with basic auth and .htpasswd

I have a website on Uberspace. It is password protected by Apache .htpasswd. This website is queried by another website via KQL. Therefore, basic auth is activated in config.php.

When I try to login to the panel, this error message comes up:

The Panel cannot connect to the API

Console output:

Failed to load resource: the server responded with a status of 403 ()
GET https://my-domain.de/api/auth 403
login:1 Uncaught (in promise) 
{status: 'error', message: 'Invalid login', code: 403, key: 'error.access.login', details: Array(0)}

It seems to be a conflict between Apache authentification and Kirby basic auth. When I deactivate one of the two, panel login works.

config.php:

return [
	'api' => [
		'basicAuth' => true
	]
];

Tested with two Kirby-versions:
3.5.7.1
3.7.5

In the Forum post Installation login issues, a solution is suggested either by upgrading Kirby version or using hashed password. I tried MD5 and BCRYPT for password encryption in .htpasswd, without success.

The post Invalid Login Message with Panel Login suggests to flush caches and users and install a fresh panel on remote server; I tried that too.

Are you using the same credentials for the Apache password protection and the Kirby user?

There are two user/password pairs in .htpasswd:

  • human user (owner of website)
  • API client for KQL request

In Kirby, there are two users:

  • human user with again different credentials for panel access
  • API client for KQL requests – identical credentials because it needs to get verified by both, .htaccess password protection and Kirby

OK, this should work.

What does var_dump($_SERVER['HTTP_AUTHORIZATION']) say in Kirby? Does it match what the API client sends?
Please don’t post the output as it’s the encoded credentials.

No, it matches the base64_encoded credentials of the human user that got authorized via .htaccess to enter the directory. These credentials do not exist in Kirby.

But you request the KQL endpoint with the credentials of the API user, right? If you still get the “human” credentials in PHP, then Apache is messing with the Authorization header that comes from the request.

Yes, maybe I got your last question wrong. The API connection does work (I’m using Insomnia to test it). The error occurs for the human user trying to reach the panel.

As a “human” user to access the directory at all, I need to authorize via .htaccess first. Then, when the homepage loads, my “human” .htaccess-credentials are returned by var_dump($_SERVER['HTTP_AUTHORIZATION']) (temporarily added to the home-template).

I just found out that the moment I log into the directory via .htaccess, a file .logins is being created in /site/accounts of the Kirby installation. That is before I even try to log into Kirby.
This does not happen at an API request (Insomia).

The file says:
{"by-ip":{"xxxxxxxxxxxx":{"time":1664122310,"trials":5}},"by-email":[]}
I masked by-ip for this post, but by-email is originally empty.
What’s also Interesting is that trials counts up everytime I reload the page (again without even trying to reach the panel).

Ah, I see. Once you enable the api.basicAuth option, Kirby will use the Authorization header also to authenticate the human user. Because the “human” credentials are not known to Kirby, every request by the human user counts as an invalid authentication attempt, which increments the trials in the .logins file (brute-force protection).

So you need to configure Apache to pass the Authorization header to Kirby only if it’s the API user. Otherwise the header should be dropped so that the human user can authenticate via a session cookie as normal. Alternatively you can create a Kirby user for the “human” credentials from .htpasswd. This would have the advantage that the user is automatically logged in, but the disadvantages that you need to mirror the config and that authenticated Kirby responses are a bit slower because the password hash needs to be checked on every single request.

Thank you for the explanation!

Sounds good but also advanced. Can you give a hint on how to achieve it?

Also worth considering, thank you.

You can encode the username of your API user with Base64 and use it in the SetEnvIf rule:

SetEnvIf Authorization "^(Basic yourBase64Username.*)" HTTP_AUTHORIZATION=$1

Thank you and sorry for the late reply.

I’m having trouble with the suggested SetEnvIf rule. I encoded the API-username and set the rule in .htaccess, but still cannot login to the panel (The Panel cannot connect to the API). Maybe I’m missing something?

What does your full .htaccess look like at the moment?

# Kirby .htaccess

# rewrite rules
<IfModule mod_rewrite.c>

# enable awesome urls. i.e.:
# http://yourdomain.com/about-us/team
RewriteEngine on

# make sure to set the RewriteBase correctly
# if you are running the site in a subfolder.
# Otherwise links or the entire site will break.
#
# If your homepage is http://yourdomain.com/mysite
# Set the RewriteBase to:
#
# RewriteBase /mysite

# In some environments it's necessary to
# set the RewriteBase to:
#
RewriteBase /

# block files and folders beginning with a dot, such as .git
# except for the .well-known folder, which is used for Let's Encrypt and security.txt
RewriteRule (^|/)\.(?!well-known\/) index.php [L]

# block text files in the content folder from being accessed directly
RewriteRule ^content/(.*)\.(txt|md|mdown)$ index.php [L]

# block all files in the site folder from being accessed directly
# except for requests to plugin assets files
RewriteRule ^site/(.*) index.php [L]

# Enable authentication header
# SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
SetEnvIf Authorization "^(Basic YXBpQGhoZXJvbGQuZGU=.*)" HTTP_AUTHORIZATION=$1

# block direct access to kirby and the panel sources
RewriteRule ^kirby/(.*) index.php [L]

# make site links work
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*) index.php [L]

</IfModule>

# compress text file responses
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE text/javascript
AddOutputFilterByType DEFLATE application/json
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/x-javascript
</IfModule>

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

Looks good. Could you please try what‘s in $_SERVER['HTTP_AUTHORIZATION'] both for the Panel user and the API user (dump that variable somewhere in config.php for example)?

It’s the encoded user name. The strings don’t exactly match the ones I encoded. The first part is identical, but then it’s longer.
However, if I use the string that $_SERVER['HTTP_AUTHORIZATION'] echoes for the api-user in the SetEnvIf-rule, the error still remains.

Maybe you explicitly need to unset the env variable for non-API users:

# only pass the Authorization header for the API user
<If "req('Authorization') =~ '^Basic YXBpQGhoZXJvbGQuZGU'">
  SetEnvIf Authorization "(.+)" HTTP_AUTHORIZATION=$1
</If>
<Else>
  # unset the Authorization value for other users
  SetEnv HTTP_AUTHORIZATION
</Else>