6

I'd like to implement a password-less auth flow for my mobile app that only requires a user clicking a link in their email to log in. Similar to how Slack handles auth. I'll be using node and jwt for this implementation.

I think I've come up with a secure design, but I'm sure I'm missing something. I would love some critique from the community.

Here we go:

  • User opens the mobile app.
  • We check to see if user has a token in their local storage.
  • If they do, we add that token to their headers and send to the home page of the app.
  • Else, we prompt them to enter their email to get started
  • When they click "Submit", we POST that email address to the requestMagicLink endpoint on our server.
  • The server checks the database for a user with that email address
  • If we find a user with that email, we take the id from that user
  • If the user does not exist, we create a new user, and get that id
  • We use JWT to generate a token with the id, and our secret that expires after 1 hour
  • We send that token to the user via a link in an email.
  • Upon being clicked, that link sends a GET request to our server at the magicLogin endpoint with the token in a query param
  • We verify that the token is correct using JWT and our secret.
  • If it fails verification, we redirect the user to the screen where we prompt them with their email to get started.
  • If it's successful, we generate a new JWT token using their id, and our secret that doesn't have an expiration, then pass that back to the user in the params of a URL that redirects them to a success page in our app.
  • The app takes the token from the param and stores it in local storage until the user chooses to logout, and the user is redirected to the home page.
  • The requests to the api all now contain the token in the headers, and the user is good to go.

EDIT (adding additional thought): Something I'm considering to make it more secure: What if we split the token into 2 pieces, and send half to the mobile device and the other half to the email. That way, only the user with access to that email AND access to that specific device will be able to authenticate.

  • 4
    You should avoid using the word "auth" as it can be ambiguous whether you mean authentication or authorization. Of course it becomes clear you meant authentication by reading your context, but it takes more time for the reader to understand your question. – Daisetsu Nov 17 '18 at 18:29
  • 1
    You will want to make sure your email server uses TLS or all your emails will be bounced around mail servers in the clear. Even with TLS on your email server the recipient needs it too or email is sent clear-text. I'm guessing this is a very low security (requirement) application. Have you considered using OAuth2? It allows people to sign up using credentials like Google, Facebook, etc. – Daisetsu Nov 17 '18 at 18:32
  • This question may be a good read too security.stackexchange.com/q/197004/39616 – Daisetsu Nov 17 '18 at 18:33
  • 1
    [Changed my answer to a comment, as I read over your request for offical sources; my bad] Make sure the secret is securely and randomly generated. Also you should prevent one token being used twice to receive a permanent session. In addition, when sending the permanent token to the used, avoid putting it in the GET parameters. Lastly, make sure to invalidate sessions server-side if users log out or change their password (in the latter case, invalidate *all sessions associated with that user). – GxTruth Nov 20 '18 at 9:33
  • 1
    Worth mentioning that the central concept is no less secure than any website that allows you to reset your password via an 'forgotten password' process that sends an email to you - this just has less steps and is used every time. – Adonalsium Nov 20 '18 at 14:03
4
+25

The main concept of your flow (magic links) is one of the more well known password-less alternatives for authentication. The general consensus seems to be that the security of such schemes (as has been noted in comments) is not any worse than that of a system that allows 'forgot password' email reset mechanisms. This does make sense considering that the point of failure is now in the email part.

Email servers are generally not encrypted and mail can end up stored in insecure locations such as the user's mail client. You will also have no control over how securely the user sets up their email account. It might also be useful (since it's often compared to it) to remember how hard it is to implement a password reset securely. In the end whether or not this should worry you is really dependent on your threat model and the value of your app's data.

Sources that I can find that support magic links and other password-less schemes are mostly from vendors who have a vested interest in pushing their own implementations of this system (e.g. Auth0, okta) and there are some big names that already support it such as amazon-cognito. You can also take a look at similar questions from this stack:

Password-less authentication in web apps - How safe it is?

Passwordless login over email - security considerations

As for your custom implementation there are several concerns worth mentioning:

  • Don't create users whenever there is no existing email-address. Endpoints and web forms are regularly spammed and you'll find your DB filled with junk.
  • There might not be a need to send a token in the email and instead just send a link directly. Just make sure that the link URL is generated randomly and that again the expiration is set to disable use.
  • As mentioned above emails can easily be compromised so you should set a shorter expiration to mitigate. In most cases your users will be accessing their email directly after anyway so I would keep it in the range of 5-10 minutes.
  • After login the JWT should also have an expiration even if it's a long one. You don't really want the tokens to be valid indefinitely.

To be honest I wouldn't recommend setting this up yourself if you can avoid it; Try to find libraries that already support this functionality.

2

So what you are trying to do is to implement a new "security protocol". I have a bad feeling about this because I think this needs more research and QA than a question on stackexchange. New protocols (Auth2) or algorithms (AES) take a lot of time and effort in research before they are generally considered as "approved" or "safe".

Generally I think that mail is not the best way to share access tokens. You say, you create a token with a secret, send a link with this token as a query-parameter to an email-address, and when the user clicks on this link, he is considered as authenticated. Would you also send a cleartext password to a user and then put this password via query-parameter over the wire? I hope you wouldn't because thats really bad practice, but if I understand you correct, you want to do exactly this: send the "ticket" which provides the user access to your app in cleartext via mail. Even if it is encrypted thats not a good practice.

Also, you should not automatically "create" the new user if you receive a POST to the requestMagicLink-endpoit with an unknown mail-address: this gives an attacker the opportunity to spam your db with mail-addresses from users who don't want their mail-addresses in your app ;-)

Some other pitfalls I think about spontaneously:

  • Sending mails is always tricky - hackers can send fake mails to your users or capture and read sent mails because mails are not encrypted
  • Redirecting is always tricky - hackers can compromise a poorly implemented system to redirect your users to an compromised website under their control
  • Keep the lifetime for the process as short as possible - if the user didn't click the link in the mail after 10 minutes, refuse it
  • Never send private data via query-parameter, neither in GET-requests nor in every other method
  • Possibility of replay-attacks when a token can be used multiple times

I would recommend you to use best practice protocols and methods for authentication which are used and verified for years instead of writing your own mechanism just to prevent your user from entering a password. Otherwise, if you really want to go this way, you should corporate with professionals and spend a lot of time and effort to ensure the security of your mechanism.

  • If I understand correctly, this “secret” is, unlike a password, unique and of one-time and time-limited use. – user76284 Aug 2 at 5:45
1

Don't put access tokens/secrets in local storage

By putting the access token in local storage, it will be accessible through JavaScript. This means the token can be stolen with XSS (Cross-Site Scripting). Also, authentication will only work if JavaScript is enabled.

A malicious browser extension could extract the access token, and send it to an attacker. This is especially critical if, as you say, the access token doesn't expire.

To prevent the risk of XSS, it is better to use cookies for such tokens with the HttpOnly flag set. (You will also want to use the Secure flag, and to force HTTPS for all connections)

NOTE: When using cookies instead of local storage, you have to consider and protect yourself against CSRF (Cross-Site Request Forgery) attacks.


Useful resources:

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.