[GH-ISSUE #1419] declarative users are unable to login-in, subscribe #999

Closed
opened 2026-05-07 00:29:27 +02:00 by BreizhHardware · 7 comments

Originally created by @jkummerow on GitHub (Aug 8, 2025).
Original GitHub issue: https://github.com/binwiederhier/ntfy/issues/1419

🐞 Describe the bug
Declaratively created users are unable to log-in or subscribe to protected topics.

Definition via env variables in docker compose according to example in the docs, see https://docs.ntfy.sh/config/#example-config

Login fails with Login failed: Invalid username or password

💻 Components impacted

ntfy server 2.14.0 on docker compose
running on linux 6.12 for arm64

💡 Screenshots and/or logs

log entry:

2025/08/08 15:54:49 TRACE Authentication of user failed (3) (error=crypto/bcrypt: hashedSecret too short to be a bcrypted password, tag=user_manager, user_name=phil)

ntfy access:

user phil (role: admin, tier: none, server config)
- read-write access to all topics (admin role)
user * (role: anonymous, tier: none)
- no topic-specific permissions
- no access to any (other) topics (server config)
Originally created by @jkummerow on GitHub (Aug 8, 2025). Original GitHub issue: https://github.com/binwiederhier/ntfy/issues/1419 :lady_beetle: **Describe the bug** Declaratively created users are unable to log-in or subscribe to protected topics. Definition via env variables in docker compose according to example in the docs, see <https://docs.ntfy.sh/config/#example-config> Login fails with `Login failed: Invalid username or password` :computer: **Components impacted** ntfy server 2.14.0 on docker compose running on linux 6.12 for arm64 :bulb: **Screenshots and/or logs** <!-- If applicable, add screenshots or share logs help explain your problem. To get logs from the ... - ntfy server: Enable "log-level: trace" in your server.yml file - Android app: Go to "Settings" -> "Record logs", then eventually "Copy/upload logs" - web app: Press "F12" and find the "Console" window --> log entry: ``` 2025/08/08 15:54:49 TRACE Authentication of user failed (3) (error=crypto/bcrypt: hashedSecret too short to be a bcrypted password, tag=user_manager, user_name=phil) ``` ntfy access: ``` user phil (role: admin, tier: none, server config) - read-write access to all topics (admin role) user * (role: anonymous, tier: none) - no topic-specific permissions - no access to any (other) topics (server config) ``` <!--:crystal_ball: **Additional context**--> <!-- Add any other context about the problem here. -->
BreizhHardware 2026-05-07 00:29:27 +02:00
  • closed this issue
  • added the
    🪲 bug
    label
Author
Owner

@binwiederhier commented on GitHub (Aug 8, 2025):

Can you share your config?

My gut tells me that your configuration somehow evaluates the $2a and $.... in the bcrypt hash, or that you don't actually put a bcrypt hash in the config.

<!-- gh-comment-id:3168479302 --> @binwiederhier commented on GitHub (Aug 8, 2025): Can you share your config? My gut tells me that your configuration somehow evaluates the `$2a` and `$....` in the bcrypt hash, or that you don't actually put a bcrypt hash in the config.
Author
Owner

@Oxidela commented on GitHub (Aug 8, 2025):

Short answer: Try leaving in the trailing equals in the basic auth header when making api requests.

Long answer:

I was running into something similar "Authentication failed" error when making calls with users with only "write-only" access to their topics.

Below solved the "Authentication failed" error when using api/fetch requests:

I found the formatting for making requests through curl/javascript from trial and error that works with the current v2.14.0:

curl -i -H 'Accept:application/json' -u '<declared username in server.yml>:<raw password>' -d "<message body>" <server>/<my_topic>

With javascript fetch, the two changes was add "Accept: application/json" (prior version worked without this header), and including the trailing equals of the B64 hash:

fetch('https://<server>/<mytopic>', {
  method: 'POST',
  headers: {
    Accept: 'application/json',
    Authorization: 'Basic <b64hash>=='
  },
  body: 'Backup successful'
})

Notes:
User/Passwords are valid when attempting to login to the web UI
bcrypt hashes were generated by "ntfy user hash" and verified by third party bcrypt hasher
api/fetch calls were working in the prior 2.13 release (i.e. B64URL encoded user:pass) and "application/json" header being omitted

<!-- gh-comment-id:3169019451 --> @Oxidela commented on GitHub (Aug 8, 2025): Short answer: Try leaving in the trailing equals in the basic auth header when making api requests. Long answer: I was running into something similar "Authentication failed" error when making calls with users with only "write-only" access to their topics. Below solved the "Authentication failed" error when using api/fetch requests: I found the formatting for making requests through curl/javascript from trial and error that works with the current v2.14.0: ```curl -i -H 'Accept:application/json' -u '<declared username in server.yml>:<raw password>' -d "<message body>" <server>/<my_topic>``` With javascript fetch, the two changes was add "Accept: application/json" (prior version worked without this header), and including the trailing equals of the B64 hash: ``` fetch('https://<server>/<mytopic>', { method: 'POST', headers: { Accept: 'application/json', Authorization: 'Basic <b64hash>==' }, body: 'Backup successful' }) ``` Notes: User/Passwords are valid when attempting to login to the web UI bcrypt hashes were generated by "ntfy user hash" and verified by third party bcrypt hasher api/fetch calls were working in the prior 2.13 release (i.e. B64URL encoded user:pass) and "application/json" header being omitted
Author
Owner

@jkummerow commented on GitHub (Aug 8, 2025):

Can you share your config?

My gut tells me that your configuration somehow evaluates the $2a and $.... in the bcrypt hash, or that you don't actually put a bcrypt hash in the config.

For testing purposes, I used the exact same config from the docs:

services:
  ntfy:
    image: binwiederhier/ntfy
    restart: unless-stopped
    environment:
      NTFY_BASE_URL: http://ntfy.example.com
      NTFY_CACHE_FILE: /var/lib/ntfy/cache.db
      NTFY_AUTH_FILE: /var/lib/ntfy/auth.db
      NTFY_AUTH_DEFAULT_ACCESS: deny-all
      NTFY_AUTH_USERS: 'phil:$2a$10$YLiO8U21sX1uhZamTLJXHuxgVC0Z/GKISibrKCLohPgtG7yIxSk4C:admin'
      NTFY_BEHIND_PROXY: true
      NTFY_ATTACHMENT_CACHE_DIR: /var/lib/ntfy/attachments
      NTFY_ENABLE_LOGIN: true
    volumes:
      - ./:/var/lib/ntfy
    ports:
      - 80:80
    command: serve

Trying to log into the web UI with some made up password, which is obviously incorrect. But given the trace hashedSecret too short I think there is an issue parsing the bcrypt hash.
Ofc I already tested with a user and hash created with ntfy user hash so that the entered user/password is indeed valid. Same error.

<!-- gh-comment-id:3169105339 --> @jkummerow commented on GitHub (Aug 8, 2025): > Can you share your config? > > My gut tells me that your configuration somehow evaluates the `$2a` and `$....` in the bcrypt hash, or that you don't actually put a bcrypt hash in the config. For testing purposes, I used the exact same config from the docs: ``` services: ntfy: image: binwiederhier/ntfy restart: unless-stopped environment: NTFY_BASE_URL: http://ntfy.example.com NTFY_CACHE_FILE: /var/lib/ntfy/cache.db NTFY_AUTH_FILE: /var/lib/ntfy/auth.db NTFY_AUTH_DEFAULT_ACCESS: deny-all NTFY_AUTH_USERS: 'phil:$2a$10$YLiO8U21sX1uhZamTLJXHuxgVC0Z/GKISibrKCLohPgtG7yIxSk4C:admin' NTFY_BEHIND_PROXY: true NTFY_ATTACHMENT_CACHE_DIR: /var/lib/ntfy/attachments NTFY_ENABLE_LOGIN: true volumes: - ./:/var/lib/ntfy ports: - 80:80 command: serve ``` Trying to log into the web UI with some made up password, which is obviously incorrect. But given the trace `hashedSecret too short` I think there is an issue parsing the bcrypt hash. Ofc I already tested with a user and hash created with `ntfy user hash` so that the entered user/password is indeed valid. Same error.
Author
Owner

@binwiederhier commented on GitHub (Aug 8, 2025):

This exact same hash works for me; the password matching this hash is "phil":

NTFY_AUTH_USERS='phil:$2a$10$YLiO8U21sX1uhZamTLJXHuxgVC0Z/GKISibrKCLohPgtG7yIxSk4C:user' ntfy -d serve 
2025/08/08 15:56:14 INFO Listening on [::]:8080[http], ntfy v2.14.0-next, log level is DEBUG (tag=startup)
2025/08/08 15:56:14 DEBUG Waiting until 2025-08-09 00:00:00 +0000 UTC to reset visitor stats (tag=resetter)

$ ntfy user list
user phil (role: user, tier: none, server config)
- no topic-specific permissions
user * (role: anonymous, tier: none)
- no topic-specific permissions
- no access to any (other) topics (server config)

$ curl -su phil:phil http://localhost:8080/v1/account | jq '[.username,.provisioned]'
[
  "phil",
  true
]

I also checked with the bcrypt online thingy and with the web app

Image Image

If you join Discord, it's easier to debug back and forth.

<!-- gh-comment-id:3169148230 --> @binwiederhier commented on GitHub (Aug 8, 2025): This exact same hash works for me; the password matching this hash is "phil": ``` NTFY_AUTH_USERS='phil:$2a$10$YLiO8U21sX1uhZamTLJXHuxgVC0Z/GKISibrKCLohPgtG7yIxSk4C:user' ntfy -d serve 2025/08/08 15:56:14 INFO Listening on [::]:8080[http], ntfy v2.14.0-next, log level is DEBUG (tag=startup) 2025/08/08 15:56:14 DEBUG Waiting until 2025-08-09 00:00:00 +0000 UTC to reset visitor stats (tag=resetter) $ ntfy user list user phil (role: user, tier: none, server config) - no topic-specific permissions user * (role: anonymous, tier: none) - no topic-specific permissions - no access to any (other) topics (server config) $ curl -su phil:phil http://localhost:8080/v1/account | jq '[.username,.provisioned]' [ "phil", true ] ``` I also checked with the bcrypt online thingy and with the web app <img width="1074" height="685" alt="Image" src="https://github.com/user-attachments/assets/8c3b19d9-a5a5-4ebd-b6b7-e1fb70d53c30" /> <img width="934" height="832" alt="Image" src="https://github.com/user-attachments/assets/a9dcf4d7-a808-4f73-831f-8b0eaf0d9790" /> If you join Discord, it's easier to debug back and forth.
Author
Owner

@jkummerow commented on GitHub (Aug 9, 2025):

After some digging and testing, I found the reason.
Somehow, the $-sign in the bcrypt hash was the issue. When you double it, everything works.

'phil:$2a$10$YLiO8U21sX1uhZamTLJXHuxgVC0Z/GKISibrKCLohPgtG7yIxSk4C:admin' -> 'phil:$$2a$10$$YLiO8U21sX1uhZamTLJXHuxgVC0Z/GKISibrKCLohPgtG7yIxSk4C:admin'

Single/double quote doesn't make a difference.

This is the config:

services:
  ntfy:
    image: binwiederhier/ntfy
    restart: unless-stopped
    environment:
      NTFY_BASE_URL: http://ntfy.example.com
      NTFY_CACHE_FILE: /var/lib/ntfy/cache.db
      NTFY_AUTH_FILE: /var/lib/ntfy/auth.db
      NTFY_AUTH_DEFAULT_ACCESS: deny-all
      NTFY_AUTH_USERS: 'phil:$$2a$10$$YLiO8U21sX1uhZamTLJXHuxgVC0Z/GKISibrKCLohPgtG7yIxSk4C:admin'
      NTFY_BEHIND_PROXY: true
      NTFY_ATTACHMENT_CACHE_DIR: /var/lib/ntfy/attachments
      NTFY_ENABLE_LOGIN: true
      NTFY_LOG_LEVEL: trace
    volumes:
      - ./:/var/lib/ntfy
    ports:
      - 8088:80
    command: serve
<!-- gh-comment-id:3170541162 --> @jkummerow commented on GitHub (Aug 9, 2025): After some digging and testing, I found the reason. Somehow, the $-sign in the bcrypt hash was the issue. When you double it, everything works. `'phil:$2a$10$YLiO8U21sX1uhZamTLJXHuxgVC0Z/GKISibrKCLohPgtG7yIxSk4C:admin'` -> `'phil:$$2a$10$$YLiO8U21sX1uhZamTLJXHuxgVC0Z/GKISibrKCLohPgtG7yIxSk4C:admin'` Single/double quote doesn't make a difference. This is the config: ``` services: ntfy: image: binwiederhier/ntfy restart: unless-stopped environment: NTFY_BASE_URL: http://ntfy.example.com NTFY_CACHE_FILE: /var/lib/ntfy/cache.db NTFY_AUTH_FILE: /var/lib/ntfy/auth.db NTFY_AUTH_DEFAULT_ACCESS: deny-all NTFY_AUTH_USERS: 'phil:$$2a$10$$YLiO8U21sX1uhZamTLJXHuxgVC0Z/GKISibrKCLohPgtG7yIxSk4C:admin' NTFY_BEHIND_PROXY: true NTFY_ATTACHMENT_CACHE_DIR: /var/lib/ntfy/attachments NTFY_ENABLE_LOGIN: true NTFY_LOG_LEVEL: trace volumes: - ./:/var/lib/ntfy ports: - 8088:80 command: serve ```
Author
Owner

@binwiederhier commented on GitHub (Aug 9, 2025):

https://chatgpt.com/s/t_68971702196c8191a3b6839e9db4edd5

Oh my, that's fun. Turns out that docker compose does variable substitution, just like I initially suspected. The fact that it didn't fail at start-up is probably because $2a is not a valid variable in docker compose, so it didn't replace that one, but only the second $....

I will

  • Fix the docs for docker compose and add a warning
  • add some code to validate the bcrypt hash

Thanks for investigating.

<!-- gh-comment-id:3170549877 --> @binwiederhier commented on GitHub (Aug 9, 2025): https://chatgpt.com/s/t_68971702196c8191a3b6839e9db4edd5 Oh my, that's fun. Turns out that docker compose does variable substitution, just like I initially suspected. The fact that it didn't fail at start-up is probably because `$2a` is not a valid variable in docker compose, so it didn't replace that one, but only the second `$...`. I will - Fix the docs for docker compose and add a warning - add some code to validate the bcrypt hash Thanks for investigating.
Author
Owner

@binwiederhier commented on GitHub (Aug 9, 2025):

Fixed in main, and in the docs. Thanks 🙏

<!-- gh-comment-id:3171899835 --> @binwiederhier commented on GitHub (Aug 9, 2025): Fixed in main, and in the docs. Thanks 🙏
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/ntfy#999
No description provided.