Skip to content
Anthony Pace Logo
  • Home
  • Schedule Consultation
  • Blog
  • Portfolio
  • Services:
    • Web Development
    • Design
    • Marketing
  • Close
  • Home
  • / Blog
  • / 2020

Month: November 2020

Posted on November 4, 2020January 11, 2025

Registration and login for web apps

web app registration resources

Getting app visitors registered can be a challenge. It is the first hurdle in any onboarding process. Popular products handle it in different ways. The available signup flows have a major impact in this step.

Some products force you to login immediately, without being able to access anything else until you do. Others let you skip logging in until you are ready. Immediate access to the software’s value leads to a greater signup rate.

Onboarding

BJJ Tracker is a fitness tracking app for martial arts. It allows full access without ever registering. If a non-registered visitor records a training session, the app inserts an anonymous account record into the ‘users’ database table. That record’s ID is saved as a cookie on the client’s device. If they ever do sign up, that account record is updated with their credentials.

Registration UI for a fitness app

Here is the markup that describes my registration form UI:

<div class="form-wrapper register-ui">
	<div class="reg-ui-email">
		<div class="reg-el-wrap">
			<form class="password-register-form">
				<input placeholder="your@email.com" type="email" class="email-input" />
			</form>
			<div class="button-section">
				<button type="button" class="register-button btn">Register <i class="fas fa-user-plus"></i></button>

			</div>
		</div>
	</div>
	<div class="reg-ui-pw">
		<div class="reg-el-wrap">
			<form class="password-register-form"> <input placeholder="password" type="password" class="password-input" name="password" /> </form>
			<div class="button-section">
			  <button type="button" class="password-button btn">Set Password <i class="fas fa-user-plus"></i></button>
			</div>
		</div>
	</div>
	<hr />
	<!--  facebook login -->
	<button type="button" class="facebook-login facebook-login-button btn">Continue with Facebook <i class="fab fa-facebook"></i></button>
	<!--  google login -->
	<button type="button" id="google-login-button" class="google-login google-login-button btn">Continue with Google <i class="fab fa-google"></i></button>

	<p class="or">or</p>
	<p class="switch switch-txt login-cta">Login</p>
</div>

I use CSS to transform the same HTML from the homepage view to the sticky bar UI.

When the app first loads, they are immediately presented with options to register or login. But, they also have full sight of the app’s most important features. If they try to record a training session, the registration UI persists as a sticky bar. The signup prompt remains prominent, but is not a road-block to exploring functionality.

This workflow ensures that:

  • The value proposition speaks for itself
  • Minimal input is required, creating the least amount of friction
  • Core features are highlighted
  • It’s quick, easy, and not overwhelming

If a user does decide to register, they have three choices: email (with a password), Google, or Facebook. The last two have greatly increased conversions for products I have developed. Social signup reduces friction and provides a quick option for logging in.

Email

Email is a simple way to sign up for any app. In this example, I make email address and password two separate steps in the registration process. Doing it that way is a UI/UX decision that I’ve found to have a positive impact on conversions (which I’ve confirmed via A/B testing).

When the “Register” button is clicked, I validate the input via JavaScript:

window.validateEmail = function(emailInput){
	var regex = /^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/;
	validEmail = regex.test(emailInput.val());
	if(validEmail){
		return true;
	}else{
		//error
		return window.inputError(emailInput, "Please use a valid email.");
	}
}

If email is not valid, a custom error message is displayed. Otherwise, I pass that value along to a back-end service:

$(".register-button").click(function(){
	window.emailRegister();
});

window.emailRegister = function(){
	var emailInput = $(".email-input");
	var email = emailInput.val();

	if(window.validateEmail(emailInput)){
		
		$.ajax({
			url: "/service-layer/user-service?method=registerEmail&email="+email,
			complete:function(response){
				var jsonResponse = JSON.parse(response.responseText);
				if(jsonResponse['duplicateEmail']){
					window.inputError(emailInput, "That email is already registered. <span class='reset-pw-cta' data='"+email+"'>Reset password?</span>");
				}else{

					$(".reg-ui-email").slideUp(function(){
						$(".reg-ui-pw").slideDown();
					});
					$(".reg-copy").text("Set a password, so you can log back in.")
					$(".or, .login-cta, .login-alt").hide();
					$("body").addClass("auth");

				}
				
			}
		});

	}
}

My server-side PHP code checks if that email address already exists. If it does, a “duplicate email” error is thrown. Otherwise, we continue through our logic tree.

