0
b2plane
1y

Lets say i have to send an email to the user when:

- user forgot password (email sent with a token to verify the user owns that email, and token identifies for which user is this link valid)

- email verification (email sent with a token to verify the user who just registered, where this token uniquely is generated for each newly registered user)

- etc

Notice how both of these cases include the same shit:
- sending emails
- generating unique tokens
- attaching each record to individual user

Does this mean i should pack this up in 1 single model in the database and differentiate which type of email it is over an enum (EMAIL_CONFIRM, FORGOT_PASSWORD etc)?

Or should these shits each have a different model and thus different tables in database?

Comments
  • 1
    What do you mean by "model"?

    You have:
    - 1x model: user

    - 4x use cases:
    - send email address verification email
    - verify email address
    - send pw reset email
    - reset pw

    - 2x email DTOs (w/ email templates and translations)

    - 1x message queue for your mailer
  • 3
    good rant
  • 1
    How about not storing anything in the database and use something like jwt?
    You could create a jwt token with the necessary stuff you'll need and an expiration, send a link to the e-mail, the link posts the jwt token to some api or whatever and that's it. Boom saved database storage😎

    Otherwise, yes use the same db and just define the type what token that is
  • 0
    @j0n4s because by clicking the reset password link in email redirects you to /reset-password?token=shitfucks123

    That token (UUID) in the query parameter is uniquely generated for each user in and thats how i can identify for which user should the reset password happen.

    Since anyone can manually open /reset-password link, i also need a token
  • 0
    @devRantUser1 exactly this guy gets it. Storing a UUID token in database and returning it is fine. But using JWT as a token to reset password is unsecure especially if i have to store it in db

    Imagine if user requests to reset the password multiple times? I could return them that 1 same token or choose to recreate a new one

    What if the user has already confirmed their email and tried to do it again? By storing the token in db i can simply return an alert telling them the email has already been confirmed
  • 1
  • 0
    @SuspiciousBug

    x2 message queues. Why 1?

    1 for email verification
    1 for password reset

    Etc

    Each message different type of no-reply email = new queue. Thats how i would organize the types of emails into queues. Why?

    Because what if there are 10 requests for email verification but 1000 requests for password reset? This way i could handle it more efficiently by letting email verification happen in isolate thread by 1 queue and password reset in the other. Etc
  • 0
    @SuspiciousBug

    Why 1 model?

    By user clicking the /reset-password link in email how can you possibly identify who it is? It could be anyone. And anyone can manually type the /reset-password route in url of the site and open it. What then? How do i know for Who to reset the password?

    Thats why i need a UUID token to identify the user

    /reset-password?token=a1b2c3jriisnbfjwwooskdn
  • 0
    @SuspiciousBug

    I tested on selly.gg for example how they did it.

    Heres the url when i clicked the reset password link in my email:

    https://selly.io/users/password/...

    /users/password/edit?reset_password_token=G_MzMx4LEj1vi97xo2_6

    Exactly the token im also talking about
  • 2
    @b2plane

    It is not any less secure at all.

    Email verification is supposed to be an idempotent operation (have you looked up the word yet?). It doesn't matter if you do it 100x times. The result is the same, the user is verified.

    Password reset is not idempotent, but it's simple to implement assuming you have "updated_at" fields in your passwords table. Check the iat claim of the jwt vs the updated_at field of the password.
    If iat is earlier, it was already used.

    No need to store anything in a database, and as secure as any other token scheme. (Remember, JW *Token*)

    And no need to run any housekeeping on expired shit because checking for expired tokens is part of validation.
  • 1
    @b2plane why is it less secure and why would you need to store the jwt in the db?
  • 0
    @CoreFusionX how do i identify the user on frontend who clicked the reset password link in email, which redirects them to /reset-password on frontend, if they are not authenticated prior?
  • 2
    @b2plane

    The JWT *is* the authentication.

    User inputs their email address, id or whatever and hit reset password.

    You create a jwt with that id as sub, and reset password as aud, or as custom field, sign it and include it in your mail template, then you send the mail.

    When your backend receives the request in the mail, the token is in the URL. Grab it, verify the signature.

    If the token is valid, your user is right there in the sub claim.
  • 0
    @CoreFusionX im supposed to show jwt in url query parameter?
  • 2
    @b2plane

    You can't do anything but a plain get without headers from a link. How are you supposed to pass a token if not in the URL?

    Nothing in the token is private. You sign it with your private key and you verify it with your public key.

    Anyone forging the payload would cause the signature to be invalid, and no one can create a new valid signature without your private key.
  • 0
    @CoreFusionX so you're saying i should extract only the headers part from the jwt payload and put that in the url query parameter?

    What is the headers supposed to contain? So far my jwt headers only has

    {
    "alg": "HS256"
    }

    What else is supposed to go here?

    The url to reset the password is

    /reset-password?token=...

    What value am i supposed to extract from jwt and set it as a token in query parameter?
  • 2
    @b2plane

    No. You pass the whole token, otherwise it's pointless.

    Also, as I've told you other times, I'm not your personal stackoverflow.

    If you need to learn more about JWT and how it's used, go brush up on https://jwt.io
  • 0
    @CoreFusionX if i send the whole jwt and show it in url as query parameter wouldn't that mean anyone who can grab that token can also log into that user's account?
  • 3
    @b2plane

    If you are going to mention me please *read*, I'll repeat *READ* my posts, not just skim them.

    Anyone that gets the whole token can only do a password reset and only for the user it was meant to.

    The token is only ever sent to the user registered mail, which means it is not any more or less secure than mailing any other thing to an email address.

    No one but you (or whoever you shared your private key with; HINT: YOU DON'T) can produce a valid token. They can't modify the payload, nor resign it.

    For the rest, again, https://jwt.io
  • 0
    @CoreFusionX i read ur replies. I dont skim anything. I read jwt.io. i know how jwt works. I know no one else can get the token and its locally stored on users pc unless i do some dumb shit (which i dont)

    But i have never seen anyone put the jwt as query parameter before. Look at selly.gg, they created their own some random token which is not jwt and put it as query parameter

    I know how i can complete this with just jwt as you explained. my question is not how to implement it but how secure that would be

    I just tested how mongodb does it and this is the url they sent me to reset the password

    account.mongodb.com/account/reset/password/98f0558fdccf3d487e58c3efec31898bcade02b0e8fb1697e8cfea22b2bf9456

    Does this look like a jwt to you? No. Seems like everyone generates their own unique tokens and append it to url in order to reset the password

    You're the only one telling me that i should put a jwt here
  • 2
    @b2plane

    I'm the one telling you that JWT is *one* way of doing it, and that it isn't inherently any more or less secure than other options.

    It does have advantages in not requiring any database storage.

    If you want to do it other way, that's perfectly fine. Other methods have other advantages.
  • 2
    @b2plane you want a token for this job. You can craft your own token system, or you can use JWT, or whatever.

    I'd recommend JWT, because based on what you've been saying, I don't believe you would create a secure token system on your own accord.

    Good on you for learning, but this is a rant room, not a code bootcamp.
  • 1
    @b2plane

    As @lungdart said, you run a very serious risk of just botching any self-implemented scheme.

    Based on your post, and I quote

    "No one else can get the token".

    Wrong. No one else should get *the email with the token*. The whole point of signed JWTs is that they securely authenticate a user in an easily distributed way. Of course sharing such a token is the same as sharing access, but if you do it right, you only provide access for a short time and for specific functions.

    Also

    "It's locally stored on users' PC unless I do some dumb shit".

    It can, or it can not. Depends on use case. If used as a "session" token? Yes. You save it in a cookie and the browser sends it for you in every request.

    If used as an access token for an API, you pass it however the API requires. (And needs not be stored anywhere). In this specific case, you can't set up headers or change the method and body of a request in a hyperlink, so you use the URL (which JWT specifies is perfectly safe).
  • 1
    So yeah. I'm skeptical in your statement that you read my replies and JWT.io and you now know how JWT works.

    You may have gotten superficial notions (and that's good already), but you definitely don't know it well, and when dealing on whether something is secure or not, you show delicate gaps in your knowledge, so rolling your own security scheme is 100% doomed to fail in one way or another.
  • 0
    @lungdart @CoreFusionX

    Whst if a dumbass fuckshit user requests an email to reset password but doesnt open it for 2 months? Is the email link still supposed to be valid?

    That could fuck up security. Thats why having independent uuid token with expiry date, without conflicting with jwt, makes sense and also storing it in db
  • 3
    @b2plane go look to jwt. If you understood it, you wouldn't ask these questions.
  • 2
    @b2plane iat and exp claims. This is my last answer to you on this topic.
  • 0
    @b2plane you basically send a json which contains the email adress, an expiration time and whatever you need for completing the request. But that json is signed with your key, so no one can create this json object themselve to validate some random email adress without having your secret token. It's not less secure, it expires, you don't need to store it in a database, as you know this request can only originate from that email, because you created and signed that jwt. You don't need to run some cron job to clean old 'tokens', it just works, it does not need maintenance.
  • 0
    @j0n4s so i would use jwt both for confirming the email and resetting the password?

    If i use jwt am i supposed to save it to local storage on frontend (and therefore the user is able to login) or use it just for resetting the password?

    Because if jwt is saved in local storage then user can login without resetting the password or confirming the mail
  • 2
    @b2plane you don't need to save the token anywhere. You don't need to compare the token the user sends you to anything. You validate the signature, and if it's valid, you know all the values in the token can be trusted
  • 1
    @b2plane the jwt should not be created nor stored on the client side, only should be send in the e-mail and well then it will be in the url.
    Basically
    1. User presses password reset, enters email, sends the form to the server
    2. The server will create the jwt (with the email as a sub and and expiration time), the jwt will be send as a link to the email for example https://example.com/api/......., the server will reply with a page that says check your inbox and click on the link
    3. The user opens the email and the link, the server will receive the jwt token, checks the signature, then checks that it's not expired and then looks at the sub and sees the email and then knows for sure this email is valid and the user could only have this jwt token if they have access to the email adress, then the server responds with a page that lets the user reset the password, the user enters the new password and then sends the form to the server, the server should check again that the jwt in the url is valid and then set the password.

    The expiration of the jwt token should be not long, less than 30 minutes, but definitely longer than 5 minutes, as it may take some time to get the email open the link etc...
  • 1
    For anyone struggling with this, I highly recommend reading the OWASP "Do's and Don'ts" of resetting passwords: https://cheatsheetseries.owasp.org/...
  • 1
    @j0n4s ok that makes more sense thx for explaining step by step

    Right now i set the username as sub in jwt when logging in/registering, not email.

    If i understood you correctly i can set anything in the sub regardless if authentication system is set up to take username and not email as sub

    Therefore by generating a jwt with email in the sub i would create a unique, distinct jwt from regular authentication jwt and that way use it only for email confirmation/password reset and not for authenticating the user

    Correct?

    Hopefully i didnt write a shit explanation. I feel like i understand it but cant explain it right
  • 0
    Yes the jwt just acts as a kind of datastore, think of the fields as api parameters. Except that with just api parameters, anyone could verify the email/reset the password, thats why it is signed.
  • 1
    @b2plane one thing I'll add to my comment from yesterday, just to be sure it does not get misinterpreted, not only check if the signature is valid but check that the signature originates from your key,
Add Comment