DEV Community

Jess Chandler
Jess Chandler

Posted on

Your thoughts on Creating a New User

To handle the case when someone becomes a user of a paid web application, "we", as developers, need to take several steps:

  • add them to the database
  • verify their email
  • send them a welcome message <- server
  • charge them
  • redirect them
  • is there something else?

Most dev guides cover "add them to the database" and "redirect them". I have created all of the steps in my application. Naively, I took all of my steps and plopped into the route handler for a new user.

As I stand on the precipice of really-this-matters development, I am wondering:

  • Does the order of operations matter? (obviously, we don't want to redirect to an authenticated area before we actually make an account and charge them, but should we wait to create the account until they click on the "confirm" email?)
  • Do you have a story from your experience?
  • If you verify email through verification service, do you still want to send a verify/confirm email email?
  • Am I missing something huge?

Oldest comments (47)

Collapse
 
ben profile image
Ben Halpern

charge them
redirect them

Just to throw a word of caution here: There are situations where redirects can be intercepted and exploited as a vulnerability. I'm not in a good position to go into length on this at the moment but if someone wants to add some insight here, please do!

P.S. I'm definitely overly cautious in this area. If anyone wants to tell me I'm wrong to think this could be a point of vulnerability, let me know.

This is a part of your process where security should be front and center. It always should be in a sense, but especially here. Of course, HTTPS and ensuring the verification process verifies what it's supposed to verify are other things to take a lot of care over.

I'd love to read the rest of what folks have to say. This was just a quick reaction.

Collapse
 
jessachandler profile image
Jess Chandler

Yes, I think caution is reasonable here. I hope to learn always from other developer approaches and patterns. Mostly, I care about why. :)

Collapse
 
_bigblind profile image
Frederik 👨‍💻➡️🌐 Creemers • Edited

Regarding the dangers of redirects, I think Ben is talking about open redirects, where your taking the URL or part of it from user input. If you don't validate this, an attacker can create a link that looks like a link to your site, but that actually takes the user to another site.

For example, it's very common to redirect users back to the page they were viewing when they logged in.

Let's say a user visits

yoursite.io/private

which requires login, so you redirect them to

yoursite.io/login*?next=/private*

After logging in, you take that next parameter from the URL and redirect them there.

If you're not careful to validate that that parameter starts with a single slash (which indicates that it is a path within your site), an attacker could give someone a link to

yoursite.io/login*?next=evil.com*

which causes your server to redirect the user to http://evil.com

Thread Thread
 
jessachandler profile image
Jess Chandler

Thanks for the description of this, Frederik! That is good to protect against. I think I've covered this risk, but I should verify.

Thread Thread
 
shalvah profile image
Shalvah

How I handle that next url: in the auth middleware, if the check fails, I store the path of the request in the session before redirecting to login. Then, in the login handler, retrieve the path from the session and redirect. That way, we're guaranteed that the path is on our site AND it can't be modified.

Thread Thread
 
jessachandler profile image
Jess Chandler

Nice. Thanks for sharing your approach. I have seen devs (in videos) have a catchall route for any route that doesn't match something on the server or client -> if any route is passed to domain.com/someroute that doesn't match an explicit route just goes to domain.com/ - but catching it in the next would ensure that they go to the right place and not just back to the homepage.

Collapse
 
ripsup profile image
Richard Orelup

Yes, generally, you still want to use a confirmation email even if you are using a verification service (we use BrightVerify at my company) because you want to make sure the person who signed up is actually the person who has access to the email and isn't someone just entering their email in to get them spammed by you or just generally pretending to be be someone else.

Collapse
 
jessachandler profile image
Jess Chandler

Thanks for your thoughts here, Richard.

Follow up question on your order of operations: Do you send the confirmation email immediately and then finish your user onboarding AFTER they respond, or do you create your new user (getting all their info) and then send the confirmation email? and then handle (somehow) deleting the user if they don't confirm?

For the process I'm thinking of, I think I'd have to wait to finish onboarding to avoid collecting money from people who don't have access to the email that they gave us. However, I am super curious how other people handle it.