If the user had logged a training session before signing up, an anonymous account will have been created and that user ID saved as a cookie. During this email registration step, we check to see if that blank record exists, so we can update it. If not, we can safely create a new account in our database.

$first = "";
$last = "";
$password = "";
$timestamp = time(); 
$authid = md5($timestamp);
$emailFound = 0;

if($number_of_rows > 0){
	
	$row = $this->row;
	$email = $row['email'];

	if(strlen($email)>0){ 
		//check if email exists for this record (not anon user)
		$emailFound = 1;
		$this->status = "Duplicate email found.";
	}else{
		//user might have anon userid already set, in which case we want to update, not insert
		$email = $this->email;
		$stmt = $conn->prepare("UPDATE `users` SET email = ?, first = ?, last = ?, password = ?, authid = ? WHERE ID = ?");	
		$stmt->execute(array($email, $first, $last, $password, $authid, $_SESSION['userid']));

	}

}else{
	$email = $this->email;
	$stmt = $conn->prepare("INSERT INTO `users` (email, first, last, password, authid) VALUES (:email,  :first, :last, :password, :authid)");	
	$stmt->bindParam(':email', $email);
	$stmt->bindParam(':first', $first);
	$stmt->bindParam(':last', $last);
	$stmt->bindParam(':password', $password);
	$stmt->bindParam(':authid', $authid);
	$stmt->execute();
	$userid = $conn->lastInsertId();
	$this->status = "User record created.";
}

$this->emailFound = $emailFound;

if($emailFound == 0){ //make sure not a duplicate email address before setting authid cookie and authorizing user
	$this->authorizeUser($authid, $email, $userid, true);
}

After that AJAX call returns, a password input field is shown to finalize the signup process.

password UI

When the password is submitted, another call is made to the service layer:

window.passwordRegister = function(){
	var passwordInput = $(".password-input");
	var password = passwordInput.val();
	if(password.length > 0){	//#TODO: better pw reqs
		var data = $(".password-register-form").serialize();
		$.ajax({
			url: "/service-layer/user-service?method=passwordRegister",
			type: "POST",
			data: data,
			complete:function(response){
				console.log(response.responseText)
				window.location = "/dashboard";
				
			}
		});
	}

}

The server hashes the password and updates the account record created in the previous step. Taken from php.net: “password_hash() creates a new password hash using a strong one-way hashing algorithm”

function registerPassword(){
	$number_of_rows = $this->number_of_rows;
	$conn = $this->connection;
	if($number_of_rows == 1){
		$this->emailFound = 1;
		$email = $this->email;
		if(isset($_POST['password']) && !empty($_POST["password"])){
			$password = $_POST['password'];
			$passwordHash = password_hash($password, PASSWORD_DEFAULT);
			$sql = "UPDATE `users` SET password = ? WHERE ID = ?"; 
			$result = $conn->prepare($sql);
			$result->execute(array($passwordHash, $_SESSION['userid']));
			$this->status = "Password added.";
			$_SESSION['usingPassword'] = true;
		}else{
			$this->status = "No password.";
			die("No password.");
		}

	}else{
		$this->emailFound = 0;
	}
}

If the user ever forgets their password, they can use the reset flow we have created.

Facebook Login

Using Facebook Login lets visitors quickly create an account without having to set a password. It’s easy, quick, and secure. The first step is creating a Facebook developer account and registering a Facebook app. The official documentation explains the steps of implementing Facebook Login.

Once that is setup, you can implement their JavaScript SDK:

window.fbAsyncInit = function() {
    FB.init({
      appId      : "XXXXX",
      cookie     : true,
      xfbml      : true,
      version    : 'v2.12'
    });
      
    FB.AppEvents.logPageView();   
  		
};

(function(d, s, id){
 var js, fjs = d.getElementsByTagName(s)[0];
 if (d.getElementById(id)) {return;}
 js = d.createElement(s); js.id = id;
 js.src = "https://connect.facebook.net/en_US/sdk.js";
 fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));

Make sure you take your app out of development mode (from your account dashboard), and use the correct production ID in your code.

Now, you’ll be able to call FB api methods to log users in and get their name and email. That information can be passed along to our back-end to create a new account.

