Creating a Signup Login system with JavaSript is way easier than you think! Let's made one from scratch!
SIMPLEST WAY TO CREATE LOGIN SIGNUP
- Create Strategy
- Config Passport
- Handle Route
- Use cookie
- Use frontEND
Let's go one by one.
COMPLETE TUTORIAL
For those who already know how to set up their basic server and database, skip to STEP 4.
Step 0. Setting up our Project folder
Create a folder called "authDemo" and open it on your favorite text editor. Create a file named app.js. Now type npm init -y on your terminal. It'll just create a package.json file to track our packages etc. Anyway let's begin the real adventure!
Step 1. Basic Server Setup
First type this on your terminal to install these packages
npm i express mongoose passport passport-local cookie-session
Short package descriptions
- express: to create our server
- mongoose: to connect to our database
- passport: our main package for login/signup including Google Github logins too
- passport-local: to create our login/signup with login and signup forms
Now write these on your app.js file
// core modules
const express = require('express');
const path = require('path'); //it is an in-built node module so no need to install it
const passport = require('passport'); //this is our main package that will help us create the login signup system
const cookieSession = require('cookie-session'); //this is for using cookies so that our users stay logged in
// ------------------------------FIRING EXPRESS APP
const app = express();
app.use(express.json()); //it allows us access the data sent from frontend using req.body
app.use(express.urlencoded({ extended: false }));
app.use(express.static(path.join(__dirname, `client`))); //here we are saying that our static files I mean html css etc files will be served from this client file
// -------------------------COOKIE AND PASSPORT
app.use(cookieSession({
maxAge: 24*60*60*1000, //it is the total expiration time, here the cookie will be alive for 1 day
keys: [`abcdefghijklmn`], //here type whatever your want instead of abcdefghijklm, I just typed abcdefghijklm
}));
/* -------------------------------------------------
. config
------------------------------------------------- */
require('./config/mongodbConfig'); //Here it is firing the mongodbConfig file that has our database configuration, we'll create it soon
require('./config/passportConfig'); //Here it is firing the passportConfig file that has our login/signup configuration, we'll create it soon
/* -------------------------------------------------
. routes
------------------------------------------------- */
// auth routes
app.use(require('./routes/authRoute')); //here authRoute has our login signup routes, we'll create it soon
// CATCH ALL HANDLER, if there is any route that does not match the above routes, send the index.html file
app.get('*', (req, res, next)=>{
try {
res.sendFile(path.join(__dirname, `client/index.html`));
} catch (err) {
next(err, req, res)
}
})
// ERRORS HANDLER
app.use((err, req, res, next)=>{
console.log(err.message);
console.log(err);
res.json({ msg: `Server error`, error: err.message })
});
// --------------------end of routes------------------------
// -----------------------------------------LISTEN
const PORT = process.env.PORT || 5000;
app.listen(PORT, ()=>{
console.log(`Server is running on port ${ PORT }`);
});
Will explain this code later. First let's set our database configuration and routes
Step 2. Basic Database setup
create a config folder, here we will store all our configuration. You don't have to, but I prefer it because if your project gets larger it'll help you make your codebase cleaner and easier to maintain. Anyway, now create a file called mongodbConfig.js on that folder.
Write these on mongodbConfig.js file
const mongoose = require('mongoose');
mongoose.connect(YOUR_DATABASE_STRING,
{
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true,
useFindAndModify: false,
},
(err) =>{
if(err) throw err;
console.log('connected to MongoDB');
}
)
Replace YOUR_DATABASE_STRING with your database string, if you don't have that, go and use MongoDB Atlas and create a cluster and you'll get your database string.
Step 3. Creating an USER model
Create a folder called models and create User.js
Type these on User.js file
const mongoose = require('mongoose');
/* ----------------------------------
. SUB SCHEMAs
---------------------------------- */
const LocalSchema = new mongoose.Schema({
email: String,
password: String,
})
/* ------------------------------------------
. MAIN SCHEMA
------------------------------------------ */
const UserSchema = new mongoose.Schema({
local: LocalSchema, //I'm using this sub schema now because we will be creating Login with Google Twitter Linkedin Github etc, so it'll help us in the future too.
username: String,
});
/* ------------------------------------------
. USER MODEL
------------------------------------------ */
module.exports = User = mongoose.model('User', UserSchema);
Here we will just store the email, password and username to keep everything simple.
Step 4. REAL CODING BEGINS
Create a passportConfig.js file inside config folder. Also create a passportStrategies sub-folder inside config folder. Now create a SignupStrategy.js and LoginStrategy.js files inside passportStrategies folder.
STEP 5. Create Login and Signup Strategies
Write these on your SignupStrategy.js file
const Strategy = require('passport-local'); //this is to create our login signup strategies
const User = require('../../models/User'); //our User model to save our user data also to retrieve our user data
const bcrypt = require('bcryptjs'); //we use it to hash our passwords, if you don't know how to use it, go see my prev blog. I already make one on it explaining in detail
module.exports = SignupStrategy = new Strategy(
{
// overriding the default username with email
usernameField: 'email', //passport by default uses username and password to login and signup, just like My Anime List website. We are here changing it so that users signup with email and password system and not with username and password system
passwordField: 'password',
passReqToCallback: true, //this will allow use use req on the following callback function
},
(req, email, password, done)=>{
const { username } = req.body; //retrieving username from the data that frontend sent to us. Look here we'll also retrieve other data if it sent us, like first name last name location etc. To keep it simple I'm just using username. One more thing You don't need to retrieve email of password this way because passport will already retrieving it for you
User.findOne({ 'local.email': email }, (err, user)=>{ //checking if there is already an user with this email
// if there is an error while checking
if(err) return done(err); //finish this process right here and send back error to our error handler
// if there is already an account with this email, we'll finish the process right here and notify the user that this email is already taken
if(user) return done({ msg: `This email is already taken` }, null);
// if this email is not already taken, create a new account with this email
User.create({
'local.email': email,
'local.password': bcrypt.hashSync(password, bcrypt.genSaltSync()), //here saving the hashed password, see my prev blog to know in detail
username,
}).then(newUser=> done(null, newUser)); // Now when the account has been created, send this data onto the passport middleware on the auth route (we'll create it soon) and that middleware will send this data back to the cookie-fyer which will then cookie-fy our data and store it in a cookie
})
}
)
Read the comments to understand to code. I've explained line by line
Now similarly we are gonna create the Login Strategy. It's even easier.
Write these on LoginStrategy.js file
const Strategy = require('passport-local'); //to create login signup strategy
const User = require('../../models/User'); //to save or retrieve user data
const bcrypt = require('bcryptjs'); //to hash or verify passwords, to know more see my prev blog
module.exports = LoginStrategy = new Strategy(
{
// overriding default username with email
usernameField: 'email', //as explained passport uses username and password to login by default, we are overriding it so that it uses email and password for logging in
passwordField: 'password',
passReqToCallback: true, //it'll allow us use req on the following callback function
},
(req, email, password, done)=>{
User.findOne({ 'local.email': email }, (err, user)=>{ //finding the user with that email
// if there is any error while finding, finish the process right here and send back the error to our error handler
if(err) return done(err);
// if there is no account with that email then let the user know that there is no account with this email
if(!user) return done({ msg: `No user found`}, null);
// if password does not match, let the user know that he typed wrong passwords
const isPasswordValid = bcrypt.compareSync(password, user.local.password); //it is comparing the plain password with the saved hashed password to see if they match, to know more about it see my previous blog, I've explained in detail
if(!isPasswordValid) return done({ msg: `Invalid Credentials` }, null);
// if everything is OK, send the user data to the password middleware on the auth route that will then send the user data onto the cookie-fyer that will then cookie-fy and store the data on a cookie
return done(null, user)
})
}
)
Read the comments to understand each line of code.
Step 6. Passport Config
Now open the passportConfig.js file and write these
const passport = require('passport'); //our main package for creating login signup system
const User = require('../models/User'); //to save or retrieve
user data
const LoginStrategy = require('./passportStrategies/LoginStrategy');
const SignupStrategy = require('./passportStrategies/SignupStrategy');
/* ------------------------------------
. SERIALIZE AND DESERIALIZE
------------------------------------ */
//this is our cookie-fyer machine, it'll take the user data and cookie-fy it and store it on a cookie, here we will only cookie-fy the id of the user because we do not want to store his email and password on the cookie because if we do and if hackers find this cookie then it'll be a disaster. ha ha, I think you got my point
passport.serializeUser((user, done)=>{
done(null, user.id);
});
//this is the de-cookie-fyer machine. When a user with the cookie comes to our website, it asks them to show him the cookie so that it knows that the user is already logged in. Then it will de-code the cookie and get that id we stored out of the cookie and find the user who has this id, then it will retrieve it's data and store in in a user object and it will attach it on our req object. so now if he is logged in we can access his data with req.user amazing right?
passport.deserializeUser((id, done)=>{
User.findById(id).then(user=> done(null, user));
});
/* ------------------------------------
. STRATEGIES
------------------------------------ */
//here we are using those strategies we created
passport.use('local-signup', SignupStrategy); //we are also giving them name so that we can reference them by name later
passport.use('local-login', LoginStrategy); //same thing here too
Now the 1st part done. Now we just need to create the routes and then use it on our frontend. let's go!
Step 7. Creating routes
let's create a folder on our root level named routes and create a file called authRoute.js inside it.
Now write these inside the authRoute.js file
const router = require('express').Router(); //this is the router that'll create the routes for us
const passport = require('passport'); //this is our main package for login signup system
/* --------------------------------------
. LOGOUT
-------------------------------------- */
//this is a route for logging out. It'll log out the users and then send back a message to let them know that they are successfully logged out
router.get('/logout', (req, res)=>{
req.logOut();
res.json({ msg: `Logged out` });
});
/* --------------------------------------
. GET LOGGED IN USER
-------------------------------------- */
//this is a route to get logged in user data
router.get('/user', (req, res)=>{
if(req.user) { //if user is logged in, user data will be stored on req.user
res.json({ user: req.user });
} else { //if user is not logged in, req.user will not exist
res.json({ msg: "Please log in to access this data" });
}
});
/* --------------------------------------
. SIGNUP
-------------------------------------- */
router.post('/signup', (req, res, next)=>{
passport.authenticate('local-signup', (err, user, info)=>{ //this is our passport authenticating middleware I was talking about
// if there is any error (including the error I defined on the Strategy), send back the error with that error message to the user
if(err) return res.status(400).json(err);
//if there is no error in sign up, it'll create their account. so now log them in
req.logIn(user, (err)=>{
// if there is any error while logging in, send the error message
if(err) return res.status(500).json({ msg: `Oops, something went wrong` });
// if everything is OK, return the user onto the Cookie-fyer
return res.json(user);
})
})(req, res, next)
})
/* --------------------------------------
. LOGIN
-------------------------------------- */
router.post('/login', (req, res, next)=>{
passport.authenticate('local-login', (err, user, info)=>{ //this is the passport middleware I was talking about
// if there is any error (including the error I defined on the Strategy) send back the error message to the user
if(err) return res.status(400).json(err);
//if there is no error, log them in
req.logIn(user, (err)=>{
// if there is any error while logging in, send back the error message to the user
if(err) return res.status(500).json({ msg: `Oops, something went wrong`});
// if everything is OK, send the user data onto the Cookie-fyer
return res.json(user);
})
})(req, res, next)
})
module.exports = router;
Now we are almost done! Yippie. We just need a frontend to interact with our login signup system.
I'm gonna use basic HTML, you can use whatever your want, be it react, angular, vue or whatever. Everything is same.
Step 8. Create the FrontEND
Create a folder called client on our root level. Then create index.html. You can also create external js and other external css files here and reference it from the index.html. I'm gonna keep it simple and going with the default styles and not any extra styles.
Let's create a login and a signup form inside index.html.
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>LOGIN SIGNUP</title>
</head>
<body>
<form id="signupForm" >
<input id="signupEmail" type="email" required/>
<input id="signupPassword" type="password" required/>
<input id="signupUsername" type="text" />
<button>Sign up</button>
</form>
<form id="loginForm" >
<input id="loginEmail" type="email" required/>
<input id="loginPassword" type="password" required/>
<button>Log in</button>
</form>
<script>
const signupForm = document.querySelector('#signupForm');
const loginForm = document.querySelector('#loginForm');
const signupEmail= document.querySelector('#signupEmail');
const signupPassword= document.querySelector('#signupPassword');
const signupUsername= document.querySelector('#signupUsername');
const loginEmail= document.querySelector('#loginEmail');
const loginPassword= document.querySelector('#loginPassword');
//signup form (if you don't know how fetch works see my prev blog, I explained in detail)
signupForm.addEventListener('submit', async (e)=>{
e.preventDefault();
const response = await fetch('/signup', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: signupEmail.value,
password: signupPassword.value,
username: signupUsername.value
})
});
const data = await data.json();
console.log(data);
});
//login form
loginForm.addEventListener('submit', async (e)=>{
e.preventDefault();
const response = await fetch('/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: loginEmail.value,
password: loginPassword.value
})
});
const data = await data.json();
console.log(data);
});
</script>
</body>
</html>
Congrats we just created a complete Login Signup System. Read the comments to understand each line of code. Now let me go over it one more time.
What is happening on the code?
We made a signup POST request to /signup url from our frontend giving email, password and username. You can send more data too. I just kept it simple.
Now our server is listening for requests on /signup route. She found this request and told, "Hey Passport, this is about signup. You handle this for me please". So now Passport takes over it, it grabs email and password (if we did not override the default username with email it would have grabbed username and password) and sends it to our strategy. On the strategy, if in signup, we checked to see that the email is not already taken, if yes, it will send back an erro message saying "email already taken" or something. You can show it on your frontend. Now if it's not taken, after successfully creating the account it'll cookie-fy the user id and attach the cookie to our req object. so every time we make a new request we'll already be logged in.
Now same for log in strategy. We'll check if there is the account and also check the passwords to match. if any error, it'll send back error message. If not it'll log them in and cookie-fy.
When they log out, it'll destroy the cookie and you'll be logged out.
Now let's test our app.
TESTING OUR APP
Go signup and got to localhost:5000/user, you'll see the user data. Now go to localhost:5000/logout to log out. Now go again to localhost:5000/user. You'll no longer see the user data because you already logged out. Now log in and then go to localhost:5000/user, you'll see user data. Log out again by going to localhost:5000/logout and you'll be logged out and you'll not see user data. Amazing right?
Congrats, you just created your very first user login and signup system!!! Yippie!
Now get ready for more!!
If you have any questions or If you are stuck
Feel free to reach out to me. You can also contact me on LinkedIN https://www.linkedin.com/in/silvenleaf/ or on Twitter (as @silvenleaf).
If you wanna know more about me, this is my portfolio website SilvenLEAF.github.io
I'd LOVE to be your friend, feel FREE to reach out to me!!
NEXT BLOG is coming on 28th Nov, 2020
on Signup/Login with Google Github and Linkedin Series**
Next Blogs DATE
Nov 28th and Nov 5th 2020, on Signup/Login with Google Github and Linkedin Series**
Nov 28th 2020, How to create Login with Google
Nov 28th 2020, How to create Login with Github
Nov 28th 2020, How to create Login with LinkedIn
Nov 28th 2020, How to create Login with Twitter
Nov 30th 2020, Password Reset Series (with Node.js and React)
If this blog was helpful to you,
PLEASE give a LIKE and share,
It'd mean a lot to me. Thanks
Prev Blog
Change CSS variables with 1 JavaScript line
Next Blog
Coming on 28th Nov
Top comments (2)
how to deploy on live
Hi there @kdtrrs thanks for your question. To deploy live you can use Heroku or anything. You might find heroku easier. Simply create an account, create an app, connect your github repo and choose auto deploy and that's all. You are set. Every time you push to your remote, it'll redeploy. So updatiny it is super easy, also if using MERN or other stack, you can simply write a heroku script to automatically create the build of your app. You can find the guide on the web or YTube too. Maybe if you want I can write a blog on it in the future. Wish you an incredible weekend my lovely friend!!