[GH-ISSUE #77] What is the purpose of having both username and password? #29

Open
opened 2026-03-13 15:25:07 +03:00 by kerem · 9 comments
Owner

Originally created by @rmbolger on GitHub (Apr 29, 2018).
Original GitHub issue: https://github.com/acme-dns/acme-dns/issues/77

In the current acme-dns design, each subdomain registration generates a random username and password to authenticate with when updating records. But why have both a username and password when a single password or api token value would do?

In most authentication setups, you have both because the username can be exposed on user facing UI and is considered more or less public info. But in this architecture, the username is kept secret for the duration of its lifetime. So it's really just like you have two passwords that have to be used together. So why not combine them into one value and reduce the complexity a bit?

Originally created by @rmbolger on GitHub (Apr 29, 2018). Original GitHub issue: https://github.com/acme-dns/acme-dns/issues/77 In the current acme-dns design, each subdomain registration generates a random username and password to authenticate with when updating records. But why have both a username and password when a single password or api token value would do? In most authentication setups, you have both because the username can be exposed on user facing UI and is considered more or less public info. But in this architecture, the username is kept secret for the duration of its lifetime. So it's really just like you have two passwords that have to be used together. So why not combine them into one value and reduce the complexity a bit?
Author
Owner

@joohoi commented on GitHub (Apr 29, 2018):

But why have both a username and password when a single password or api token value would do?

This is the model I began the project with, and wanted to have a bit leeway in implementing features along the way.

But in this architecture, the username is kept secret for the duration of its lifetime. So it's really just like you have two passwords that have to be used together. So why not combine them into one value and reduce the complexity a bit?

This is a good idea, as the design has stabilized and there really is no reason to have both of them. This however is a backward compatibility breaking change, and I'm being careful not to break things for existing users. There are a few more breaking changes in my wishlist however, and I would see this being implemented and released in conjunction with those.

<!-- gh-comment-id:385273903 --> @joohoi commented on GitHub (Apr 29, 2018): > But why have both a username and password when a single password or api token value would do? This is the model I began the project with, and wanted to have a bit leeway in implementing features along the way. > But in this architecture, the username is kept secret for the duration of its lifetime. So it's really just like you have two passwords that have to be used together. So why not combine them into one value and reduce the complexity a bit? This is a good idea, as the design has stabilized and there really is no reason to have both of them. This however is a backward compatibility breaking change, and I'm being careful not to break things for existing users. There are a few more breaking changes in my wishlist however, and I would see this being implemented and released in conjunction with those.
Author
Owner

@rmbolger commented on GitHub (Apr 29, 2018):

The backwards compatibility point is definitely important. Good to know I wasn't missing anything else though.

<!-- gh-comment-id:385277873 --> @rmbolger commented on GitHub (Apr 29, 2018): The backwards compatibility point is definitely important. Good to know I wasn't missing anything else though.
Author
Owner

@Ajedi32 commented on GitHub (May 7, 2018):

A good first step might be to mark the username value as deprecated in the documentation and start ignoring the X-Api-User header, relying only on X-Api-Key. That way existing clients will continue to work, while updated clients can just pretend the username value doesn't exist.

<!-- gh-comment-id:387095406 --> @Ajedi32 commented on GitHub (May 7, 2018): A good first step might be to mark the username value as deprecated in the documentation and start ignoring the `X-Api-User` header, relying only on `X-Api-Key`. That way existing clients will continue to work, while updated clients can just pretend the username value doesn't exist.
Author
Owner

@Ajedi32 commented on GitHub (Aug 3, 2018):

Actually, I just looked a bit deeper at the code and it seems this is a bit trickier than I thought. The API keys are all salted and hashed in the database, so it's not feasible for the API to just accept a key without a username and look up the user that way. (You could do it, but it would involve computing a Bcrypt hash for every single user in the database every time you do a lookup.)

In light of this discovery, I'm no longer certain that getting rid of usernames is a good idea. Even aside from the backwards compatibility concerns, hashing the API keys seems like a great way to protect against possible database leaks (cracking the 40-character random passwords ACME-DNS generates is next to impossible), and switching to a key-only authentication system would necessitate getting rid of that.

<!-- gh-comment-id:410276106 --> @Ajedi32 commented on GitHub (Aug 3, 2018): Actually, I just looked a bit deeper at the code and it seems this is a bit trickier than I thought. The API keys are all salted and hashed in the database, so it's not feasible for the API to just accept a key without a username and look up the user that way. (You _could_ do it, but it would involve computing a Bcrypt hash for every single user in the database every time you do a lookup.) In light of this discovery, I'm no longer certain that getting rid of usernames is a good idea. Even aside from the backwards compatibility concerns, hashing the API keys seems like a great way to protect against possible database leaks (cracking the 40-character random passwords ACME-DNS generates is next to impossible), and switching to a key-only authentication system would necessitate getting rid of that.
Author
Owner

@Ajedi32 commented on GitHub (Aug 3, 2018):

Although... hashing the API keys doesn't necessarily require a salted hash. In fact, since we're using long, randomly-generated keys salting isn't really necessary, nor is key stretching.

New (somewhat more complicated) transition plan:

  • New API keys are hashed with SHA-256 in the database.
  • Authenticated requests for existing accounts automatically trigger a migration, changing the API key from being hashed with Bcrypt to one hashed with SHA-256.
  • Authenticated API requests that don't include a username trigger a lookup by the SHA-256 hash of the API key.
  • After >3 months of running an updated ACME-DNS server, server administrators may choose to discard old accounts which haven't been upgraded to the new API key version. Those accounts are likely unused, as any Let's Encrypt certificates they might have been used to renew have already expired.

After 3 months of running the new server, this would result in basically the same outcome mentioned in my previous comment, where existing clients will continue to work while updated clients can just pretend the username value doesn't exist.

<!-- gh-comment-id:410294939 --> @Ajedi32 commented on GitHub (Aug 3, 2018): Although... hashing the API keys doesn't necessarily require a salted hash. In fact, since we're using long, randomly-generated keys salting isn't really necessary, nor is key stretching. New (somewhat more complicated) transition plan: * New API keys are hashed with SHA-256 in the database. * Authenticated requests for existing accounts automatically trigger a migration, changing the API key from being hashed with Bcrypt to one hashed with SHA-256. * Authenticated API requests that don't include a username trigger a lookup by the SHA-256 hash of the API key. * After >3 months of running an updated ACME-DNS server, server administrators may choose to discard old accounts which haven't been upgraded to the new API key version. Those accounts are likely unused, as any Let's Encrypt certificates they might have been used to renew have already expired. After 3 months of running the new server, this would result in basically the same outcome mentioned in my previous comment, where existing clients will continue to work while updated clients can just pretend the username value doesn't exist.
Author
Owner

@rmbolger commented on GitHub (Aug 3, 2018):

Sounds good to me.

<!-- gh-comment-id:410311763 --> @rmbolger commented on GitHub (Aug 3, 2018): Sounds good to me.
Author
Owner

@Yannik commented on GitHub (Jan 9, 2019):

@Ajedi32 Are you going to open a PR for your proposal? :-)