$(".facebook-login").click(function(){
    FB.login(function(response) {
      if (response.status === 'connected') {
        // Logged into your app and Facebook.
        FB.api('/me', {fields: 'last_name, first_name, email'}, function(response) {
            console.log(response)
            var first = response.first_name || "";
            var last = response.last_name || "";
            var email = response.email || "";
            if(email.length < 1){
		window.inputError($(".email-input"), "It looks like there is no email address associated with your Facebook account. Sorry, please try registering another way.");
		$(".facebook-login").hide();
		return;
	    }
	    $.ajax({
		url: "/service-layer/user-service?method=thirdPartyAuth&third_party_source=facebook&email="+email+"&first="+first+"&last="+last,
		complete:function(response){
		    var jsonResponse = JSON.parse(response.responseText);
					
		    window.location = "/projects";
		}
	    });
        });        
      } else {
        // The person is not logged into this facebook or we are unable to tell. 
        console.log("not logged in")
      }

    }, {scope: 'public_profile,email'});
});

Google Sign-In

Logging in through Google is similar to the Facebook Login flow. It uses a JavaScript SDK to provide “access to many Google APIs”. It leverages the OAuth 2.0 protocol.

You’ll need to link to the JavaScript API SDK and provide a function for it to call once loaded:

<script async defer src="https://apis.google.com/js/api:client.js?onload=onLoadCallback"></script>

The code that fires once the library loads contains three functions . The first instantiates the Google auth2 API. It accepts a client ID, “which can be found and created in the Google Developers Console“. Next, attachSignin() uses the library’s attachClickHandler() method to link to the HTML buttons I created in my registration form UI. Finally, once the user is authorized their name and email are passed along to my back-end service.

<script>
window.onLoadCallback = function(){
 
  var googleUser = {};

  var continueWithGoogle = function() {
    gapi.load('auth2', function(){
      // Retrieve the singleton for the GoogleAuth library and set up the client.
      auth2 = gapi.auth2.init({
        client_id: 'XXXXXXX',
        cookiepolicy: 'single_host_origin',
        // Request scopes in addition to 'profile' and 'email'
        //scope: 'additional_scope'
      });
      attachSignin(document.getElementById('google-login-button'));
      attachSignin(document.getElementById('google-register-button'));
    });
  };

  function attachSignin(element) {
    auth2.attachClickHandler(element, {},
        function(googleUser) {
            var email = googleUser.getBasicProfile().getEmail();
            var first = googleUser.getBasicProfile().getGivenName();
            var last = googleUser.getBasicProfile().getFamilyName();
            googleAuth(email, first, last);
        }, function(error) {
          console.log(JSON.stringify(error, undefined, 2));
        });
  }
  function googleAuth(email, first, last){
		
	var first = first || "";
	var last = last || "";
	$.ajax({
		url: "/user-service?method=thirdPartyAuth&third_party_source=google&email="+email+"&first="+first+"&last="+last,
		complete:function(response){
			console.log(response.responseText)
			var jsonResponse = JSON.parse(response.responseText);
			window.location = "/projects";
		}
	});
   }

  continueWithGoogle();

}

</script>

Session authorization

Registering with any one of these mechanisms allows login with any of the others. Once the user has been authorized, all that is left is to set session variables and cookie their ID:

$_SESSION['auth'] = true;
$_SESSION['email'] = $email;
$_SESSION['userid'] = $userid;
setcookie("authid", $authid, time() + (86400 * 60), "/"); // 86400 = 1 day; total 60 days	

Welcome email

If this is their first time logging in, we can send them a welcome email using Amazon SES and PHPMailer. Here is the class I use to send emails from my web app:

<?php
// Report all errors
error_reporting(E_ALL);
ini_set("display_errors", 1);

//send email via amazon ses
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;


class SendEmailService{ 
	// send email services
	public $connection;
	public $status;
	public $email;
	 
	
	function __construct()
	{
		if (session_status() == PHP_SESSION_NONE) {
		    session_start();
		}

		include '/var/www/html/db-connection.php';
		$this->connection = $conn;
		$this->status = "None.";

	}

