-
Notifications
You must be signed in to change notification settings - Fork 318
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
auth.signUp() doesn't error for existing accounts - security vulnerability #1517
Comments
hey @CalebLovell this is actually a fix for a previous security issue. Previously the interface was leaking information by allowing an attacker to see whether a given email had an account or not. Now the endpoint returns a "success" response regardless of whether an account exists already or not. The metadata you see in the response is actually faux info - the user ID is random and the datetimes are set to the time that the request was made. note: this is only the case for supabase instances where AUTOCONFIRM is disabled (as per the default) hope this helps! |
Ahh, very interesting. Would be great to maybe add that to the docs somewhere, because I suspect I won't be the only one confused by it. Firebase returns an error in this situation so that is what I was expecting, but I like the faux info return as an even better solution! Thanks for the response and enjoy your weekend. |
This 'security fix' seems like 'security through obscurity' . IMHO it doesn't make sense for supabase to be opinionated about how a signup process should be handled by developers. There are many use cases where a back-end service may need to know if a user already exists and having to store an additional user profile table just to be able to figure this out seems to be an unnecessary extra step. Perhaps the 'service key' responses can be accurate while the 'app key' responds with a generic 'invalid credentials'? |
In the case where a user forgets they are already signed up with their email address, most websites will show some variation of “there’s already an account for this email address”. If a user tries to sign up with an email that already has an account associated with it, how can I tell so I can let the user know? |
I also agree that security through obscurity is not a good way to fix this as most services online do tell you if a user already exists, what they do have however is a rate limit on how many logins you can try within a certain time period to prevent brute force attacks. This fix should probably be reverted as the behaviour is unexpected and it seems to be confusing users more than anything. |
@awalias so if the user is trying to create an account, when they already have one, is there a way to let me know? |
Correct. Rate limited logins would be the correct process for this, but it's also difficult to defend against some forms of attack unless they are targeting one specific account, in which case locking that account from logging in for a duration or until an account owner performs some verification process. If an attacker is aiming to test as many accounts as possible, then the options are either cookies (easily defeated) or IP blocking (also easily defeated). There's some hybrid options out there such as behaviour analysis, but they have their own set of limitations and potential for false positives. Rate-limiting registration isn't feasible to introduce as an attacker could easily bombard the system with fake registrations and block legitimate users from registering. For ecommerce sites, this would potentially make it a haven for scalpers. At the same time, it becomes difficult for developers when they're unsure if an account exists during registration. What most online services have is a generic truthy response during the account recovery process. For example, instead of saying "An email was sent to the account we found", they'll instead say "If an account with this email exists, we have sent an email to it". I believe rate limiting per account already exists for this in Supabase. It's a difficult problem to solve because there's potential for hackers to use the sign up process to ascertain if an account exists (as opposed to testing login), but developers want to know if an account exists when a user is registering. |
Every security control can be defeated. Supabase shouldn't be responsible for solving every edge case of security for app developers and many companies have regulatory guidance on how to implement authentication flows. There should be a straight forward api available for server to server calls so that developers can decide what the proper pattern would be for themselves based on their own needs. The current Supabase position is to create an associated "user profile" table inside supabase in order to recreate missing functionality (such as basic checks to see if a user exists). The more of these types of work arounds a developer needs to create, the less compelling supabase becomes over say a vanilla postgres + hasura instance. I would also argue that for B2B applications where access is not available to the general public many of the concerns around account harvesting, fake registration etc are much less of an issue. |
Building off of this, I think the crux of this issue and what we want to know is what should an average auth flow look like, implementation-wise, when built with Supabase? The only guide available in the official documentation uses magic links "for simplicity" but doesn't really touch on how to handle any error or edge-case scenarios, which is extremely important in production. I'd just like to know how to do it properly, whichever the security measures taken are. |
Hey everyone thanks for your comments! To be clear this is not an example of security through obscurity, the red herring here might be the presence of faux info or the fact that we haven't done a good job of documenting this behavior. As long as we don't leak any info about whether a user account exists or not on any public endpoint, then the information is secure (at least as far as this specific issue is concerned). The correct solution here is to take this one step further: If a user tries to sign up who already has an existing account, we still return a "check your inbox for confirmation email" message, but we instead send them an account recovery email (which is actually just a magic link) then the developer can decide whether to direct them to a password reset page or just allow them to go about their session as normal. This solves for:
An important reason for why we decided to be "secure by default" on issues like this, is partly because of the number of users adopting our auth service. I could fairly easily take your email address, make a sign-up request to every known supabase endpoint on the internet, building up a map of all the various services that you are/are-not subscribed to. Which is quite a large privacy concern. I propose that we do several things:
As always any help on these ones would be hugely appreciated. And thanks again for the active discussion and helping improve the product 🙏. |
@naegelin I also agree with your idea that the admin endpoints should return truthful responses, if you are creating an account from the backend. We recently added edit: it looks like this already works as expected - again the issue here is lack of documentation {
data: null,
error: {
message: 'Email address already registered by another user',
status: 422
}
} |
@awalias Is it still true that the My config says: |
@awalias - There still seems to be a mismatch here that I'm trying to work through. My account is set-up to require email confirmations.
Based on what you mentioned above, it would seem that a confirmation/recovery email should be sent in both case and in the UI, one can simply direct the user to check their email. That said, I'm not sure how to handle the case where a user already signed up using that email without them getting confused on the what the password is. In the case of a confirmed user getting an account recovery email, their mental model would be the password they just entered, which would be wrong if it's different than the one actually associated with the account. |
We are hitting the exact same issue. Registered users with verified email addresses can forget they already signed up, they go through the sign up process, we show the message to check their emails, but no email arrives. Any solutions? |
Is there any update to this? We need a clear solution for letting users know if they've already signed up, like how @awalias mentioned. I can see many users getting stuck and waiting for a sign up email that will never come. |
hey! Was just discussing this issue with @hf in depth - his opinion is that since it's still possible for people to determine the existence of accounts with side-channel attacks (e.g. time how long it takes to get a response, if an email is sent it will take longer etc.) that by default we should return an error similar to how firebase does it. e.g. "This email already exists". Then the idea is to make this functionality optional to people who require more privacy controls. There is some draft documentation on the issue here: https://github.com/supabase/supabase/blob/f8682bbb095d728ee19a581170a5d972a39543e4/apps/reference/docs/guides/auth/auth-security.mdx#user-information In the meantime I will check if we can revert the default behavior to always give an error for existing accounts |
Adding a bit more color to this issue. I believe this behavior should be documented to avoid others losing a couple of hours trying to debug it. |
exactly this issue! |
@awalias Any updates on this issue? The UX is very poor without a way of letting them know that they already have an account. And if I have to pre-vet the user, then that's a poor DX. "information about whether an account exists in the system can leak even if the application returns an ambigouous message such as "If you have an account an email has been sent." This is because the final part of the request handling will need to send out an email message or SMS using a third-party system. This makes requests naturally last longer when an account exists." I don't understand this- couldn't this email or SMS process happen asynchronously so that you can return the response immediately in all cases? Alternatively, your proposed fix of sending it whether there is an account or not means it will always take the same (long) time. |
It makes sense to maintain privacy, but it doesn't make sense not to email existing users. Let's assume that my user registered with Github. If he loses access to github or wants to create a password, he will be waiting for the email forever and will think that not receiving it is an error. This is very confusing. User should be able to confirm their new form of authentication via email |
Complete protection against user enumeration seems hard to achieve with email-based logins. This is why I consider username-based logins to be an easy and practical solution to this problem. It'd be much simpler to ask users to choose a username/login that doesn't identify them, than to ask them to create an anonymous email/phone number and come back and use this "anonymous" email. Users would still need to provide their emails/SMS upon registration(to support OTP, magic links, etc...) but it'd only ask for the username upon signing in. Yes, this means that supabase would still need to have some mechanism to prevent leakage of the existence of an email if the system is to have a functionality like "Forget username? send it to email..." but I imagine that protection against such a leakage would be easy to implement by always responding in a constant time. I noticed that someone asked for this feature #903 a while ago but it was closed. I think the supabase team should reconsider this feature. Privacy is a superhigh priority and should always be approached with an opt-out model rather than an opt-in model. |
I have just discovered this behavior today and found this issue, really surprised and agree that |
I've just opened this issue https://github.com/supabase/gotrue-js/issues/769, which is related to the discussion going on here. |
it would be really helpful if you can let developers decide how to handle this instead of forcing a solution. it's always a balance between security and convenience and nothing is absolutely secure. each service has different needs and developers can choose what works the best for their use cases |
In case this helps anyone, we don't verify emails in our dev/staging environment but we do verify emails in production. Due to this, we have a
|
How is this hack: const {error, data} = await supabase.auth.signUp({email, password})
if(data?.user?.identities?.length === 0){
alert("This user already exists")
} still the acceptable solution? A hacker could also read this thread and exploit the hack to see if the user exists. This means that the "security" argument is not valid. It's almost over half a year and this issue seems stale. |
@awalias @kiwicopple hope supabase leave the freedom to developers to decide how to handle existing accounts instead of dictating a solution that is really not helpful for a lot of use cases |
Hey guys, here's an interesting finding → it appears that if you're running |
@Saranoja thank you!! That's super helpful to know. |
Keep it simple...check for an error coming back from signUp. If there was an error, something went wrong (password not right length, etc.) so display that. if there was no error, 1 of 2 things happened.
If you to check the length of the return to figure out which, ok that's fine. I chose to just return a generic message either way...something like: "New users require authentication. Please check your email. If you are already a user and forgot your password, please reset your password form the login form" With that approach, you don't tip your hat as to if the user existed or not, and you tip an existing user to try reseting their password if they forgot. I suppose on condition 1 above, where the user already exists, if you get a zero length result, you could also programmatically initiate an email indicating to the user someone tried signing up using their email address. While supabase may not do this for you, there is no stoping you from doing it in the code based on the return. |
It's been more than two years 👀. It happens all the time that users forget that they registered before... |
Same problem, this is an incredibly goofy decision to not return a basic error. At least make it configurable to turn the error on and off. |
I'm seeing it does return an error. When using the following: const { error } = await supabase.auth.signUp({ email, password }) error.message will contain "user already registered" as the error message, if the user already exists. |
Adding to the complexity, when email confirmation is enabled, using a service won't throw an error for an existing confirmed email. However, if you self-host supabase manually or via CLI, it will throw an error stating 'User already registered". |
@awalias, would it be possible to get an update on where supabase stands on this issue? The behaviour is a little strange to say the least, and it seems like there isn't a firm stance ~3 years after this has been first reported. I love the package so far, but there's the odd bug/strange behaviour here and there that would be great to get resolved. Hacking around this by checking the identities array isn't optimal. |
As mentioned here: supabase/auth-js#513 (comment), it seems to me that the latest docs have clarified the approach:
It's still not an optimal solution, but at least we can get an explicit error message by disabling the email confirmation. The error response looks like:
|
I do not get the magic link in the mailbox when I try to sign up with an already existing user? I only get the fake user data object in return. Email service is working, signup emails and sending magic link manually works without a problem. |
Bug report
Describe the bug
supabase.auth.signUp()
is not erroring for existing accounts. Right now, you can submit an existing email with any incorrect password, and supabase will return you the account metadata (without a jwt).To Reproduce
Go to this example app
Sign-up with an email and a password
Log out
Try to sign up again with the same email using any password you want. Try
asdfasdfasdf
if you want!You will get an alert saying you logged in, but you won't get a working access token. Just the email you submitted.
You can also view the request in the Network tab of the Dev Tools and see metadata about the account, like when it was created and what provider it uses.
Expected behavior
Attempting to sign up with an existing email should throw an error.
System information
The text was updated successfully, but these errors were encountered: