this post was submitted on 25 Sep 2023
33 points (94.6% liked)

Programming

17460 readers
139 users here now

Welcome to the main community in programming.dev! Feel free to post anything relating to programming here!

Cross posting is strongly encouraged in the instance. If you feel your post or another person's post makes sense in another community cross post into it.

Hope you enjoy the instance!

Rules

Rules

  • Follow the programming.dev instance rules
  • Keep content related to programming in some way
  • If you're posting long videos try to add in some form of tldr for those who don't want to watch videos

Wormhole

Follow the wormhole through a path of communities !webdev@programming.dev



founded 1 year ago
MODERATORS
 

In an API I have there's a requirement to use an authentication method other than OAuth2 or any kind of token generation which requires making an extra HTTP call.

With this in mind there's this https://www.xml.com/pub/a/2003/12/17/dive.html
I've only stored passwords as hashes and used functions like password_verify to know the user sent the proper credentials without actually knowing the password stored in DB.
WSSE requires to encrypt with SHA1 the credentials being sent, which means the API needs to retrieve the password in plain text to recreate the digest and compare it to the one sent by the user.
So, how should I be storing this password if the code needs it to recreate the hash?
Should I have something like a master password and store them encrypted instead of hashed?


Most of the information I've found about WSSE is very very old, and some implementations have it marked as deprecated, do you know any other type of standard authentication where the user can generate the token instead of having to make an extra HTTP call?

top 24 comments
sorted by: hot top controversial new old
[–] towerful@programming.dev 35 points 1 year ago (2 children)

This seems like a XY problem. You are asking how to do X, when actually you need to be doing Y.

Your description is either too vague, or something I have never encountered.
It seems like what you have is Service A, Service B, and a client.
Service B doesn't have access to user credentials stored in Service A, but Service B has to know that the client has provided valid credentials for Service A.
At no point can the client make a request to Service A.
The client only makes requests to Service B.
And this has to be a username/password combination.

Is that right?

Implementing security tech from 2003 that is deprecated, especially considering it's SHA1 (which was deprecated 10 years ago) is not a good idea. Like, just store the password plaintext level of bad idea.
You either have to reasses what you actually want to do to ensure it is actually secure, or you are not describing your goal well (XY problem : "how do I implement WSSE on SOAP" instead of "I'm trying to do Y")

[–] JakenVeina@lemm.ee 11 points 1 year ago (1 children)

Seconded. In particular:

I have there's a requirement to use an authentication method other than ... any kind of token generation which requires making an extra HTTP call.

Why? What qualifies as an "extra" HTTP call, and why does it matter?

[–] pe1uca@lemmy.pe1uca.dev 3 points 1 year ago (3 children)

Well, an "extra HTTP call" is any call besides the one required for the client to access my API, in this case is an extra call to generate an access token.
Why does it matter? In words of the client: "making a call to generate a token is slow"

[–] paysrenttobirds@sh.itjust.works 7 points 1 year ago (1 children)

The client is not always right. Make them define "slow" in concrete comparison to the rest of the things that happen in their product and once you have a reasonable number, I think it's likely you can beat it.

[–] pe1uca@lemmy.pe1uca.dev 2 points 1 year ago

Completely agree with you, I made that comment, but most people agreed with the client '-.-

[–] JakenVeina@lemm.ee 6 points 1 year ago* (last edited 1 year ago) (1 children)

making a call to generate a token is slow

If it was happening every page load or every API call, you might have a weak argument.

Regardless, what's the general architecture of this app? You've got an HTTP API, what else? How much is under your control, versus third parties?

[–] pe1uca@lemmy.pe1uca.dev 1 points 1 year ago (1 children)

I agree, the token has a lifespan of some hours so it could be generated after that amount of time, which for a ~400ms call is not that much, but I was overruled .-.

The only thing I control is the API, the client's implementation is outside of my control (although I know is a backend service).

[–] JakenVeina@lemm.ee 4 points 1 year ago

Okay, so you're building an API that another server needs to auth with? If the opposing side is a server, a pre-shared PKI cert ought to work. If the opposing side is a potentially-untrustworth client application, the truth is there's nothing that's going to fit such a simple definition of "extra". The back and forth it takes to establish token exchange is not "extra" is the cost you have to pay to get security.

