Magic Login is pretty useful if you want to skip using passwords for your SaaS or any tools. All you need is just their email address and you can magically create a login link for them. This is secure since the Token is generated on your server and sent to them via email.
In this tutorial, we are going to see how we can create a Magic Login system with PHP and MySQL. We will be using a Library called PHPMailer. Which is a pretty useful library for handling SMTP in PHP.
Alright, Let’s get started.
This is how our database looks like
- id – Auto Increment Row ID
- email – Type Varchar holds the Email Address
- token – Type Varchar holds the Generated token
- date created – Type DateTime
Here is the Code for SQL Database
Login.sql
-- phpMyAdmin SQL Dump
-- version 4.9.5deb2
-- https://www.phpmyadmin.net/
--
-- Host: localhost:3306
-- Generation Time: Oct 03, 2021 at 06:00 PM
-- Server version: 8.0.26-0ubuntu0.20.04.2
-- PHP Version: 7.4.3
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET AUTOCOMMIT = 0;
START TRANSACTION;
SET time_zone = "+00:00";
--
-- Database: `magicLogin`
--
-- --------------------------------------------------------
--
-- Table structure for table `magicLogin`
--
CREATE TABLE `magicLogin` (
`id` int NOT NULL,
`email` varchar(99) COLLATE utf8mb4_general_ci NOT NULL,
`token` varchar(99) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
`date_created` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
--
-- Indexes for dumped tables
--
--
-- Indexes for table `magicLogin`
--
ALTER TABLE `magicLogin`
ADD PRIMARY KEY (`id`);
--
-- AUTO_INCREMENT for dumped tables
--
--
-- AUTO_INCREMENT for table `magicLogin`
--
ALTER TABLE `magicLogin`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
COMMIT;
Once the Database is created. Create a new file called config.php Please Refer to this GitHub Repo, if you don’t understand
Next, Create index.php file
<?php
session_start();
ob_start();
require 'config.php';
$db_connection = new Database();
$connect = $db_connection->dbConnection();
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
use PHPMailer\PHPMailer\Exception;
require 'phpmailer/Exception.php';
require 'phpmailer/PHPMailer.php';
require 'phpmailer/SMTP.php';
if (isset($_SESSION['email'])) {
header('location: dashboard.php');
}
//Generate Token
$token = bin2hex(random_bytes(24));
$ServerURL = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'] ;
if (isset($_POST['login'])) {
$emailTo = $_POST["email"];
$query = "SELECT * FROM magicLogin WHERE email =:email";
$statement = $connect->prepare($query);
$statement->execute(
array(
'email' => $emailTo
)
);
$count = $statement->rowCount();
if ($count > 0) {
//If the Email is Present
// Insert Token into User Table
$UpdateToken = "UPDATE magicLogin SET token='" . $token . "' WHERE email='" . $emailTo . "'";
$UpdateToken = $connect->prepare($UpdateToken);
$UpdateToken->execute();
SendEmail();
} else {
// Insert User & Token into User Table
$AddNew = "INSERT INTO magicLogin (email, token, date_created) VALUES (?,?,now())";
$AddNew = $connect->prepare($AddNew);
$AddNew->execute([$emailTo, $token]);
SendEmail();
}
}
function SendEmail()
{
//PHPMailer Object
$mail = new PHPMailer(true); //Argument true in constructor enables exceptions
global $smtpUsername;
global $smtpPassword;
global $smtpHost;
global $smtpPort;
global $emailFrom;
global $emailFromName;
global $emailTo;
global $token;
global $ServerURL;
$emailToName = $emailTo;
$mail->isSMTP();
$mail->SMTPDebug = 2; // 0 = off (for production use) - 1 = client messages - 2 = client and server messages
$mail->Host = $smtpHost; // use $mail->Host = gethostbyname('smtp.gmail.com'); // if your network does not support SMTP over IPv6
$mail->Port = $smtpPort; // TLS only
$mail->SMTPSecure = 'tls'; // ssl is deprecated
$mail->SMTPAuth = true;
$mail->Username = $smtpUsername;
$mail->Password = $smtpPassword;
$mail->setFrom($emailFrom, $emailFromName);
$mail->addAddress($emailTo, $emailToName);
$mail->Subject = 'Here is your magic login url'; // '. $token .'
$mail->msgHTML("Hey, <br><br> Here is the Magic Login URL <br><br> <a href='$ServerURL/login.php?token=$token' target='_blank'>Click here to Open in New Tab</a><br><br>Or Copy paste below link in New Tab<br><br><b>$ServerURL/login.php?token=$token</b><br><br><b>This link will expire in 60 minutes<br><br>Note: If you didn't requested login url.You can ignore this email.<br><br>Thank you."); //$mail->msgHTML(file_get_contents('contents.html'), __DIR__); //Read an HTML message body from an external file, convert referenced images to embedded,
$mail->AltBody = 'Here is your magic login url';
// $mail->addAttachment('images/phpmailer_mini.png'); //Attach an image file
if (!$mail->send()) {
echo "Mailer Error: " . $mail->ErrorInfo;
} else {
$_SESSION['magicRequest'] === 'true';
header('location: message.php?success=true');
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PHP Email Sender</title>
<link rel="stylesheet" href="./assets/style.css">
</head>
<body>
<div class="container">
<div class="box">
<form method="POST">
<h2>🪄 PHP Magic Login</h2>
<p>No need of Password, Enter your email and get login link</p>
<p><a href="https://webcheerz.com/php-magic-login">✍🏻 Show me the Tutorial</a></p>
<div class="field">
<input type="email" name="email" class="email" placeholder="📨 Enter your Email" autocomplete="off" autofocus="on">
</div>
<div class="field">
<input type="submit" class="login" name="login" value="Login" />
</div>
</form>
</div>
</div>
</body>
</html>
dashboard.php – This will show after login
<?php
session_start();
ob_start();
$_SESSION['magicRequest'] = 'false';
$_SESSION['magicFailed'] = 'false';
if (!isset($_SESSION['email'])) {
header('location: index.php');
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard</title>
<link rel="stylesheet" href="./assets/style.css">
</head>
<body>
<div class="container">
<h2><span class="wave">👋</span> Hello, <?php echo $_SESSION['email']; ?></h2>
<p><a href="https://webcheerz.com/php-magic-login">✍🏻 Show me the Tutorial</a></p>
<a href="logout.php">
<p class="logout">Logout?</p>
</a>
</div>
</body>
</html>
message.php – This will show the success message & failure message on the login URL sent
<?php
session_start();
ob_start();
$msg = '';
if (isset($_SESSION['magicRequest']) || isset($_SESSION['magicFailed'])) {
if (isset($_GET['success']) && $_GET['success'] === "true") {
if (isset($_SESSION['email'])) {
header('location: dashboard.php');
} else {
$msg = "<p class='message'>🎉 Magic URL Sent! Please Check your Email inbox</p>";
$title = "Magic URL Sent!";
}
} elseif (isset($_GET['failed']) && $_GET['failed'] === "true") {
if (isset($_SESSION['email'])) {
header('location: dashboard.php');
} else {
$msg = "<div class='message'><h2><a class='login-again' href='index.php'>Please Login Again</a></button></h2><p>😢 Looks like your Magic URL is Expired.</p></div>";
$title = "Magic URL Expired!";
}
} else {
unset($_SESSION['magicRequest']);
unset($_SESSION['magicFailed']);
header('location: index.php');
}
} else {
unset($_SESSION['magicRequest']);
unset($_SESSION['magicFailed']);
header('location: index.php');
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Magic URL Sent</title>
<link rel="stylesheet" href="./assets/style.css">
</head>
<body>
<div class="container">
<?php echo $msg; ?>
</div>
</body>
</html>
logout.php – This will unset the SESSION and Logout the user
<?php
session_start();
session_destroy();
header('Location:index.php');
?>
Now create a folder called assets, Inside create a file called style.css
body {
margin: auto;
font-family: "Courier New", Courier, monospace;
background-color: #282a36;
color: #f8f8f2;
}
a {
color: #f1fa8c;
text-decoration: none;
}
a.login-again{
color: #6272a4;
padding: 10px;
}
a.login-again:hover{
color: #f1fa8c;
background-color: #6272a4;
padding: 10px;
}
a:hover {
color: #d0dd40;
}
.container {
padding: 50px;
}
.field {
padding-top: 25px;
}
.email {
font-size: 25px;
outline: none;
padding: 5px 50px 5px 0;
background-color: #282a36;
border: none;
color: #f8f8f2;
border-bottom: #d0dd40 1px solid;
}
.email::placeholder {
color: #777777;
}
.login {
background-color: #6272a4;
color: #f8f8f2;
border: none;
border-radius: 5px;
padding: 15px 32px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
margin: 4px 2px;
cursor: pointer;
}
.login:hover {
background-color: #45527a;
}
.message {
background-color: #f1fa8c;
color: #45527a;
padding: 25px;
}
.logout {
color: rgb(255, 34, 34);
}
.logout:hover {
cursor: pointer;
text-decoration: underline;
}
.wave {
animation-name: wave-animation; /* Refers to the name of your @keyframes element below */
animation-duration: 2.5s; /* Change to speed up or slow down */
animation-iteration-count: infinite; /* Never stop waving :) */
transform-origin: 70% 70%; /* Pivot around the bottom-left palm */
display: inline-block;
}
@keyframes wave-animation {
0% { transform: rotate( 0.0deg) }
10% { transform: rotate(14.0deg) } /* The following five values can be played with to make the waving more or less extreme */
20% { transform: rotate(-8.0deg) }
30% { transform: rotate(14.0deg) }
40% { transform: rotate(-4.0deg) }
50% { transform: rotate(10.0deg) }
60% { transform: rotate( 0.0deg) } /* Reset for the last half to pause */
100% { transform: rotate( 0.0deg) }
}
After Adding the Style. Our Project will look like this.
Once the User enters the Email Address and Click on Login. They will get email with login URL like this.
If the user is not present in the Database, It’ll automatically create a new user and send a login URL.
So signup and login will be in a single form.
Finally, add the PHP mailer library inside a folder called phpmailer Here are the files
That’s it. Your PHP Magic login is created.
But it won’t automatically reset the token every 60 minutes. For that, we need to set up a cron job.
If you’re in a Linux system.
Type this command crontab -e
and insert the below line at the bottom of the file
0 * * * * /usr/bin/php /var/www/magicLogin/ResetToken.php
Please change the path of the file to your project directory.