	function sendEmail($subject, $body, $altBody, $email){ 
		require '/var/www/html/PHPMailer/src/Exception.php';
		require '/var/www/html/PHPMailer/src/PHPMailer.php';
		require '/var/www/html/PHPMailer/src/SMTP.php';

		// Instantiation and passing `true` enables exceptions
		$mail = new PHPMailer(true);
		try {
		    //Server settings
		    $mail->SMTPDebug = 0;
		    $mail->isSMTP();                                            // Set mailer to use SMTP
		    $mail->Host       = 'email-smtp.XXX.amazonaws.com';  // Specify main and backup SMTP servers
		    $mail->SMTPAuth   = true;                                   // Enable SMTP authentication
		    $mail->Username   = 'XXX';                     // SMTP username
		    $mail->Password   = 'XXX';                               // SMTP password
		    $mail->SMTPSecure = 'tls';                                  // Enable TLS encryption, `ssl` also accepted
		    $mail->Port       = 587;                                    // TCP port to connect to

		    //Recipients
		    $mail->setFrom('info@bjjtracker.com', 'BJJ Tracker');
		    $mail->addAddress($email);     // Add a recipient
		    $mail->addReplyTo('info@bjjtracker.com', 'BJJ Tracker');

		    // Content
		    $mail->isHTML(true); // Set email format to HTML
		    
		    $mail->Subject = $subject;
		    $mail->Body    = $body;
		    $mail->AltBody = $altBody;

		    $mail->send();
		    $this->status = 'Message has been sent to: ' . $email;
		} catch (Exception $e) {
		    $this->status = "Message could not be sent. Mailer Error: {$mail->ErrorInfo}";
		}

		// echo $this->status;
		return $this->status;

	}
}

?>

And the implementation to send an email to newly registered users:

function sendWelcomeEmail($email){
	include 'send-email-service.php';	
	$SendEmailService = new SendEmailService();

	$subject = 'Welcome to BJJ Tracker';
        $body    = "Thanks for registering with BJJ Tracker.<br><br> Now's your chance to keep track of your grappling workouts and improve!.<br><br><a href='https://www.bjjtracker.com/'><img src='https://www.bjjtracker.com/img/bjjtracker-logo.png'></a>";
        $altBody = "Thanks for registering with BJJ Tracker";
        $SendEmailService -> sendEmail($subject, $body, $altBody, $email);

}

Instead of messaging new registrants immediately, I run a nightly cron job that checks accounts that we created the day previous. Here is the MySql query that I use to select them:

SELECT * FROM `users` WHERE DATE(created_date) = DATE(NOW() - INTERVAL 1 DAY);

You may also want to email users additional resources, at different times, depending on your product. Continue the onboarding process to increase the value of signup conversions.

Recent Posts

  • Secure Your Website: Implementing reCAPTCHA on Contact Forms
  • Engaging Website Elements: Add Typing and Backspace Effects with JavaScript
  • Tracking Google Ad Conversions in WordPress
  • AWS Lightsail & WordPress: A Seamless Website Migration & Redesign
  • Case Study: Fixing a WooCommerce Website for a New Client
  • Why Every Business Needs a Website in 2025
  • Boost Your Marketing: Integrating Newsletter Signups with HubSpot API
  • Magic Squares in JavaScript
  • CSS Grid: Border Between Each Row
  • Background Parallax Webpage with CSS
  • Freelance Web Development: Delivering Value to Clients
  • Diagnosing and Resolving Disk Space Issues on AWS EC2 with MariaDB
  • How to Fix a GoDaddy Parked Page and Revive Your Website
  • Binary Search in JavaScript
  • Redirecting Traffic from EC2 Public DNS to a Domain Name

Archives

  • May 2024
  • March 2024
  • February 2024
  • January 2024
  • December 2023
  • November 2023
  • October 2023
  • September 2023
  • August 2023
  • July 2023
  • June 2023
  • May 2023
  • April 2023
  • March 2023
  • February 2023
  • January 2023
  • December 2022
  • November 2022
  • October 2022
  • September 2022
  • August 2022
  • July 2022
  • June 2022
  • May 2022
  • March 2022
  • February 2022
  • January 2022
  • December 2021
  • November 2021
  • October 2021
  • September 2021
  • August 2021
  • July 2021
  • June 2021
  • May 2021
  • April 2021
  • March 2021
  • February 2021
  • January 2021
  • December 2020
  • November 2020
  • October 2020
  • September 2020
  • August 2020
  • July 2020
  • June 2020
  • May 2020
  • April 2020
  • March 2020
  • February 2020
  • January 2020
  • December 2019
  • November 2019
  • October 2019
  • September 2019
  • August 2019
  • July 2019
  • June 2019
  • April 2019
  • February 2019
  • December 2018
  • October 2018
  • September 2018
  • August 2018
  • November 2017
  • August 2014
  • November 2013
  • June 2012
  • October 2011
  • February 2010
  • April 2009
  • May 2008

  Ant Pace 2025

  646-533-0334

  info@antpace.com