[–] frezik@midwest.social 1 points 1 year ago

Are there a limited number of clients hitting the API? Would client side TLS certs be an option?

[–] pe1uca@lemmy.pe1uca.dev 1 points 1 year ago (3 children)

Based on the title you're right, I asked about how to do X when probably I need to do Y, but the first and last paragraphs mention what's my requirement: a for of authentication which doesn't require to make an extra HTTP call to generate a token.

And what I mean by this is OAuth specifies the client needs to request an access token and an optional refresh token to the authorization server, afterwards the access token can be sent to the resource server (in this case my API), if the token expires the client can make another request to the authorization server with the refresh token.
Each call to the authorization server is that "extra http call" I mentioned.

Currently the only solution I found which seemed somewhat secure was WSSE, but again, I've only worked with OAuth2 and hashing passwords (or even better, using a dedicated service like keycloak), so I'm not sure what's the best option to store the data it requires or if there's a better solution.

I don't know how to be more clear, is there a way to authenticate a client to the resource server (my API) without making the client call endpoints to generate the tokens? Is there a way for the client to generate their own tokens and for me to validate them?

[–] towerful@programming.dev 3 points 1 year ago* (last edited 1 year ago)

As for client side token generation...
Never trust the client.

Say you hash the password client side. At this point, you have to have static salt (which can be extracted from clients), and the hashed result becomes the password.
All of this greatly weakens the security.

If the client sends a username, and the server returns a salt, then it's a bit more secure. At least this way the salt can be randomly generated for each user.
But, it's an extra API call.

You could use the username as the salt. This makes things a bit better, but you open yourself to being rainbow-tabled for usernames like "admin". Also, the salt doesn't change when a password is updated.

Here's a SE post that kinda pertains to what you want:
https://security.stackexchange.com/questions/93395/how-to-do-client-side-hashing-of-password-using-bcrypt

This one has a section on client side hashing:
https://security.stackexchange.com/questions/211/how-to-securely-hash-passwords/31846#31846


Edit:

Client side key generation isn't worth it. There aren't any good implementations.
I think one of the answers linked above alludes to the following solution:

Do a double salt:

Username & password get bcrypted (or similar) together in the client. The username-as-salt reduces the parallel of brute force attacks to a single user (ie if an attacker has a bunch of hashes of different passwords for the same user, they can brute force them all at the same time - they know the salt)

Username and hash is sent along with the request (the hash essentially becomes the new password without leaking it in plaintext in server logs, gateways, proxies etc.).

The server then retrieves the users salt from the database, hashes it again using the appropriate salt.

This way, at least the data at rest is fairly well protected. But I'm not a crypto guy. I have learned to follow the herd when it comes to authentication and security.
And I don't think there is actually a decent way to do this that actually provides the kind of security required these days.

[–] noli@programming.dev 1 points 1 year ago

Couldn't you do something like JWT except allow the client to slap on their credentials to any initial request?

From the backend side that means that if there is no valid token, you can check the request body for the credentials. If they're not there, then it's an unauthorized request.

You're eliminating a singular request in a long period of time at the cost of adding complexity to both client and backend but if the customer wants to be silly that's their fault

[–] towerful@programming.dev 1 points 1 year ago* (last edited 1 year ago) (2 children)

Is the client a web browser used by an end user? Or is it a trusted environment?

Because if it's a trusted environment (server to server) then you could add an extra field to your user table for api_key, and an extra tokens table to your database.
Think of githubs legacy access token system (and, again, it's now legacy because it's a dated and insecure way of doing it).
Each user gets a randomly generated 16 character string as their api_key.
Then the user is given a way to create/regenerate/delete records into the tokens table: a friendly name, user id relation, and finally a randomly generated 16 character string as for the token.
You could even add some sort of token expiry date, to limit the timeframe of damage for a leaked key.