Collapse
 
jochemstoel profile image
Jochem Stoel • Edited

Jess. Create the user the moment they enter your form. In the form, only ask for minimal user info needed for a valid account. Name and email for instance. Let them add all user metadata / fields such as address and phone after they fully registered/confirmed. When users decide on their own to provide their info in stead of it being mandatory during registration they have a more positive experience, communicate more open, are more likely to trust you. Youre right. Never accept payments before email is confirmed and do not even ask for payment details in the sign up form. It should not be possible to pay without verification.Let the user add this information manually after their email is verified and they are logged in. Not only will nobody be able to say they never gave you that info (after all they consciously added it manually) and it also increases the likelihood of a purchase/lead whatever you're doing. This has a number of reasons well known to marketing and lead generation minded people.
This may sound obvious but do not send the confirmation email from the sign up page script. So don't call your mail() function from the page that handles the POST data of the form. Separate the two always. Create a task of some sorts, even if it gets executed almost immediately don't let the same process be responsible.

Thread Thread
 
jessachandler profile image
Jess Chandler

Thanks for your thoughts. I was thinking that the "send confirmation email" is it's own function but is called kinda like so in the route handler -> action is createUser -> go to /api/user/new -> call createUser which ... checks to see if user already exists.then(create user).then(send confirmation email).then(go to login page or something else) - However, I'm not sure if you are saying that I should exit the createUser function and say now, go to send confirmation email.

Thread Thread
 
christiansk profile image
christiansk

I think what he means is that sending the e-mail should be done asynchronously in a separate thread/process/worker, thus not blocking your main thread. You can return a response to the user while the e-mail is still being sent in the background.

Thread Thread
 
jessachandler profile image
Jess Chandler

Thanks for your thoughts Christian!

Collapse
 
dmfay profile image
Dian Fay

It's a good idea to have an is_active flag on users. This way you can create them but not allow access until they've confirmed/paid up, and should it become necessary to disable their access later you can do it without nuking their entire account and dealing with whatever foreign key mayhem ensues.

Collapse
 
jessachandler profile image
Jess Chandler

Thanks for your comment, Dian!

I agree to having an is_active flag. Do you create users before confirmation in your apps? Do you have a flow that is like, "Hey, wanna join, enter your email" form collecting email -> create temp user with email, isactive=false, and token -> send email with button to confirm -> when user clicks on button -> go to website with token and get redirected to form collecting rest of stuff ?

Collapse
 
dmfay profile image
Dian Fay

My situation's a bit different since the stuff I work on is all enterprise software. We recently offloaded all our user management onto a single sign-on provider, but before that we did collect their information on signup and send them an activation/password reset email with a token (token hash and expiry stored in the database for verification). We didn't have a second stage, but then we weren't charging individual users.

Thread Thread
 
jessachandler profile image
Jess Chandler

Thanks for sharing your experience!

Collapse
 
bgadrian profile image
Adrian B.G.

This also covers the case when the money are refunded (for any reason), or they brake the user policy of somekind (revoke access).

Collapse
 
jochemstoel profile image
Jochem Stoel

I would like to suggest that in stead of is_active, you call this property of user just active. (Boolean) this is more correct for many reasons.

Collapse
 
dmfay profile image
Dian Fay

What are those reasons? I find is_* clear for boolean fields since it makes it easy to identify them as answering a yes-or-no question, and where naming things isn't about clarity it's really down to taste.

Thread Thread
 
jochemstoel profile image
Jochem Stoel

The word is is redundant.
User.active = true
Why would you add it? You can tell that it is yes/no by the field/property type being Boolean. It's like putting 'equals' in your property name.

Thread Thread
 
dmfay profile image
Dian Fay • Edited

It's not always obvious that something is a boolean unless you're looking at a table or class definition. Redundancy isn't universally evil: here it means you don't have to look up the type if you're just looking at usage.

Thread Thread
 
Sloan, the sloth mascot
Comment deleted
Collapse
 
jessachandler profile image
Jess Chandler

thanks for your thoughts. I'll have to look at the naming conventions.

Collapse
 
_bigblind profile image
Frederik 👨‍💻➡️🌐 Creemers • Edited

One thing you see more and more in cases like this is a publish/subscribe system of events, so different parts of your site can react to things that happen. For example, your main route handler might take care of storing the user's data and then emit a signup event that other parts of the system can then take action on.

You might have a billing subsystem that creates the user's subscription and charges them for the first time, and an emails subsystem that takes care of sending the welcome email.

This might be implemented within your codebase using a signals module built into the web framework you use, or you can use an external application like Redis pubsub or Apache Kafka

Collapse
 
jessachandler profile image
Jess Chandler

Interesting. Thanks for sharing your thoughts!

Collapse
 
jpwilliams profile image
Jack Williams

We wrote remit as a nice wrapper around RabbitMQ for exactly this purpose.

Collapse
 
jessachandler profile image
Jess Chandler

I'll check it out. Thanks!

Collapse
 
moopet profile image
Ben Sinclair

If someone's becoming a paid member of something, you might (depending on what they're paying for) need to know who they are; that's determined by the parameters of your project.

But maybe I could sign up for a site using OAuth or some other third-party service where my identity is established without having to directly submit personal information.

I pay for a VPN which (if I so desired) I could pay for by using gift certificates purchased in cash. I can make regular micropayments to charities and organisations I support without wanting to give out my email address. I sign up for services which cost money using throw-away email accounts meaning that the email is no longer connected to me in any way after 30 minutes.

I'm not saying you're wrong, I'm just saying you're generalising requiring emails to cover all "new user" scenarios - the thing I think you're missing is that your email solution should be built in such a way that it's not a hard dependency for the rest of the application, and other modules shouldn't expect to have access to an email address or to expect it to be valid.

Collapse
 
jessachandler profile image
Jess Chandler

Thanks, Ben. Stuff like this is a real concern of mine for all projects.

For my current project, the concept was that getting email from our service is something the user should want - it's how they know what happened. However, I could rethink it such that OAuth via google or facebook lets folks in and ensure that anything we would have sent via email is presented in a notification on the service. Reasonable expectation from the perspective of a user.

Collapse
 
pchinery profile image
Philip

Depending on the legal requirements, you might want to collect and store"evidence" during the signups process, i.e. to prove when and by which ip address the sign up took place and when the subscription to a newsletter or they payments became effective. Especially here in Germany it's good to have because there are people trying to sue others for privacy claims.

In that case, it might become very valuable if you can prove that a certain ip signed up or that a validation was performed from a certain email address.

The same goes for B2B VAT handling in Europe, the seller is liable, but the buyer has to declare it (even ftom the US), so you better validate the VAT and store the response.

Collapse
 
carlosmgspires profile image
Carlos Pires

VAT problems are usually solved with the billing and/or shipping address.

Collapse
 
jessachandler profile image
Jess Chandler

Thanks. I'm going to use an address checking API because trying to roll your own gets so crazy.

Collapse
 
jessachandler profile image
Jess Chandler

Ooh, good call. Thanks for the insight, Philip.

Collapse
 
carlosmgspires profile image
Carlos Pires • Edited

Yes, the order of operations does matter. It might be up to the business model, though.

If you require an email, then you should require the email to be:

  1. Valid;
  2. Real;
  3. Unique;
  4. Really owned by the user;

Additionally, if you want to restrict 1-to-1 relationship between user and email address, then you should also use a RegExp to filter out infinite gmail address combinations — otherwise any user will be able to register an infinite number of accounts.

If you require #4, then you must send a verification email. In that case, the order of operations is as follows:

  1. User submits form with all data including email;
  2. If email address MX record doesn't exist > validation error;
  3. If normalised email address already exists in DB > validation error;
  4. Create user in DB, generating confirmation token (hash) and timestamp;
  5. Send confirmation email to the user;

Assuming you require an email, you shouldn't allow the user to do anything a logged-in user can do until the user clicks the confirmation link. URLs for restricted areas should be different.

