58 comments

[ 3.1 ms ] story [ 134 ms ] thread
I'm getting an ERR_CERT_INVALID, doesn't bode well for an authentication service.
That's odd. The Let's Encrypt cert is almost brand new. What was the context of this error?
Works fine for me. I got a letsencrypt certificate valid starting Saturday, December 28, 2019 and ending on Friday, March 27, 2020.
I think Slack has an extra step where it sends some token from the browser that opened the link to the Slack app. This way just opening the link is not enough. Links can be opened by the controller of the email even if they didn't make the original request. Links can also be opened by something scanning the email before the user sees them. Neither of these potentially malicious use-cases are covered by emauth.io.
Both valid points. I think the email scanner issue will be especially common. Could you explain more how the Slack browser token would work? Warning the user not to open the link if they weren't expecting it is a pretty common approach.

My initial plan was to require the user to reply to the email, which solves the scanner problem and has the benefit of not opening a new tab. This comes at the cost of being an unfamiliar flow, and requiring an extra click. What are your thoughts on that?

I ended up starting with links since it worked for me (apparently gmail and fastmail don't pre-open links) and was simpler to implement for the prototype. Also it's less dependent on the speed of the user's email server.

> Could you explain more how the Slack browser token would work?

Hi Anders — see my comment above for how do to this.

This is more or less what we do at Fleetsmith for our magic link login functionality, to defend against the threats you’ve mentioned.

We store the user’s session ID at the time they request the email be sent, and then validate it’s still the same session ID when the token (which is a parameter on the URL) is consumed.

If you’re using something like Mailchimp or SendGrid to send emails, this design also protects against a scenario where the email service is compromised by an attacker. The attacker will be able to see the emails, but will be unable to use the links to login.

This breaks the use-case where the user is on a desktop computer but checks mail on their phone.

Not too rare of a thing.

Or segments their browsing in different browsers/containers.

Yeah, working across devices is a must. I'm leaning towards the reply-to-verify flow I mentioned above. What are yall's thoughts on that?
As another commenter (gregmac) mentioned, you can still allow clicking the link from a mobile device, but only authorize the session ID of the original browser for login. The only flow that’s not possible then is to start login on one device and finish it on another, which seems pretty niche (why would the user start a login process on desktop if they wanted to login on their phone?). To clarify, it’s still possible to allow for this user flow with the design I mentioned:

1. Request magic link on desktop. 2. Click magic link on mobile. 3. Become logged in on desktop.

gregmac mentioned (correctly) the things you’d want to do in terms of UI/UX if you want to allow for this flow.

A reply based design leaves you open to attacks because you have no way of having strong security guarantees/validation on the email reply. You would have to rely on validating that the email has valid DKIM, but DKIM won’t be available 100% of the time. And without DKIM the reply could be spoofed.

> why would the user start a login process on desktop if they wanted to login on their phone?

I've run into this a lot for things like when I want to log in to a public computer, such as to print something. Having a logged-in device with you makes this much quicker.

> A reply based design leaves you open to attacks because you have no way of having strong security guarantees/validation on the email reply. You would have to rely on validating that the email has valid DKIM, but DKIM won’t be available 100% of the time. And without DKIM the reply could be spoofed.

It's just as strong as using a link, because you can verify against the message ID of the original email. And has the advantage that it's not going to be triggered by your email client without action on your part. Plus you can use DKIM when it's available as a bonus security layer.

> I've run into this a lot for things like when I want to log in to a public computer, such as to print something. Having a logged-in device with you makes this much quicker.

Again, this flow is still very much possible with a design that verifies session ID. Please re-read my last comment and gregmac’s comment carefully!

To be really clear, you can design it so that you:

(1) start the login process on a public computer, (2) click the link on a mobile device, and (3) end up logged in on the public computer. And you can do this while ensuring that the session ID is the same in steps #1 and #3.

I think I see where the confusion is coming from. emauth essentially already works the way you're describing. It validates whatever session makes the original request. Having the link isn't enough to login on its own.
I believe this also breaks in some cases on the same device. On my Android phone, when I open a link from gmail, it uses an embedded web view inside gmail to open the link, whereas the original request came from Firefox. I'm assuming there's no way for the session ID to bridge that gap, correct?
Both of these cases can be handled by having the email link validate the original session rather than the "current" session/browser.

If it happens to be on the same browser that requested the login, it will be the same session anyway, and will work as you'd expect.

If it's a different device/browser, then (using your example) the user will see a message on their phone like "Login validated, please check the original device where you initiated login" and their desktop session will now be validated. Ideally the site uses polling or a push mechanism to proactively show the right page, but the lazy way is just to show a message like "Refresh this page after clicking the login link from your email".

I think I'm missing something. How is this different than the way emauth is currently doing it?
This is actually independent of emauth's implementation, it depends on how you implement the validation workflow (what you do when you receive the token).

The naive implementation is to just say "Hey, a valid token... Ok, you're now logged in", but this suffers from the issue mentioned in a parent comment:

>>> Links can be opened by the controller of the email even if they didn't make the original request. Links can also be opened by something scanning the email before the user sees them. Neither of these potentially malicious use-cases are covered by emauth.io.

>> We store the user’s session ID at the time they request the email be sent, and then validate it’s still the same session ID when the token (which is a parameter on the URL) is consumed.

> This breaks the use-case where the user is on a desktop computer but checks mail on their phone.

So what I'm saying is:

* Store the sessionId with the token

* When you handle the validation request, mark the stored sessionId as "logged in"

This handles the "click on validation from a different device" use case, while also preventing someone who's able to intercept your email from being able to log in themselves.

Out of curiosity: Which email service is opening links itself? Yes, they scan attachments or check for domains known being malicious but opening links??
If you don't visit the link, you only have the url to go by. If you visit the link, you can analyze the contents of the page as well.
So what happens when an email scanner opens a Slack link?
After talking through this thread a bit, I realized that I'm pretty sure emauth.io already works the way you're describing. Having the link alone is not enough to login. Only the process (whether in browser, on your server, curl, or whatever) that made the original request will know that the email has been verified. The browser that opens the link simply gets a text response that says the email was verified (or failed if already expired). You can use that information however makes the most sense. For a secure application, it would be used by your server to validate a session.
Looks like email security software will automatically follow the link and mark it as verified, without me clicking on the link.
That's a great point. Apparently gmail and fastmail, which are what I use personally, don't do this, so it's not something I came up against in my testing. I'm definitely going to need to handle that.
Test with Outlook, they tend to be the most aggressive at hitting links. Gmail is based on some reputation secret sauce or possibly a random number generator. You'll also want to test sending to domains that use FireEye and similar security products.

In general: links will get followed, javascript will be executed, buttons will get clicked, and subsequent links will be spidered too.

I really love the idea of this - lightweight auth is desperately needed in the web ecosystem (Auth0 is close, but a bit too complicated and bloated).

Would be great if the service returned something like a JWT or just a signed copy of the email address verified (potentially with an expiry date). That way you can use the token itself as authentication without making network calls on the backend to verify it on each call. Not saying this is necessarily production-grade security, but could suffice for small projects where ease-of-use is more important than token revocability.

Thanks for the feedback. I totally agree about simple auth.

emauth is intended to be very barebones. On it's own it really doesn't do much. You still need to have some sort of session management on your server. What emauth saves you from is having to worry about generating/storing passwords.

Returning a JWT is an interesting idea. Can you explain more what that would look like? Obviously anything that involves consulting emauth.io for every request isn't going to work. But maybe there would be a simple way for emauth to delegate token verification to your server.

Jwt are usually signed with a private key, and the corresponding public key you need for verification is static/fetchable for dog-and-world.

Not sure why he wants you to implement another oauth server though, there are several quite good (i.e. keycloak) around at this point.

Oh that makes sense. Idk, I think that's actually a good deal simpler than oauth. With a single request you get a token that says emauth.io verifies that at X time the owner of this token had control over Y email address. Anyone can then use emauth.io's public key to verify the claim. This makes the "email whitelist" use case trivial to implement on the server. You just have to have expiration logic.
I don't see how. There are libraries to interface with these oauth solutions so you basically just send them to the corresponding server and wait for them to come back with their token , which you'll verify with said library... You're done
Do you have an example of such a library/service?
I should probably point out that I'm talking about an authentication framework which includes sessions etc. I just can't see any other way for jwts granted by your service to be useful.

If it sounded like your service already exists as an open source solution with libraries for every language... I don't believe it does, it at least I don't know about it.

You could actually implement it with a module in keycloak, but you'd need to write the code yourself. Though it shouldn't be too hard

https://www.keycloak.org/

https://www.keycloak.org/downloads.html

There are community libraries for other languages as well.

And lots of code example for custom authentication flows like

https://github.com/stianst/keycloak-experimental/tree/master...

And an example implementing the auth flow with libraries for a webservice https://github.com/houmie/sso/blob/master/src/app.py

(I'm unrelated to all links, just got them from quick Google searches)

A way it would be useful without sessions is if you have a webapp that loads a list of email addresses on startup. For each request that comes in, it checks if the request has an emauth JWT (maybe passed in a Emauth-Token header). If it does, it verifies the sig, then checks the contained email address against the whitelist. If it matches the request goes through.
Yeah, but how would you log a user out? Delete the local token? And if someone got access to the token, your account will be owned forever with no way to revoke the access. Unless you auto expire them... Now you need a flow to refresh the tokens....

If you implement jwt, you will need sessions and all other features authentication servers have... It would be way easier to implement a custom authentication flows mimicking your email auth than recreating everything else.

I think your service is good as is and introducing jwt to it would be a net-negative

But I'm just a random person from the internet, do whatever you feel like doing ;)

Sorry, I didn't notice you had responded. I think maybe you're reading too much into adding JWTs to emauth.io. It simply provides another source of information, ie that as of a certain time, emauth.io verifies that the holder of this token had control over the email address. Implementations can do whatever they want with that information, depending on their threat model. And since previously the API was returning a fairly redundant text response (status code already tells you whether verification was successful), returning the JWT doesn't interfere with the previous functionality.
Keycloak and CAS for example are popular in the Java ecosystem.

But generally any authentication server that can generate JWTs for authenticated users.

And then with any JWT lib you check the validity (expiration, signature, ...) of the token. See https://jwt.io for a list of libs.

(comment deleted)
@jeffbarg just FYI I've thought about this some more and I really like this idea. I'm planning to add it. You could have a client talk directly to emauth.io to get a JWT, then they can authenticate against any server that accepts emauth.io-formatted JWTs (probably just email and timestamp). It essentially decouples clients from servers.
Just as an update, on successful email verification emauth.io now returns signed JWTs which currently contain the email address, iss ("https://emauth.io"), and iat. They can be verified using the public key available at emauth.io/pubkey. Thanks for the idea!
Email addresses ought to be handled with care. I'm sorry but I hope few would trust any third party service with their customers/users email addresses just like that.
Valid concern. I figured it went without saying, but obviously nothing goes without saying. I added a privacy disclaimer to the bottom of the page. We don't use the emails for anything other than authentication and internal analytics for billing.
Nice that somebody is finally offering this as an service. But the service is full of deal-breakers (for me at least):

> Any given email address can only be verified a certain number of times each month.

How many times?

> If you subscribe to emauth pro for $4/month, you can make 512 verifications per month

512 verifications for $4/month is a big ask. Auth0 and Firebase have bigger free tiers, not 100% sure though.

> If you need more verifications than this (for example if you want to use emauth for a public app with many users), please contact sales at sales ...

Before you start enterprise sales, some options which are bigger than 512 would be great and I am also not sure if this type of smaller service would justify any enterprise sales. However, let us know if anyone contacted you for some enterprise-y deals.

> Also, in order to protect the email sender reputation for emauth, we aggressively blacklist ip addresses that repeatedly make requests to invalid email addresses.

Makes sense but how long are these IP blacklisted? How many requests are required to blacklist an IP? Btw, this won't help OPs email reputation, just few dozens of bounces are required to get your domain on many blacklists. How did other people solved this problem? How did Medium worked around this problem in its early days?

Biggest problem though:

Integrating something like your service is easy but building processes around creates a light lock-in and getting the stuff out again is some work. Looking at your landing page which I like because it's light and subtle, it still gives the feeling that it's hobby project and nobody knows if you are still on the market next month or next year.

Smaller problem:

No custom domain possible (or is it?), so customer might miss that your email belongs to my service.

Thanks for the feedback

>> Any given email address can only be verified a certain number of times each month.

> How many times?

Currently 10. It's a mostly arbitrary number intended to be enough for adding simple authentication to things like family photo albums, where infrequent logins and longlived tokens are more acceptable.

>> If you subscribe to emauth pro for $4/month, you can make 512 verifications per month

> 512 verifications for $4/month is a big ask. Auth0 and Firebase have bigger free tiers, not 100% sure though.

512 is a complete shot in the dark. The unfortunate truth is there's no way to get a feel for a good number until you measure usage and/or get feedback. And no one is going to give you feedback until you hit the front page so...

>> If you need more verifications than this (for example if you want to use emauth for a public app with many users), please contact sales at sales ...

> Before you start enterprise sales, some options which are bigger than 512 would be great and I am also not sure if this type of smaller service would justify any enterprise sales. However, let us know if anyone contacted you for some enterprise-y deals.

Don't disagree. But I have no data to make tiering decisions from and wanted to have at least some paid option right away.

>> Also, in order to protect the email sender reputation for emauth, we aggressively blacklist ip addresses that repeatedly make requests to invalid email addresses.

> Btw, this won't help OPs email reputation, just few dozens of bounces are required to get your domain on many blacklists.

That's good to know, thanks.

> Integrating something like your service is easy but building processes around creates a light lock-in and getting the stuff out again is some work. Looking at your landing page which I like because it's light and subtle, it still gives the feeling that it's hobby project and nobody knows if you are still on the market next month or next year.

I'm no fan of vendor lock-in, believe me. I never would have made this if I didn't need it for my other products and services. Worst case maybe the free tier is useful for a few people. The service is trivial enough that you can implement your own fairly easily. I'm sure it's already been implemented a million times. And if it comes down to me no longer being able to run the service for some reason, I'll open source it.

> No custom domain possible (or is it?), so customer might miss that your email belongs to my service.

Not currently. You can customize the email text a bit to make it clear where the email originated from.

Why does this need to be a service? I wrote a Django library to do this, all you need is the ability to send email:

https://github.com/skorokithakis/django-tokenauth

nice, how did you solve the risk of getting on blacklists in case of too many bounces?
I added per-ip rate-limiting, which weeds out bad actors reasonably well, although if someone wants to DDoS you, they can.
Ok, what kind of token do you use?
What do you mean? A random one.
I don't see it as a winner-takes-all market. If someone is using django they probably should use your library.
Sure, but why is it a service as opposed to a library? What is the advantage of a service for this? It seems to me it's just so the author can get paid, which is fair, but that's not a selling point that appeals to me, the user.
> Sure, but why is it a service as opposed to a library?

In what language? Sometimes a service is nothing more than a library where HTTP is the ABI.

Your solution is only good for Django. There are way more languages and frameworks in which there is a need to implement this kind of login.
This is cool. One improvement could be to add a redirection URL as a parameter in the request so that users land back on the app.
Thanks! One tricky thing with that is spammers could use emauth with an email list to send people (at least anyone who clicks the link) to whatever site they want.
Congratulations on allowing `"$(curl https://steve.fi/)" <steve@steve.fi>` as an email address, and not getting exploited!

(I've seen too many systems in the past where they shell out to the command-line for delivering email, I had to try it!)

Ha thanks. That's scary. Fortunately I learned at least that much in my security class.
Looks interesting and easy to integrate. We recently developed something similar as a module for the Caddy web server: https://github.com/TNO/auth-by-email

We developed it for a website that needed (simple) authenticated access for a read-only website, but we didn’t want to store the (hashed) passwords.

Additional features:

- email addresses for the whitelist can be stored as keyed hashes with the key provided at run-time. This way the whitelist is not on on disk in plaintext.

- Adminstration of the whitelist is done by email: upon entry of an unknown email address, the administrator gets an email with a link with which the new entry can be approved or denied.

- Kiosk-mode: user enters e-mail address on laptop, receives link on mobile, clicks link on link in mobile browser. Server detects that it is a different browser, gives user the option to login on session on laptop (for one day) or for session in mobile browser. Useful for giving demos.