Another option for untrusted environments (egba we browser) is JWT. It's used a lot for microservices.
It's a token with a lifespan minted by your Auth server. Anyone can decode the token and inspect the payload, so it's not secure for storing passwords but it's great for storing user ID and perhaps access scopes. The token can be verified by anyone to ensure the token is authentic and hasn't been tampered with.
But only servers that know the JWT secret can mint them.
The issue with JWTs is that there is no way to revoke them. If you mint a jwt that's valid for 4 years, the only way to invalidate it is by having all servers share a list of revoked tokens - or by having all servers call back to the Auth server that minted the token (which probably maintains a revoke list) to check it's still valid. And, there is no way to "ban" a user if they still have a valid token.
Essentially the JWT is keys to the kingdom, and they are hard to get back.
Which is why they often have lifetimes of 5-30 minutes, and - you guessed it - are issued along with a refresh token.


Edit:
There is SRP (6a).
https://github.com/LinusU/secure-remote-password
All the SRP projects I can find haven't been updated in 5 years - not a good look for a security system.

The problem with all of these things is that they haven't been deemed secure enough or provide enough additional benefits for widespread adoption.
The money has gone into oauth, oidc, saml, jwt etc.

[–] pe1uca@lemmy.pe1uca.dev 1 points 1 year ago

Oh I've only used JWTs with OIDC so I didn't thought about using them directly.
It could be a good solution since the user can generate them on their own and we can validate them with the correct information (secret or public key).

About the issue of long lived or not expiring JWT, maybe a custom restriction of valid tokens with lifespans of more than X amount of minutes are rejected?
Yeah, the token could be a valid one but we could say the payload is invalid for our API.

[–] mrkite@programming.dev 1 points 1 year ago (1 children)

The issue with JWTs is that there is no way to revoke them.

Except you can have a nonce in the JWT that corresponds to a field on the server.. which is revokable.

[–] towerful@programming.dev 1 points 1 year ago

Oh, as opposed to a revocation list.
Yup, I'm an idiot

[–] MajorHavoc@lemmy.world 16 points 1 year ago (1 children)

Someone wants you to use this WSSE? I would brush up your resume and start interviewing - it sounds like that place is on track for a "we liquidated our internal IT and now pay a consulting firm" level of security event.

In the meantime, If you have to store a non-rolling API secret for your app to use, and it's going to live a long time (not regenerated), then you need to secure the entire environment that has access to that secret. Any additional local reversible encryption is just security theater.

In this scenario, you need a popular modern well supported password vault soltion. Do not attempt to roll your own. The purpose of this vault is not to protect the secret, it is to quickly reset the secret* when it inevitably eventually gets compromised.

You must do frequent tests of replacing this secret, in production, with a new one. This is not theoretical. You will eventually either test it on your own terms, or on the badguys' terms.

Good luck, you're going to need it.

[–] pe1uca@lemmy.pe1uca.dev 1 points 1 year ago (1 children)

Someone want's me to implement a way to access a resource without having to make the extra HTTP calls required by OAuth, WSSE is a possibility since I saw it had some standards to send the credentials in a secure way.
I have been reading about WSSE for less than a week '^-^

Yeah, the idea would be the tokens used to generate the digest WSSE requires will live in our secure environment, and that's the question: how is a secure environment created to store tokens/API keys of users which will be used to authenticate them into my API?
I haven't implemented this kind of stuff so I don't know what are the best practices to store this kind of sensitive data.
So, I'd need to research password vaults to store my user's secrets so I can use them to authenticate them?

I went into WSSE since sending a client id + secret seems just rewording of basic authentication and well, sending the credentials in plain text seems more insecure than sending a hash.

[–] MajorHavoc@lemmy.world 7 points 1 year ago* (last edited 1 year ago) (1 children)

Neat. I can help with some of these concepts:

  • WSSE is just a reimplantation of Basic Auth, but it has a dramatically worse security posture today, than Basic Auth. WSSE is complex and deprectaed. Basic Auth is not wonderful, but is simple and is still being patched.
  • On that note, Basic Auth is almost certainly what you want here.
  • Don't use cleartext for any of this (obviously). It need HTTPS or it'll get owned immediately.
  • Don't do an extra encryption step inside your HTTPS connection. HTTPS isn't perfect security, but if a job doesn't call for OAuth and token rotation, it also probably also doesn't call for a needless extra layer of encryption on the wire.
  • Do encrypt your data at rest.
  • None of your source code should be doing any kind of encrytion.
  • Encryption on the wire should be via HTTPS.
  • Encrytion in storage (at rest) should be via a database or disk drive setting.
  • There's no benefit to encrytping data that has already been encrypted, and there's real risks of accidentally ruining the protection of the original encryption. But mostly, additional encryption just makes life hard for your ops team while adding zero security value.