Collapse
 
jessachandler profile image
Jess Chandler

Thanks for your comments, Carlos. I think I have less risk of people creating infinite accounts with my current project because it is a paid service. However, I like the way this is laid out. What do you mean URLs for restricted areas should be different? I have basically a concept of domain/actuallstuff/:userid and the route to actuallstuff checks that user is user with userid - If you meant something else, I really want to know!

Collapse
 
carlosmgspires profile image
Carlos Pires

Sorry for the confusing remark. What I mean is that it is cleaner and safer if domain/actualstuff is only accessed by logged-in users. If you want the world to see similar information, make it domain/almostactualstuff

Thread Thread
 
jessachandler profile image
Jess Chandler

Thanks for clarification!

Collapse
 
nestedsoftware profile image
Nested Software

In case someone else hasn't brought this up: I think it's not unusual to allow people to log in provisionally into applications even before they've verified their e-mail. Their access is usually restricted in some way and will stop entirely if they haven't verified by a certain date... Depending on the nature of the app you're creating, maybe this would be worthwhile (usually it's a good idea if you want to lower the barrier to entry as much as possible and get people using your app right away).

Collapse
 
jessachandler profile image
Jess Chandler

Thanks - I think that's what P Martinez was saying (and I just read that comment). I agree that seems like a good idea that I had not previously considered. I appreciate your feedback.

Collapse
 
carlosmgspires profile image
Carlos Pires

That's what I meant by "it might be up to business rules". However, it opens up a door to all sorts of liabilities.

Collapse
 
pedro profile image
Pedro M. M. • Edited

I’ve been experimenting with multiple ways for users to sign up and I found out one, which actually increases the conversion rate. Which takes the following steps:

  1. The sign up process asks for the email. That’s it.
  2. A random password is generated and sent to the email as part of the verification process, which includes a link to the web so we ensure it’s easy for the user and he doesn’t need to type the password.
  3. When he logs in for the first time, force user to change the password so he doesn’t forget it.
  4. Then ask for the information you need. It may be a premium membership button somewhere which leads you to choose payment method, info, etc or a panel asking for shipping address when he buys for the first time. I would recommend not to ask them for information right after forcing password change since it may feel intrusive.

However this method is not suitable for all situations. I strongly recommend it when you want the email of your users to be the username and you don’t need any additional indispensable info about your users.

Collapse
 
jessachandler profile image
Jess Chandler

Thanks for this detailed response from your experience! It is very helpful.

I am reconsidering my naive flow after all of these great responses, and I really am leaning towards a structure like this. Now, I just have to figure out how to provide something meaningful to put on the page for people who are signed up but not paid members :)

Collapse
 
pedro profile image
Pedro M. M.

Yep, you nailed it! This method uses the password as the verification process itself, which makes user feel like the process is just a step away, the email and nothing more.

So the most essential step here is to be creative when asking them for more info. once they’re logged in.

Collapse
 
fcpauldiaz profile image
Pablo Díaz • Edited

What if you have a service that allows to be used without user creation and only if you want to store the results, you can then create the user? How can that be handled? Keep the info generated in memory? Use Redis?

Collapse
 
jessachandler profile image
Jess Chandler

Good questions, Pablo. That seems like a whole other can of worms. Maybe you create a temporary user in a separate database to not muckup your user database that is based only on their IP address and timestamp or something...or issue them a token that when they first start interacting with the site? I am just guessing.

Collapse
 
pedro profile image
Pedro M. M. • Edited

If you want to store results and the user is not logged in or there is no user yet I would recommend you to store everything in a local cache, i.e the client. For example, on a website I would use localstorage. Then all you need is to sync it with the server as soon as he logs in.

Probably I would never use server side to store anything related to non-logged users since there are no guarantees on the user uniqueness, but it depends on the situation. That said, you could store non-logged users data on server side once, but trying to retrieve this data from/for the same non-logged user is probably not a good idea since we can’t ensure he is the same person :)

Collapse
 
fcpauldiaz profile image
Pablo Díaz

Nice approach 😎