<!-- gh-comment-id:452762753 --> @Yannik commented on GitHub (Jan 9, 2019): @Ajedi32 Are you going to open a PR for your proposal? :-)
Author
Owner

@Ajedi32 commented on GitHub (Jan 9, 2019):

@Yannik Not for the foreseeable future, unfortunately. If you want, you're welcome to take it on yourself.

<!-- gh-comment-id:452777189 --> @Ajedi32 commented on GitHub (Jan 9, 2019): @Yannik Not for the foreseeable future, unfortunately. If you want, you're welcome to take it on yourself.
Author
Owner

@tcely commented on GitHub (Mar 3, 2019):

The alternative I chose was to do the lookup using the subdomain provided for /update in the POST data. We could also choose to keep username and password, but make subdomainoptional instead. That just seemed less flexible to me because having multiple subdomain values in the account isn't possible with that approach.

This way the required data is password, subdomain, and txt which can all be provided in the POST data. No special headers are required.

<!-- gh-comment-id:469047173 --> @tcely commented on GitHub (Mar 3, 2019): The alternative I chose was to do the lookup using the `subdomain` provided for `/update` in the `POST` data. We could also choose to keep `username` and `password`, but make `subdomain`optional instead. That just seemed less flexible to me because having multiple subdomain values in the account isn't possible with that approach. This way the required data is `password`, `subdomain`, and `txt` which can all be provided in the `POST` data. No special headers are required.
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/acme-dns#29
No description provided.