You can protect your Basic Auth password simply by storing it in cleartext where it is needed with reasonable protections

(This is again assuming your use case is actually okay for not having OAuth. If it's health data, suck it up and do real OAuth, obviously.)

Reasonable protections for your Basic Auth passwords:

  • Make sure nobody except your administration team can read the file where it is stored.
  • Have separate basic Auth passwords for every service and client. No one wants one compromised password compromising everything. This happens all the time and it's not pretty.
  • Don't ship it around your network for fun. If you have access to AWS secrets, Hashi Vault or Azure Secrets, put it there and check it out as runtime. Otherwise, just put it where it needs to be and don't try to be clever about it.
  • Don't check passwords into source control. It ends up being another avenue to accidentally share them.

In summary:

  • Use basic Auth.
  • write the password in plain text in a correctly secured file exactly where it's needed.
  • for the love of all that's holy, don't try to use WSSE.
  • use HTTPS.

Happy sailing!

Edit: Also, practice replacing the secret, ideally with automation - and preferrably do so every 90 days.

Edit 2: Make that password as long as heck and meaningless. No one needs to memorize this thing. Generate it random, long and meaningless, paste it in two places, and forget about it for 90 days.

Edit 3: Deliver this secret to your end user over the phone (spoken to a human, not a text message). Do so every 90 days. When they complain, ask them if they're interested in OAuth now.

[–] pe1uca@lemmy.pe1uca.dev 1 points 1 year ago (1 children)

Thanks for all the information and advises!

So in theory basic auth is enough when sent through HTTPS, right?
If this is the case then the user would need to handle their password and my API can keep storing just the hash.

In another comment JWT was suggested, maybe this could also be a solution?
I'm thinking the user can worry about generating and signing the token and we could only be storing the public key , which requires less strictness when handling it, this way we can validate the token has been signed by who we expect and the user will worry about the private key.

[–] MajorHavoc@lemmy.world 2 points 1 year ago

So in theory basic auth is enough when sent through HTTPS, right?

Yes. Don't put nuclear weapons, health data or huge sums of money behind it, but basic Auth has been doing a fine job for a lot of things for a long time, and HTTPS is a complete solution (until the next time it gets owned).

If this is the case then the user would need to handle their password and my API can keep storing just the hash.

Yep. The hard part is securely delivering the generated secret to them. And making sure that, the shorter and less random that secret is, the more often it gets replaced. For a lot of not-too-sensitive use cases, a phone call and a long random secret will do the job.

In another comment JWT was suggested, maybe this could also be a solution?

JWT is a fantastic solution, and probably the first thing you want to upgrade to if your use case needs more than Basic Auth.

I'm thinking the user can worry about generating and signing the token and we could only be storing the public key , which requires less strictness when handling it, this way we can validate the token has been signed by who we expect and the user will worry about the private key.

That makes sense. Note that many popular JWT libraries will do a lot of that for you.

[–] OneCardboardBox@lemmy.sdf.org 7 points 1 year ago

Granted, security isn't much my background, but that algorithm basically sounds like a TOTP, so I'd look into how people protect those secrets. You'd generally use a kind of vault/secrets storage. Also, whatever authentication secret that the API uses should be independent from the password to any user account, such that it can be easily revoked in case of a leak.

[–] mrkite@programming.dev 5 points 1 year ago

Here's a simple approach:

  • Basic auth via a custom header, like X-Auth
  • JWT auth on Authorization header
  • uuid on the JWT (as a claim) that gets stored temporarily (until it expires) to allow the server to revoke the token

Initial request -> server looks for Authorization header, falls back to X-Auth header -> generates JWT and sends back to client in Authorization header (or whatever makes sense)

Subsequent request -> server looks for Authorization header -> checks JWT against revocation database/table and that it isn't expired

Subsequent request with expired token -> server returns 401, client retries using X-Auth header -> server sends back JWT on Authorization header -> client updates locally-stored JWT for future requests

There are probably ways to make this more standard or optimal, but this is a simple approach.