#dotenv tutorial nodejs
Explore tagged Tumblr posts
codeonedigest · 2 years ago
Text
Access Environment Variable in Nodejs JavaScript Application | Reading ENV Variable Example
Full Video Link https://youtu.be/dxrNopL1sbQ Hello friends, new #video on #reading #accessing #environmentvariables in #nodejs #projeect #application #tutorial #examples is published on #codeonedigest #youtube channel. @java #java #aws #a
In this video, we will read the environment variable in nodejs javascript project. We will learn what “dotenv” module in nodejs javascript. How to use “dotenv” package in our nodejs javascript project. ** Important Nodejs Javascript Packages or Modules ** Dotenv – DotEnv is a lightweight npm package that automatically loads environment variables from a .env file into the process.env object. To…
Tumblr media
View On WordPress
0 notes
katyslemon · 4 years ago
Text
How to Build Microservices with Node.js
Divide into independent chunks to build the entire application.
Introduction
In the last blog, we learned about Microservice Architecture and Why Build Microservices with Node.js. If you haven’t explored or read much about Microservice Architecture then I would recommend visiting our last blog, to have basic theoretical knowledge. The blog deals with What, Why, and When with Microservices. We will cover the remaining question ‘How’ in this tutorial.
You are exactly right! In this tutorial, we will learn how to build a basic demo application for illustrating Microservices with Node.js. The tutorial will consist of basic and simple steps that you can follow with us and start building your own demo app.
Without further ado, let’s get started with our application.
Microservices with Node.js Example
In this demo application, we will build microservices with NodeJS which will connect to an external API.
The app will have three services: Book, Customer, and Order services. Each of the services will have individual servers running on different ports. These services will communicate with each other through REST APIs. We all know that if it wasn’t microservices architecture we would have only one server running. But, in microservices architecture the scenario is different.
So, this was about what we are building in this tutorial, let’s get started with the coding part.
Initial Set-Up
Make sure you have Node.js installed. Visit Nodejs.org to download the latest Node version, if your system doesn’t have Node.js.
Run npm init in the project root folder. This will create a package.json file that will create certain questions about the package, if you are not sure how to respond you can use the default.
We will use four packages, Express, mongoose, axios, and dotenv which can be installed as follows:
$ npm install express mongoose axios dotenv --save
Project Structure
Refer to the below image for the project structure. The structure can vary from person to person.
Tumblr media
Read more: Creating Database Connection
0 notes
riichardwilson · 5 years ago
Text
Creating Secure Password Flows With NodeJS And MySQL
About The Author
Darshan Somashekar is a technology entrepreneur who has built & sold two startups. His latest fun project is a solitaire site called Solitaired. Previously, … More about Darshan …
Reset password functionality is table stakes for any user-friendly application. It can also be a security nightmare. Using NodeJS and MySQL, Darshan demonstrates how to successfully create a secure reset password flow so you can avoid these pitfalls.
If you’re anything like me, you’ve forgotten your password more than once, especially on sites you haven’t visited in a while. You’ve probably also seen, and/or been mortified by, reset password emails that contain your password in plain text.
Unfortunately, the reset password workflow gets short shrift and limited attention during application development. This not only can lead to a frustrating user experience, but can also leave your application with gaping security holes.
We’re going to cover how to build a secure reset password workflow. We’ll be using NodeJS and MySQL as our base components. If you’re writing using a different language, framework, or database, you can still benefit from following the general “Security Tips” outlined in each section.
A reset password flow consists of the following components:
A link to send the user to the start of the workflow.
A form that lets the user submit their email.
A lookup that validates the email and sends an email to the address.
An email that contains the reset token with an expiry that allows the user to reset their password.
A form that let’s the user generate a new password.
Saving the new password and letting the user log in again with the new password.
Besides Node, Express & MySQL, we’ll be using the following libraries:
Sequelize ORM
Nodemailer
Sequelize is a NodeJS database ORM that makes it easier to run database migrations as well as security create queries. Nodemailer is a popular NodeJS email library that we’ll use to send password reset emails.
Security Tip #1
Some articles suggest secure password flows can be designed using JSON Web Tokens (JWT), which eliminate the need for database storage (and thus are easier to implement). We don’t use this approach on our site, because JWT token secrets are usually stored right in code. We want to avoid having ‘one secret’ to rule them all (for the same reason you don’t salt passwords with the same value), and therefore need to move this information into a database.
Installation
First, install Sequelize, Nodemailer, and other associated libraries:
$ npm install --save sequelize sequelize-cli mysql crypto nodemailer
In the route where you want to include your reset workflows, add the required modules. If you need a refresher on Express and routes, check out their guide.
const nodemailer = require('nodemailer');
And configure it with your email SMTP credentials.
const transport = nodemailer.createTransport({ host: process.env.EMAIL_HOST, port: process.env.EMAIL_PORT, secure: true, auth: { user: process.env.EMAIL_USER, pass: process.env.EMAIL_PASS } });
The email solution I’m using is AWS’s Simple Email Service, but you can use anything (Mailgun, etc).
If this is your first time setting up your email sending service, you’ll need to spend some time configuring the appropriate Domain Keys and setting up authorizations. If you use Route 53 along with SES, this is super simple and done virtually automatically, which is why I picked it. AWS has some tutorials on how SES works with Route53.
Security tip #2
To store the credentials away from my code, I use dotenv, which lets me create a local .env file with my environment variables. That way, when I deploy to production, I can use different production keys that aren’t visible in code, and therefore lets me restrict permissions of my configuration to only certain members of my team.
Database Setup
Since we’re going to be sending reset tokens to users, we need to store those tokens in a database.
I am assuming you have a functioning users table in your database. If you’re using Sequelize already, great! If not, you may want to brush up on Sequelize and the Sequelize CLI.
If you haven’t used Sequelize yet in your app, you can set it up by running the command below in your app’s root folder:
$ sequelize init
This will create a number of new folders in your setup, including migrations and models.
This will also create a config file. In your config file, update the development block with the credentials to your local mysql database server.
Let’s use Sequelize’s CLI tool to generate the database table for us.
$ sequelize model:create --name ResetToken --attributes email:string,token:string,expiration:date,used:integer $ sequelize db:migrate
This table has the following columns:
Email address of user,
Token that has been generated,
Expiration of that token,
Whether the token has been used or not.
In the background, sequelize-cli is running the following SQL query:
CREATE TABLE `ResetTokens` ( `id` int(11) NOT NULL AUTO_INCREMENT, `email` varchar(255) DEFAULT NULL, `token` varchar(255) DEFAULT NULL, `expiration` datetime DEFAULT NULL, `createdAt` datetime NOT NULL, `updatedAt` datetime NOT NULL, `used` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
Verify this worked properly using your SQL client or the command line:
mysql> describe ResetTokens; +------------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +------------+--------------+------+-----+---------+----------------+ | id | int(11) | NO | PRI | NULL | auto_increment | | email | varchar(255) | YES | | NULL | | | token | varchar(255) | YES | | NULL | | | expiration | datetime | YES | | NULL | | | createdAt | datetime | NO | | NULL | | | updatedAt | datetime | NO | | NULL | | | used | int(11) | NO | | 0 | | +------------+--------------+------+-----+---------+----------------+ 7 rows in set (0.00 sec)
Security Tip #3
If you’re not currently using an ORM, you should consider doing so. An ORM automates the writing and proper escaping of SQL queries, making your code more readable and more secure by default. They’ll help you avoid SQL injection attacks by properly escaping your SQL queries.
Set Up Reset Password Route
Create the get route in user.js:
router.get('/forgot-password', function(req, res, next) { res.render('user/forgot-password', { }); });
Then create the POST route, which is the route that is hit when the reset password form is posted. In the code below, I’ve included a couple of important security features.
Security Tips #4-6
Even if we don’t find an email address, we return ‘ok’ as our status. We don’t want untoward bots figuring out what emails are real vs not real in our database.
The more random bytes you use in a token, the less likely it can be hacked. We are using 64 random bytes in our token generator (do not use less than 8).
Expire the token in 1 hour. This limits the window of time the reset token works.
router.post('/forgot-password', async function(req, res, next) { //ensure that you have a user with this email var email = await User.findOne({where: { email: req.body.email }}); if (email == null) { /** * we don't want to tell attackers that an * email doesn't exist, because that will let * them use this form to find ones that do * exist. **/ return res.json({status: 'ok'}); } /** * Expire any tokens that were previously * set for this user. That prevents old tokens * from being used. **/ await ResetToken.update({ used: 1 }, { where: { email: req.body.email } }); //Create a random reset token var fpSalt = crypto.randomBytes(64).toString('base64'); //token expires after one hour var expireDate = new Date(); expireDate.setDate(expireDate.getDate() + 1/24); //insert token data into DB await ResetToken.create({ email: req.body.email, expiration: expireDate, token: token, used: 0 }); //create email const message = { from: process.env.SENDER_ADDRESS, to: req.body.email, replyTo: process.env.REPLYTO_ADDRESS, subject: process.env.FORGOT_PASS_SUBJECT_LINE, text: 'To reset your password, please click the link below.\n\nhttps://'+process.env.DOMAIN+'/user/reset-password?token='+encodeURIComponent(token)+'&email='+req.body.email }; //send email transport.sendMail(message, function (err, info) { if(err) { console.log(err)} else { console.log(info); } }); return res.json({status: 'ok'}); });
You’ll see a User variable referenced above — what is this? For the purposes of this tutorial, we’re assuming you have a User model that connects to your database to retrieve values. The code above is based on Sequelize, but you can modify as needed if you query the database directly (but I recommend Sequelize!).
We now need to generate the view. Using Bootstrap CSS, jQuery, and the pug framework built into the Node Express framework, the view looks like the following:
extends ../layout block content div.container div.row div.col h1 Forgot password p Enter your email address below. If we have it on file, we will send you a reset email. div.forgot-message.alert.alert-success(style="display:none;") Email address received. If you have an email on file we will send you a reset email. Please wait a few minutes and check your spam folder if you don't see it. form#forgotPasswordForm.form-inline(onsubmit="return false;") div.form-group label.sr-only(for="email") Email address: input.form-control.mr-2#emailFp(type='email', name='email', placeholder="Email address") div.form-group.mt-1.text-center button#fpButton.btn.btn-success.mb-2(type='submit') Send email script. $('#fpButton').on('click', function() { $.post('/user/forgot-password', { email: $('#emailFp').val(), }, function(resp) { $('.forgot-message').show(); $('#forgotPasswordForm').remove(); }); });
Here’s the form on the page:
Your reset password form. (Large preview)
At this point, you should be able to fill out the form with an email address that’s in your database, and then receive a reset password email at that address. Clicking the reset link won’t do anything yet.
Set Up “Reset Password” Route
Now let’s go ahead and set up the rest of the workflow.
Add the Sequelize.Op module to your route:
const Sequelize = require('sequelize'); const Op = Sequelize.Op;
Now let’s build the GET route for users that have clicked on that reset password link. As you’ll see below, we want to make sure we’re validating the reset token appropriately.
Security Tip #7:
Ensure you’re only looking up reset tokens that have not expired and have not been used.
For demonstration purposes, I also clear up all expired tokens on load here to keep the table small. If you have a large website, move this to a cronjob.
router.get('/reset-password', async function(req, res, next) { /** * This code clears all expired tokens. You * should move this to a cronjob if you have a * big site. We just include this in here as a * demonstration. **/ await ResetToken.destroy({ where: { expiration: { [Op.lt]: Sequelize.fn('CURDATE')}, } }); //find the token var record = await ResetToken.findOne({ where: { email: req.query.email, expiration: { [Op.gt]: Sequelize.fn('CURDATE')}, token: req.query.token, used: 0 } }); if (record == null) { return res.render('user/reset-password', { message: 'Token has expired. Please try password reset again.', showForm: false }); } res.render('user/reset-password', { showForm: true, record: record }); });
Now let’s create the POST route which is what is hit once the user fills out their new password details.
Security tip #8 through 11:
Make sure that the passwords match and meet your minimum requirements.
Check the reset token again to make sure it has not been used and has not expired. We need to check it again because the token is being sent by a user via the form.
Before resetting the password, mark the token as used. That way, if something unforeseen happens (server crash, for example), the password won’t be reset while the token is still valid.
Use a cryptographically secure random salt (in this case, we use 64 random bytes).
router.post('/reset-password', async function(req, res, next) { //compare passwords if (req.body.password1 !== req.body.password2) { return res.json({status: 'error', message: 'Passwords do not match. Please try again.'}); } /** * Ensure password is valid (isValidPassword * function checks if password is >= 8 chars, alphanumeric, * has special chars, etc) **/ if (!isValidPassword(req.body.password1)) { return res.json({status: 'error', message: 'Password does not meet minimum requirements. Please try again.'}); } var record = await ResetToken.findOne({ where: { email: req.body.email, expiration: { [Op.gt]: Sequelize.fn('CURDATE')}, token: req.body.token, used: 0 } }); if (record == null) { return res.json({status: 'error', message: 'Token not found. Please try the reset password process again.'}); } var upd = await ResetToken.update({ used: 1 }, { where: { email: req.body.email } }); var newSalt = crypto.randomBytes(64).toString('hex'); var newPassword = crypto.pbkdf2Sync(req.body.password1, newSalt, 10000, 64, 'sha512').toString('base64'); await User.update({ password: newPassword, salt: newSalt }, { where: { email: req.body.email } }); return res.json({status: 'ok', message: 'Password reset. Please login with your new password.'}); }); And again, the view: extends ../layout block content div.container div.row div.col h1 Reset password p Enter your new password below. if message div.reset-message.alert.alert-warning #{message} else div.reset-message.alert(style='display:none;') if showForm form#resetPasswordForm(onsubmit="return false;") div.form-group label(for="password1") New password: input.form-control#password1(type='password', name='password1') small.form-text.text-muted Password must be 8 characters or more. div.form-group label(for="password2") Confirm new password input.form-control#password2(type='password', name='password2') small.form-text.text-muted Both passwords must match. input#emailRp(type='hidden', name='email', value=record.email) input#tokenRp(type='hidden', name='token', value=record.token) div.form-group button#rpButton.btn.btn-success(type='submit') Reset password script. $('#rpButton').on('click', function() { $.post('/user/reset-password', { password1: $('#password1').val(), password2: $('#password2').val(), email: $('#emailRp').val(), token: $('#tokenRp').val() }, function(resp) { if (resp.status == 'ok') { $('.reset-message').removeClass('alert-danger').addClass('alert-success').show().text(resp.message); $('#resetPasswordForm').remove(); } else { $('.reset-message').removeClass('alert-success').addClass('alert-danger').show().text(resp.message); } }); });
This is what it should look like:
Your reset password form. (Large preview)
Add The Link To Your Login Page
Lastly, don’t forget to add a link to this flow from your login page! Once you do this, you should have a working reset password flow. Be sure to test thoroughly at each stage of the process to confirm everything works and your tokens have a short expiration and are marked with the correct status as the workflow progresses.
Next Steps
Hopefully this helped you on your way to coding a secure, user-friendly reset password feature.
If you are interested in learning more about cryptographic security, I recommend Wikipedia’s summary (warning, it’s dense!).
If you want to add even more security to your app’s authentication, look into 2FA. There are a lot of different options out there.
If I’ve scared you off from building your own reset password flow, you can rely on third-party login systems like Google and Facebook. PassportJS is a middleware you can use for NodeJS that implements these strategies.
(dm, yk, il)
Website Design & SEO Delray Beach by DBL07.co
Delray Beach SEO
source http://www.scpie.org/creating-secure-password-flows-with-nodejs-and-mysql/ source https://scpie.tumblr.com/post/612405639958986752
0 notes
scpie · 5 years ago
Text
Creating Secure Password Flows With NodeJS And MySQL
About The Author
Darshan Somashekar is a technology entrepreneur who has built & sold two startups. His latest fun project is a solitaire site called Solitaired. Previously, … More about Darshan …
Reset password functionality is table stakes for any user-friendly application. It can also be a security nightmare. Using NodeJS and MySQL, Darshan demonstrates how to successfully create a secure reset password flow so you can avoid these pitfalls.
If you’re anything like me, you’ve forgotten your password more than once, especially on sites you haven’t visited in a while. You’ve probably also seen, and/or been mortified by, reset password emails that contain your password in plain text.
Unfortunately, the reset password workflow gets short shrift and limited attention during application development. This not only can lead to a frustrating user experience, but can also leave your application with gaping security holes.
We’re going to cover how to build a secure reset password workflow. We’ll be using NodeJS and MySQL as our base components. If you’re writing using a different language, framework, or database, you can still benefit from following the general “Security Tips” outlined in each section.
A reset password flow consists of the following components:
A link to send the user to the start of the workflow.
A form that lets the user submit their email.
A lookup that validates the email and sends an email to the address.
An email that contains the reset token with an expiry that allows the user to reset their password.
A form that let’s the user generate a new password.
Saving the new password and letting the user log in again with the new password.
Besides Node, Express & MySQL, we’ll be using the following libraries:
Sequelize ORM
Nodemailer
Sequelize is a NodeJS database ORM that makes it easier to run database migrations as well as security create queries. Nodemailer is a popular NodeJS email library that we’ll use to send password reset emails.
Security Tip #1
Some articles suggest secure password flows can be designed using JSON Web Tokens (JWT), which eliminate the need for database storage (and thus are easier to implement). We don’t use this approach on our site, because JWT token secrets are usually stored right in code. We want to avoid having ‘one secret’ to rule them all (for the same reason you don’t salt passwords with the same value), and therefore need to move this information into a database.
Installation
First, install Sequelize, Nodemailer, and other associated libraries:
$ npm install --save sequelize sequelize-cli mysql crypto nodemailer
In the route where you want to include your reset workflows, add the required modules. If you need a refresher on Express and routes, check out their guide.
const nodemailer = require('nodemailer');
And configure it with your email SMTP credentials.
const transport = nodemailer.createTransport({ host: process.env.EMAIL_HOST, port: process.env.EMAIL_PORT, secure: true, auth: { user: process.env.EMAIL_USER, pass: process.env.EMAIL_PASS } });
The email solution I’m using is AWS’s Simple Email Service, but you can use anything (Mailgun, etc).
If this is your first time setting up your email sending service, you’ll need to spend some time configuring the appropriate Domain Keys and setting up authorizations. If you use Route 53 along with SES, this is super simple and done virtually automatically, which is why I picked it. AWS has some tutorials on how SES works with Route53.
Security tip #2
To store the credentials away from my code, I use dotenv, which lets me create a local .env file with my environment variables. That way, when I deploy to production, I can use different production keys that aren’t visible in code, and therefore lets me restrict permissions of my configuration to only certain members of my team.
Database Setup
Since we’re going to be sending reset tokens to users, we need to store those tokens in a database.
I am assuming you have a functioning users table in your database. If you’re using Sequelize already, great! If not, you may want to brush up on Sequelize and the Sequelize CLI.
If you haven’t used Sequelize yet in your app, you can set it up by running the command below in your app’s root folder:
$ sequelize init
This will create a number of new folders in your setup, including migrations and models.
This will also create a config file. In your config file, update the development block with the credentials to your local mysql database server.
Let’s use Sequelize’s CLI tool to generate the database table for us.
$ sequelize model:create --name ResetToken --attributes email:string,token:string,expiration:date,used:integer $ sequelize db:migrate
This table has the following columns:
Email address of user,
Token that has been generated,
Expiration of that token,
Whether the token has been used or not.
In the background, sequelize-cli is running the following SQL query:
CREATE TABLE `ResetTokens` ( `id` int(11) NOT NULL AUTO_INCREMENT, `email` varchar(255) DEFAULT NULL, `token` varchar(255) DEFAULT NULL, `expiration` datetime DEFAULT NULL, `createdAt` datetime NOT NULL, `updatedAt` datetime NOT NULL, `used` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
Verify this worked properly using your SQL client or the command line:
mysql> describe ResetTokens; +------------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +------------+--------------+------+-----+---------+----------------+ | id | int(11) | NO | PRI | NULL | auto_increment | | email | varchar(255) | YES | | NULL | | | token | varchar(255) | YES | | NULL | | | expiration | datetime | YES | | NULL | | | createdAt | datetime | NO | | NULL | | | updatedAt | datetime | NO | | NULL | | | used | int(11) | NO | | 0 | | +------------+--------------+------+-----+---------+----------------+ 7 rows in set (0.00 sec)
Security Tip #3
If you’re not currently using an ORM, you should consider doing so. An ORM automates the writing and proper escaping of SQL queries, making your code more readable and more secure by default. They’ll help you avoid SQL injection attacks by properly escaping your SQL queries.
Set Up Reset Password Route
Create the get route in user.js:
router.get('/forgot-password', function(req, res, next) { res.render('user/forgot-password', { }); });
Then create the POST route, which is the route that is hit when the reset password form is posted. In the code below, I’ve included a couple of important security features.
Security Tips #4-6
Even if we don’t find an email address, we return ‘ok’ as our status. We don’t want untoward bots figuring out what emails are real vs not real in our database.
The more random bytes you use in a token, the less likely it can be hacked. We are using 64 random bytes in our token generator (do not use less than 8).
Expire the token in 1 hour. This limits the window of time the reset token works.
router.post('/forgot-password', async function(req, res, next) { //ensure that you have a user with this email var email = await User.findOne({where: { email: req.body.email }}); if (email == null) { /** * we don't want to tell attackers that an * email doesn't exist, because that will let * them use this form to find ones that do * exist. **/ return res.json({status: 'ok'}); } /** * Expire any tokens that were previously * set for this user. That prevents old tokens * from being used. **/ await ResetToken.update({ used: 1 }, { where: { email: req.body.email } }); //Create a random reset token var fpSalt = crypto.randomBytes(64).toString('base64'); //token expires after one hour var expireDate = new Date(); expireDate.setDate(expireDate.getDate() + 1/24); //insert token data into DB await ResetToken.create({ email: req.body.email, expiration: expireDate, token: token, used: 0 }); //create email const message = { from: process.env.SENDER_ADDRESS, to: req.body.email, replyTo: process.env.REPLYTO_ADDRESS, subject: process.env.FORGOT_PASS_SUBJECT_LINE, text: 'To reset your password, please click the link below.\n\nhttps://'+process.env.DOMAIN+'/user/reset-password?token='+encodeURIComponent(token)+'&email='+req.body.email }; //send email transport.sendMail(message, function (err, info) { if(err) { console.log(err)} else { console.log(info); } }); return res.json({status: 'ok'}); });
You’ll see a User variable referenced above — what is this? For the purposes of this tutorial, we’re assuming you have a User model that connects to your database to retrieve values. The code above is based on Sequelize, but you can modify as needed if you query the database directly (but I recommend Sequelize!).
We now need to generate the view. Using Bootstrap CSS, jQuery, and the pug framework built into the Node Express framework, the view looks like the following:
extends ../layout block content div.container div.row div.col h1 Forgot password p Enter your email address below. If we have it on file, we will send you a reset email. div.forgot-message.alert.alert-success(style="display:none;") Email address received. If you have an email on file we will send you a reset email. Please wait a few minutes and check your spam folder if you don't see it. form#forgotPasswordForm.form-inline(onsubmit="return false;") div.form-group label.sr-only(for="email") Email address: input.form-control.mr-2#emailFp(type='email', name='email', placeholder="Email address") div.form-group.mt-1.text-center button#fpButton.btn.btn-success.mb-2(type='submit') Send email script. $('#fpButton').on('click', function() { $.post('/user/forgot-password', { email: $('#emailFp').val(), }, function(resp) { $('.forgot-message').show(); $('#forgotPasswordForm').remove(); }); });
Here’s the form on the page:
Your reset password form. (Large preview)
At this point, you should be able to fill out the form with an email address that’s in your database, and then receive a reset password email at that address. Clicking the reset link won’t do anything yet.
Set Up “Reset Password” Route
Now let’s go ahead and set up the rest of the workflow.
Add the Sequelize.Op module to your route:
const Sequelize = require('sequelize'); const Op = Sequelize.Op;
Now let’s build the GET route for users that have clicked on that reset password link. As you’ll see below, we want to make sure we’re validating the reset token appropriately.
Security Tip #7:
Ensure you’re only looking up reset tokens that have not expired and have not been used.
For demonstration purposes, I also clear up all expired tokens on load here to keep the table small. If you have a large website, move this to a cronjob.
router.get('/reset-password', async function(req, res, next) { /** * This code clears all expired tokens. You * should move this to a cronjob if you have a * big site. We just include this in here as a * demonstration. **/ await ResetToken.destroy({ where: { expiration: { [Op.lt]: Sequelize.fn('CURDATE')}, } }); //find the token var record = await ResetToken.findOne({ where: { email: req.query.email, expiration: { [Op.gt]: Sequelize.fn('CURDATE')}, token: req.query.token, used: 0 } }); if (record == null) { return res.render('user/reset-password', { message: 'Token has expired. Please try password reset again.', showForm: false }); } res.render('user/reset-password', { showForm: true, record: record }); });
Now let’s create the POST route which is what is hit once the user fills out their new password details.
Security tip #8 through 11:
Make sure that the passwords match and meet your minimum requirements.
Check the reset token again to make sure it has not been used and has not expired. We need to check it again because the token is being sent by a user via the form.
Before resetting the password, mark the token as used. That way, if something unforeseen happens (server crash, for example), the password won’t be reset while the token is still valid.
Use a cryptographically secure random salt (in this case, we use 64 random bytes).
router.post('/reset-password', async function(req, res, next) { //compare passwords if (req.body.password1 !== req.body.password2) { return res.json({status: 'error', message: 'Passwords do not match. Please try again.'}); } /** * Ensure password is valid (isValidPassword * function checks if password is >= 8 chars, alphanumeric, * has special chars, etc) **/ if (!isValidPassword(req.body.password1)) { return res.json({status: 'error', message: 'Password does not meet minimum requirements. Please try again.'}); } var record = await ResetToken.findOne({ where: { email: req.body.email, expiration: { [Op.gt]: Sequelize.fn('CURDATE')}, token: req.body.token, used: 0 } }); if (record == null) { return res.json({status: 'error', message: 'Token not found. Please try the reset password process again.'}); } var upd = await ResetToken.update({ used: 1 }, { where: { email: req.body.email } }); var newSalt = crypto.randomBytes(64).toString('hex'); var newPassword = crypto.pbkdf2Sync(req.body.password1, newSalt, 10000, 64, 'sha512').toString('base64'); await User.update({ password: newPassword, salt: newSalt }, { where: { email: req.body.email } }); return res.json({status: 'ok', message: 'Password reset. Please login with your new password.'}); }); And again, the view: extends ../layout block content div.container div.row div.col h1 Reset password p Enter your new password below. if message div.reset-message.alert.alert-warning #{message} else div.reset-message.alert(style='display:none;') if showForm form#resetPasswordForm(onsubmit="return false;") div.form-group label(for="password1") New password: input.form-control#password1(type='password', name='password1') small.form-text.text-muted Password must be 8 characters or more. div.form-group label(for="password2") Confirm new password input.form-control#password2(type='password', name='password2') small.form-text.text-muted Both passwords must match. input#emailRp(type='hidden', name='email', value=record.email) input#tokenRp(type='hidden', name='token', value=record.token) div.form-group button#rpButton.btn.btn-success(type='submit') Reset password script. $('#rpButton').on('click', function() { $.post('/user/reset-password', { password1: $('#password1').val(), password2: $('#password2').val(), email: $('#emailRp').val(), token: $('#tokenRp').val() }, function(resp) { if (resp.status == 'ok') { $('.reset-message').removeClass('alert-danger').addClass('alert-success').show().text(resp.message); $('#resetPasswordForm').remove(); } else { $('.reset-message').removeClass('alert-success').addClass('alert-danger').show().text(resp.message); } }); });
This is what it should look like:
Your reset password form. (Large preview)
Add The Link To Your Login Page
Lastly, don’t forget to add a link to this flow from your login page! Once you do this, you should have a working reset password flow. Be sure to test thoroughly at each stage of the process to confirm everything works and your tokens have a short expiration and are marked with the correct status as the workflow progresses.
Next Steps
Hopefully this helped you on your way to coding a secure, user-friendly reset password feature.
If you are interested in learning more about cryptographic security, I recommend Wikipedia’s summary (warning, it’s dense!).
If you want to add even more security to your app’s authentication, look into 2FA. There are a lot of different options out there.
If I’ve scared you off from building your own reset password flow, you can rely on third-party login systems like Google and Facebook. PassportJS is a middleware you can use for NodeJS that implements these strategies.
(dm, yk, il)
Website Design & SEO Delray Beach by DBL07.co
Delray Beach SEO
source http://www.scpie.org/creating-secure-password-flows-with-nodejs-and-mysql/
0 notes
laurelkrugerr · 5 years ago
Text
Creating Secure Password Flows With NodeJS And MySQL
About The Author
Darshan Somashekar is a technology entrepreneur who has built & sold two startups. His latest fun project is a solitaire site called Solitaired. Previously, … More about Darshan …
Reset password functionality is table stakes for any user-friendly application. It can also be a security nightmare. Using NodeJS and MySQL, Darshan demonstrates how to successfully create a secure reset password flow so you can avoid these pitfalls.
If you’re anything like me, you’ve forgotten your password more than once, especially on sites you haven’t visited in a while. You’ve probably also seen, and/or been mortified by, reset password emails that contain your password in plain text.
Unfortunately, the reset password workflow gets short shrift and limited attention during application development. This not only can lead to a frustrating user experience, but can also leave your application with gaping security holes.
We’re going to cover how to build a secure reset password workflow. We’ll be using NodeJS and MySQL as our base components. If you’re writing using a different language, framework, or database, you can still benefit from following the general “Security Tips” outlined in each section.
A reset password flow consists of the following components:
A link to send the user to the start of the workflow.
A form that lets the user submit their email.
A lookup that validates the email and sends an email to the address.
An email that contains the reset token with an expiry that allows the user to reset their password.
A form that let’s the user generate a new password.
Saving the new password and letting the user log in again with the new password.
Besides Node, Express & MySQL, we’ll be using the following libraries:
Sequelize ORM
Nodemailer
Sequelize is a NodeJS database ORM that makes it easier to run database migrations as well as security create queries. Nodemailer is a popular NodeJS email library that we’ll use to send password reset emails.
Security Tip #1
Some articles suggest secure password flows can be designed using JSON Web Tokens (JWT), which eliminate the need for database storage (and thus are easier to implement). We don’t use this approach on our site, because JWT token secrets are usually stored right in code. We want to avoid having ‘one secret’ to rule them all (for the same reason you don’t salt passwords with the same value), and therefore need to move this information into a database.
Installation
First, install Sequelize, Nodemailer, and other associated libraries:
$ npm install --save sequelize sequelize-cli mysql crypto nodemailer
In the route where you want to include your reset workflows, add the required modules. If you need a refresher on Express and routes, check out their guide.
const nodemailer = require('nodemailer');
And configure it with your email SMTP credentials.
const transport = nodemailer.createTransport({ host: process.env.EMAIL_HOST, port: process.env.EMAIL_PORT, secure: true, auth: { user: process.env.EMAIL_USER, pass: process.env.EMAIL_PASS } });
The email solution I’m using is AWS’s Simple Email Service, but you can use anything (Mailgun, etc).
If this is your first time setting up your email sending service, you’ll need to spend some time configuring the appropriate Domain Keys and setting up authorizations. If you use Route 53 along with SES, this is super simple and done virtually automatically, which is why I picked it. AWS has some tutorials on how SES works with Route53.
Security tip #2
To store the credentials away from my code, I use dotenv, which lets me create a local .env file with my environment variables. That way, when I deploy to production, I can use different production keys that aren’t visible in code, and therefore lets me restrict permissions of my configuration to only certain members of my team.
Database Setup
Since we’re going to be sending reset tokens to users, we need to store those tokens in a database.
I am assuming you have a functioning users table in your database. If you’re using Sequelize already, great! If not, you may want to brush up on Sequelize and the Sequelize CLI.
If you haven’t used Sequelize yet in your app, you can set it up by running the command below in your app’s root folder:
$ sequelize init
This will create a number of new folders in your setup, including migrations and models.
This will also create a config file. In your config file, update the development block with the credentials to your local mysql database server.
Let’s use Sequelize’s CLI tool to generate the database table for us.
$ sequelize model:create --name ResetToken --attributes email:string,token:string,expiration:date,used:integer $ sequelize db:migrate
This table has the following columns:
Email address of user,
Token that has been generated,
Expiration of that token,
Whether the token has been used or not.
In the background, sequelize-cli is running the following SQL query:
CREATE TABLE `ResetTokens` ( `id` int(11) NOT NULL AUTO_INCREMENT, `email` varchar(255) DEFAULT NULL, `token` varchar(255) DEFAULT NULL, `expiration` datetime DEFAULT NULL, `createdAt` datetime NOT NULL, `updatedAt` datetime NOT NULL, `used` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
Verify this worked properly using your SQL client or the command line:
mysql> describe ResetTokens; +------------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +------------+--------------+------+-----+---------+----------------+ | id | int(11) | NO | PRI | NULL | auto_increment | | email | varchar(255) | YES | | NULL | | | token | varchar(255) | YES | | NULL | | | expiration | datetime | YES | | NULL | | | createdAt | datetime | NO | | NULL | | | updatedAt | datetime | NO | | NULL | | | used | int(11) | NO | | 0 | | +------------+--------------+------+-----+---------+----------------+ 7 rows in set (0.00 sec)
Security Tip #3
If you’re not currently using an ORM, you should consider doing so. An ORM automates the writing and proper escaping of SQL queries, making your code more readable and more secure by default. They’ll help you avoid SQL injection attacks by properly escaping your SQL queries.
Set Up Reset Password Route
Create the get route in user.js:
router.get('/forgot-password', function(req, res, next) { res.render('user/forgot-password', { }); });
Then create the POST route, which is the route that is hit when the reset password form is posted. In the code below, I’ve included a couple of important security features.
Security Tips #4-6
Even if we don’t find an email address, we return ‘ok’ as our status. We don’t want untoward bots figuring out what emails are real vs not real in our database.
The more random bytes you use in a token, the less likely it can be hacked. We are using 64 random bytes in our token generator (do not use less than 8).
Expire the token in 1 hour. This limits the window of time the reset token works.
router.post('/forgot-password', async function(req, res, next) { //ensure that you have a user with this email var email = await User.findOne({where: { email: req.body.email }}); if (email == null) { /** * we don't want to tell attackers that an * email doesn't exist, because that will let * them use this form to find ones that do * exist. **/ return res.json({status: 'ok'}); } /** * Expire any tokens that were previously * set for this user. That prevents old tokens * from being used. **/ await ResetToken.update({ used: 1 }, { where: { email: req.body.email } }); //Create a random reset token var fpSalt = crypto.randomBytes(64).toString('base64'); //token expires after one hour var expireDate = new Date(); expireDate.setDate(expireDate.getDate() + 1/24); //insert token data into DB await ResetToken.create({ email: req.body.email, expiration: expireDate, token: token, used: 0 }); //create email const message = { from: process.env.SENDER_ADDRESS, to: req.body.email, replyTo: process.env.REPLYTO_ADDRESS, subject: process.env.FORGOT_PASS_SUBJECT_LINE, text: 'To reset your password, please click the link below.\n\nhttps://'+process.env.DOMAIN+'/user/reset-password?token='+encodeURIComponent(token)+'&email='+req.body.email }; //send email transport.sendMail(message, function (err, info) { if(err) { console.log(err)} else { console.log(info); } }); return res.json({status: 'ok'}); });
You’ll see a User variable referenced above — what is this? For the purposes of this tutorial, we’re assuming you have a User model that connects to your database to retrieve values. The code above is based on Sequelize, but you can modify as needed if you query the database directly (but I recommend Sequelize!).
We now need to generate the view. Using Bootstrap CSS, jQuery, and the pug framework built into the Node Express framework, the view looks like the following:
extends ../layout block content div.container div.row div.col h1 Forgot password p Enter your email address below. If we have it on file, we will send you a reset email. div.forgot-message.alert.alert-success(style="display:none;") Email address received. If you have an email on file we will send you a reset email. Please wait a few minutes and check your spam folder if you don't see it. form#forgotPasswordForm.form-inline(onsubmit="return false;") div.form-group label.sr-only(for="email") Email address: input.form-control.mr-2#emailFp(type='email', name='email', placeholder="Email address") div.form-group.mt-1.text-center button#fpButton.btn.btn-success.mb-2(type='submit') Send email script. $('#fpButton').on('click', function() { $.post('/user/forgot-password', { email: $('#emailFp').val(), }, function(resp) { $('.forgot-message').show(); $('#forgotPasswordForm').remove(); }); });
Here’s the form on the page:
Your reset password form. (Large preview)
At this point, you should be able to fill out the form with an email address that’s in your database, and then receive a reset password email at that address. Clicking the reset link won’t do anything yet.
Set Up “Reset Password” Route
Now let’s go ahead and set up the rest of the workflow.
Add the Sequelize.Op module to your route:
const Sequelize = require('sequelize'); const Op = Sequelize.Op;
Now let’s build the GET route for users that have clicked on that reset password link. As you’ll see below, we want to make sure we’re validating the reset token appropriately.
Security Tip #7:
Ensure you’re only looking up reset tokens that have not expired and have not been used.
For demonstration purposes, I also clear up all expired tokens on load here to keep the table small. If you have a large website, move this to a cronjob.
router.get('/reset-password', async function(req, res, next) { /** * This code clears all expired tokens. You * should move this to a cronjob if you have a * big site. We just include this in here as a * demonstration. **/ await ResetToken.destroy({ where: { expiration: { [Op.lt]: Sequelize.fn('CURDATE')}, } }); //find the token var record = await ResetToken.findOne({ where: { email: req.query.email, expiration: { [Op.gt]: Sequelize.fn('CURDATE')}, token: req.query.token, used: 0 } }); if (record == null) { return res.render('user/reset-password', { message: 'Token has expired. Please try password reset again.', showForm: false }); } res.render('user/reset-password', { showForm: true, record: record }); });
Now let’s create the POST route which is what is hit once the user fills out their new password details.
Security tip #8 through 11:
Make sure that the passwords match and meet your minimum requirements.
Check the reset token again to make sure it has not been used and has not expired. We need to check it again because the token is being sent by a user via the form.
Before resetting the password, mark the token as used. That way, if something unforeseen happens (server crash, for example), the password won’t be reset while the token is still valid.
Use a cryptographically secure random salt (in this case, we use 64 random bytes).
router.post('/reset-password', async function(req, res, next) { //compare passwords if (req.body.password1 !== req.body.password2) { return res.json({status: 'error', message: 'Passwords do not match. Please try again.'}); } /** * Ensure password is valid (isValidPassword * function checks if password is >= 8 chars, alphanumeric, * has special chars, etc) **/ if (!isValidPassword(req.body.password1)) { return res.json({status: 'error', message: 'Password does not meet minimum requirements. Please try again.'}); } var record = await ResetToken.findOne({ where: { email: req.body.email, expiration: { [Op.gt]: Sequelize.fn('CURDATE')}, token: req.body.token, used: 0 } }); if (record == null) { return res.json({status: 'error', message: 'Token not found. Please try the reset password process again.'}); } var upd = await ResetToken.update({ used: 1 }, { where: { email: req.body.email } }); var newSalt = crypto.randomBytes(64).toString('hex'); var newPassword = crypto.pbkdf2Sync(req.body.password1, newSalt, 10000, 64, 'sha512').toString('base64'); await User.update({ password: newPassword, salt: newSalt }, { where: { email: req.body.email } }); return res.json({status: 'ok', message: 'Password reset. Please login with your new password.'}); }); And again, the view: extends ../layout block content div.container div.row div.col h1 Reset password p Enter your new password below. if message div.reset-message.alert.alert-warning #{message} else div.reset-message.alert(style='display:none;') if showForm form#resetPasswordForm(onsubmit="return false;") div.form-group label(for="password1") New password: input.form-control#password1(type='password', name='password1') small.form-text.text-muted Password must be 8 characters or more. div.form-group label(for="password2") Confirm new password input.form-control#password2(type='password', name='password2') small.form-text.text-muted Both passwords must match. input#emailRp(type='hidden', name='email', value=record.email) input#tokenRp(type='hidden', name='token', value=record.token) div.form-group button#rpButton.btn.btn-success(type='submit') Reset password script. $('#rpButton').on('click', function() { $.post('/user/reset-password', { password1: $('#password1').val(), password2: $('#password2').val(), email: $('#emailRp').val(), token: $('#tokenRp').val() }, function(resp) { if (resp.status == 'ok') { $('.reset-message').removeClass('alert-danger').addClass('alert-success').show().text(resp.message); $('#resetPasswordForm').remove(); } else { $('.reset-message').removeClass('alert-success').addClass('alert-danger').show().text(resp.message); } }); });
This is what it should look like:
Your reset password form. (Large preview)
Add The Link To Your Login Page
Lastly, don’t forget to add a link to this flow from your login page! Once you do this, you should have a working reset password flow. Be sure to test thoroughly at each stage of the process to confirm everything works and your tokens have a short expiration and are marked with the correct status as the workflow progresses.
Next Steps
Hopefully this helped you on your way to coding a secure, user-friendly reset password feature.
If you are interested in learning more about cryptographic security, I recommend Wikipedia’s summary (warning, it’s dense!).
If you want to add even more security to your app’s authentication, look into 2FA. There are a lot of different options out there.
If I’ve scared you off from building your own reset password flow, you can rely on third-party login systems like Google and Facebook. PassportJS is a middleware you can use for NodeJS that implements these strategies.
(dm, yk, il)
Website Design & SEO Delray Beach by DBL07.co
Delray Beach SEO
source http://www.scpie.org/creating-secure-password-flows-with-nodejs-and-mysql/ source https://scpie1.blogspot.com/2020/03/creating-secure-password-flows-with.html
0 notes
douglassmiith · 5 years ago
Text
Creating Secure Password Flows With NodeJS And MySQL
About The Author
Darshan Somashekar is a technology entrepreneur who has built & sold two startups. His latest fun project is a solitaire site called Solitaired. Previously, … More about Darshan …
Reset password functionality is table stakes for any user-friendly application. It can also be a security nightmare. Using NodeJS and MySQL, Darshan demonstrates how to successfully create a secure reset password flow so you can avoid these pitfalls.
If you’re anything like me, you’ve forgotten your password more than once, especially on sites you haven’t visited in a while. You’ve probably also seen, and/or been mortified by, reset password emails that contain your password in plain text.
Unfortunately, the reset password workflow gets short shrift and limited attention during application development. This not only can lead to a frustrating user experience, but can also leave your application with gaping security holes.
We’re going to cover how to build a secure reset password workflow. We’ll be using NodeJS and MySQL as our base components. If you’re writing using a different language, framework, or database, you can still benefit from following the general “Security Tips” outlined in each section.
A reset password flow consists of the following components:
A link to send the user to the start of the workflow.
A form that lets the user submit their email.
A lookup that validates the email and sends an email to the address.
An email that contains the reset token with an expiry that allows the user to reset their password.
A form that let’s the user generate a new password.
Saving the new password and letting the user log in again with the new password.
Besides Node, Express & MySQL, we’ll be using the following libraries:
Sequelize ORM
Nodemailer
Sequelize is a NodeJS database ORM that makes it easier to run database migrations as well as security create queries. Nodemailer is a popular NodeJS email library that we’ll use to send password reset emails.
Security Tip #1
Some articles suggest secure password flows can be designed using JSON Web Tokens (JWT), which eliminate the need for database storage (and thus are easier to implement). We don’t use this approach on our site, because JWT token secrets are usually stored right in code. We want to avoid having ‘one secret’ to rule them all (for the same reason you don’t salt passwords with the same value), and therefore need to move this information into a database.
Installation
First, install Sequelize, Nodemailer, and other associated libraries:
$ npm install --save sequelize sequelize-cli mysql crypto nodemailer
In the route where you want to include your reset workflows, add the required modules. If you need a refresher on Express and routes, check out their guide.
const nodemailer = require('nodemailer');
And configure it with your email SMTP credentials.
const transport = nodemailer.createTransport({ host: process.env.EMAIL_HOST, port: process.env.EMAIL_PORT, secure: true, auth: { user: process.env.EMAIL_USER, pass: process.env.EMAIL_PASS }});
The email solution I’m using is AWS’s Simple Email Service, but you can use anything (Mailgun, etc).
If this is your first time setting up your email sending service, you’ll need to spend some time configuring the appropriate Domain Keys and setting up authorizations. If you use Route 53 along with SES, this is super simple and done virtually automatically, which is why I picked it. AWS has some tutorials on how SES works with Route53.
Security tip #2
To store the credentials away from my code, I use dotenv, which lets me create a local .env file with my environment variables. That way, when I deploy to production, I can use different production keys that aren’t visible in code, and therefore lets me restrict permissions of my configuration to only certain members of my team.
Database Setup
Since we’re going to be sending reset tokens to users, we need to store those tokens in a database.
I am assuming you have a functioning users table in your database. If you’re using Sequelize already, great! If not, you may want to brush up on Sequelize and the Sequelize CLI.
If you haven’t used Sequelize yet in your app, you can set it up by running the command below in your app’s root folder:
$ sequelize init
This will create a number of new folders in your setup, including migrations and models.
This will also create a config file. In your config file, update the development block with the credentials to your local mysql database server.
Let’s use Sequelize’s CLI tool to generate the database table for us.
$ sequelize model:create --name ResetToken --attributes email:string,token:string,expiration:date,used:integer$ sequelize db:migrate
This table has the following columns:
Email address of user,
Token that has been generated,
Expiration of that token,
Whether the token has been used or not.
In the background, sequelize-cli is running the following SQL query:
CREATE TABLE `ResetTokens` ( `id` int(11) NOT NULL AUTO_INCREMENT, `email` varchar(255) DEFAULT NULL, `token` varchar(255) DEFAULT NULL, `expiration` datetime DEFAULT NULL, `createdAt` datetime NOT NULL, `updatedAt` datetime NOT NULL, `used` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
Verify this worked properly using your SQL client or the command line:
mysql> describe ResetTokens;+------------+--------------+------+-----+---------+----------------+| Field | Type | Null | Key | Default | Extra |+------------+--------------+------+-----+---------+----------------+| id | int(11) | NO | PRI | NULL | auto_increment || email | varchar(255) | YES | | NULL | || token | varchar(255) | YES | | NULL | || expiration | datetime | YES | | NULL | || createdAt | datetime | NO | | NULL | || updatedAt | datetime | NO | | NULL | || used | int(11) | NO | | 0 | |+------------+--------------+------+-----+---------+----------------+7 rows in set (0.00 sec)
Security Tip #3
If you’re not currently using an ORM, you should consider doing so. An ORM automates the writing and proper escaping of SQL queries, making your code more readable and more secure by default. They’ll help you avoid SQL injection attacks by properly escaping your SQL queries.
Set Up Reset Password Route
Create the get route in user.js:
router.get('/forgot-password', function(req, res, next) { res.render('user/forgot-password', { });});
Then create the POST route, which is the route that is hit when the reset password form is posted. In the code below, I’ve included a couple of important security features.
Security Tips #4-6
Even if we don’t find an email address, we return ‘ok’ as our status. We don’t want untoward bots figuring out what emails are real vs not real in our database.
The more random bytes you use in a token, the less likely it can be hacked. We are using 64 random bytes in our token generator (do not use less than 8).
Expire the token in 1 hour. This limits the window of time the reset token works.
router.post('/forgot-password', async function(req, res, next) { //ensure that you have a user with this email var email = await User.findOne({where: { email: req.body.email }}); if (email == null) { /** * we don't want to tell attackers that an * email doesn't exist, because that will let * them use this form to find ones that do * exist. **/ return res.json({status: 'ok'}); } /** * Expire any tokens that were previously * set for this user. That prevents old tokens * from being used. **/ await ResetToken.update({ used: 1 }, { where: { email: req.body.email } }); //Create a random reset token var fpSalt = crypto.randomBytes(64).toString('base64'); //token expires after one hour var expireDate = new Date(); expireDate.setDate(expireDate.getDate() + 1/24); //insert token data into DB await ResetToken.create({ email: req.body.email, expiration: expireDate, token: token, used: 0 }); //create email const message = { from: process.env.SENDER_ADDRESS, to: req.body.email, replyTo: process.env.REPLYTO_ADDRESS, subject: process.env.FORGOT_PASS_SUBJECT_LINE, text: 'To reset your password, please click the link below.\n\nhttps://'+process.env.DOMAIN+'/user/reset-password?token='+encodeURIComponent(token)+'&email='+req.body.email }; //send email transport.sendMail(message, function (err, info) { if(err) { console.log(err)} else { console.log(info); } }); return res.json({status: 'ok'});});
You’ll see a User variable referenced above — what is this? For the purposes of this tutorial, we’re assuming you have a User model that connects to your database to retrieve values. The code above is based on Sequelize, but you can modify as needed if you query the database directly (but I recommend Sequelize!).
We now need to generate the view. Using Bootstrap CSS, jQuery, and the pug framework built into the Node Express framework, the view looks like the following:
extends ../layout block content div.container div.row div.col h1 Forgot password p Enter your email address below. If we have it on file, we will send you a reset email. div.forgot-message.alert.alert-success(style="display:none;") Email address received. If you have an email on file we will send you a reset email. Please wait a few minutes and check your spam folder if you don't see it. form#forgotPasswordForm.form-inline(onsubmit="return false;") div.form-group label.sr-only(for="email") Email address: input.form-control.mr-2#emailFp(type='email', name='email', placeholder="Email address") div.form-group.mt-1.text-center button#fpButton.btn.btn-success.mb-2(type='submit') Send email script. $('#fpButton').on('click', function() { $.post('/user/forgot-password', { email: $('#emailFp').val(), }, function(resp) { $('.forgot-message').show(); $('#forgotPasswordForm').remove(); }); });
Here’s the form on the page:
Your reset password form. (Large preview)
At this point, you should be able to fill out the form with an email address that’s in your database, and then receive a reset password email at that address. Clicking the reset link won’t do anything yet.
Set Up “Reset Password” Route
Now let’s go ahead and set up the rest of the workflow.
Add the Sequelize.Op module to your route:
const Sequelize = require('sequelize');const Op = Sequelize.Op;
Now let’s build the GET route for users that have clicked on that reset password link. As you’ll see below, we want to make sure we’re validating the reset token appropriately.
Security Tip #7:
Ensure you’re only looking up reset tokens that have not expired and have not been used.
For demonstration purposes, I also clear up all expired tokens on load here to keep the table small. If you have a large website, move this to a cronjob.
router.get('/reset-password', async function(req, res, next) { /** * This code clears all expired tokens. You * should move this to a cronjob if you have a * big site. We just include this in here as a * demonstration. **/ await ResetToken.destroy({ where: { expiration: { [Op.lt]: Sequelize.fn('CURDATE')}, } }); //find the token var record = await ResetToken.findOne({ where: { email: req.query.email, expiration: { [Op.gt]: Sequelize.fn('CURDATE')}, token: req.query.token, used: 0 } }); if (record == null) { return res.render('user/reset-password', { message: 'Token has expired. Please try password reset again.', showForm: false }); } res.render('user/reset-password', { showForm: true, record: record });});
Now let’s create the POST route which is what is hit once the user fills out their new password details.
Security tip #8 through 11:
Make sure that the passwords match and meet your minimum requirements.
Check the reset token again to make sure it has not been used and has not expired. We need to check it again because the token is being sent by a user via the form.
Before resetting the password, mark the token as used. That way, if something unforeseen happens (server crash, for example), the password won’t be reset while the token is still valid.
Use a cryptographically secure random salt (in this case, we use 64 random bytes).
router.post('/reset-password', async function(req, res, next) { //compare passwords if (req.body.password1 !== req.body.password2) { return res.json({status: 'error', message: 'Passwords do not match. Please try again.'}); } /** * Ensure password is valid (isValidPassword * function checks if password is >= 8 chars, alphanumeric, * has special chars, etc) **/ if (!isValidPassword(req.body.password1)) { return res.json({status: 'error', message: 'Password does not meet minimum requirements. Please try again.'}); } var record = await ResetToken.findOne({ where: { email: req.body.email, expiration: { [Op.gt]: Sequelize.fn('CURDATE')}, token: req.body.token, used: 0 } }); if (record == null) { return res.json({status: 'error', message: 'Token not found. Please try the reset password process again.'}); } var upd = await ResetToken.update({ used: 1 }, { where: { email: req.body.email } }); var newSalt = crypto.randomBytes(64).toString('hex'); var newPassword = crypto.pbkdf2Sync(req.body.password1, newSalt, 10000, 64, 'sha512').toString('base64'); await User.update({ password: newPassword, salt: newSalt }, { where: { email: req.body.email } }); return res.json({status: 'ok', message: 'Password reset. Please login with your new password.'});}); And again, the view: extends ../layout block content div.container div.row div.col h1 Reset password p Enter your new password below. if message div.reset-message.alert.alert-warning #{message} else div.reset-message.alert(style='display:none;') if showForm form#resetPasswordForm(onsubmit="return false;") div.form-group label(for="password1") New password: input.form-control#password1(type='password', name='password1') small.form-text.text-muted Password must be 8 characters or more. div.form-group label(for="password2") Confirm new password input.form-control#password2(type='password', name='password2') small.form-text.text-muted Both passwords must match. input#emailRp(type='hidden', name='email', value=record.email) input#tokenRp(type='hidden', name='token', value=record.token) div.form-group button#rpButton.btn.btn-success(type='submit') Reset password script. $('#rpButton').on('click', function() { $.post('/user/reset-password', { password1: $('#password1').val(), password2: $('#password2').val(), email: $('#emailRp').val(), token: $('#tokenRp').val() }, function(resp) { if (resp.status == 'ok') { $('.reset-message').removeClass('alert-danger').addClass('alert-success').show().text(resp.message); $('#resetPasswordForm').remove(); } else { $('.reset-message').removeClass('alert-success').addClass('alert-danger').show().text(resp.message); } }); });
This is what it should look like:
Your reset password form. (Large preview)
Add The Link To Your Login Page
Lastly, don’t forget to add a link to this flow from your login page! Once you do this, you should have a working reset password flow. Be sure to test thoroughly at each stage of the process to confirm everything works and your tokens have a short expiration and are marked with the correct status as the workflow progresses.
Next Steps
Hopefully this helped you on your way to coding a secure, user-friendly reset password feature.
If you are interested in learning more about cryptographic security, I recommend Wikipedia’s summary (warning, it’s dense!).
If you want to add even more security to your app’s authentication, look into 2FA. There are a lot of different options out there.
If I’ve scared you off from building your own reset password flow, you can rely on third-party login systems like Google and Facebook. PassportJS is a middleware you can use for NodeJS that implements these strategies.
(dm, yk, il)
Website Design & SEO Delray Beach by DBL07.co
Delray Beach SEO
Via http://www.scpie.org/creating-secure-password-flows-with-nodejs-and-mysql/
source https://scpie.weebly.com/blog/creating-secure-password-flows-with-nodejs-and-mysql
0 notes
mbaljeetsingh · 6 years ago
Text
Build a Blog Using Express.js and React in 30 Minutes
Building a web server sounds complicated, but it doesn't have to be! What if I told you that you can create a web server with just a couple lines of code? Yes! You can do things like this using Express.js (the most popular web framework for Node developers).
In this tutorial you'll learn how to create a simple "My Blog" App that allows a user can create, edit, and delete a post. You'll also learn how to add authentication to the app so users have to sign in before being allowed to do any CRUD (create, read, update, delete) actions. The tutorial will use the fabulous Okta NodeJS OIDC Middleware for authentication. The back-end will use Express.js to power the server, Sequelize for storing and manipulating data, and Epilogue for automatically generating REST endpoints and controllers from the Sequelize data models.
Before you begin, head over to https://developer.okta.com/ and create an account, or log in if you’ve already signed up. The Okta service is free, and is what you'll be using to handle the user registration, login, logout, etc. It's an API service for managing user identities.
Once you've signed up for Okta, follow these steps below to configure it. This will make it easier to implement user authentication later on.:
After you log in, you will see the Org Url in the top right corner of your dashboard, save it somewhere for later use. Click Application on the navigation menu.
Click the Add Application button.
Select Web as the software, click the Next button.
Enter the following information, then click the Done button.
You will then be redirected to the general page, scroll down you will see Client ID and Client Secret values, save these somewhere for later use. These are your app's API keys that it will use to securely handle user authentication later on via the OpenID Connect protocol.
NOTE: You might be wondering why you haven't written any code and are already using an API service here. The reason is that handling user authentication yourself is actually pretty tricky. There are very few ways to do it correctly, and lots of easy ways to mess up that are non-obvious. Using a free service like Okta to handle this piece of the application will make your project code a lot simpler later on.
The way the Okta service works is by using the OAuth/OpenID Connect protocols behind the scenes to handle all of your user management. Okta handles user registration, login, password reset, multi-factor authentication, and lots of other stuff.
You should have node and npm installed, my Node version is v10.10.0 and npm version is 6.4.1. Create a project folder and basic set up with the following:
mkdir myblog cd myblog npm init
Continue selecting enter to accept all default settings.
You should now have this folder structure:
myblog └── package.json 0 directories, 1 file
Now, add two new files, index.js and .env, to your folder, so your project ends up looking like this:
myblog ├── .env ├── index.js └── package.json 0 directories, 3 files
Now you can install the required npm modules that are needed for this Express.js app. To do so, run the following commands:
npm install [email protected] --save
(module needed to start the Express web application)
npm install [email protected] --save
(module enable Cross-origin resource sharing)
npm install [email protected] --save
(module that helps to parse incoming request bodies)
npm install [email protected] --save
(module that will load our .env file into process.env variables)
npm install [email protected] --save-dev
(tool that helps automatically restart the application when file changed)
Free Node eBook
Build your first Node apps and learn server-side JavaScript.
Thank you!
You have successfully joined the Scotchy super duper web dev awesome mailing list.
These two npm modules are for Okta authentication.
npm install @okta/[email protected] [email protected] --save
In your .env file, use the Org URL, Client ID, and Client Secret you got from the Okta console to fill in the following and paste it in the file (you got them in the setup credentials steps above):
OKTA_ORG_URL={yourOktaOrgUrl} OKTA_CLIENT_ID={yourClientId} OKTA_CLIENT_SECRET={yourClientSecret} REDIRECT_URL=http://localhost:3000/authorization-code/callback RANDOM_SECRET_WORD='super secret'
NOTE: The RANDOM_SECRET_WORD setting should be a random string you type out. Just bang on the keyboard for a second to output a long random string, and use that value. This value should never be checked into source control as it is used to manage the integrity of your user sessions. It must be kept private on your web servers and should be extremely hard to guess.
In your index.js file, paste in the following code:
require('dotenv').config(); const express = require('express'); const path = require('path'); const cors = require('cors'); const bodyParser = require('body-parser'); const session = require('express-session'); const { ExpressOIDC } = require('@okta/oidc-middleware'); const app = express(); const port = 3000; // session support is required to use ExpressOIDC app.use(session({ secret: process.env.RANDOM_SECRET_WORD, resave: true, saveUninitialized: false })); const oidc = new ExpressOIDC({ issuer: `${process.env.OKTA_ORG_URL}/oauth2/default`, client_id: process.env.OKTA_CLIENT_ID, client_secret: process.env.OKTA_CLIENT_SECRET, redirect_uri: process.env.REDIRECT_URL, scope: 'openid profile', routes: { callback: { path: '/authorization-code/callback', defaultRedirect: '/admin' } } }); // ExpressOIDC will attach handlers for the /login and /authorization-code/callback routes app.use(oidc.router); app.use(cors()); app.use(bodyParser.json()); app.get('/', (req, res) => { res.send('<h1>Welcome!!</h1>'); }); app.listen(port, () => console.log(`My Blog App listening on port ${port}!`))
Let me explain what the above code does. The line that starts with app.use(session…) created session middleware with the options we passed it. This is required for ExpressOIDC's configuration. OIDC stands for OpenID Connect, it is an authentication layer on top of OAuth 2.0. You can learn more about the OpenID Connect & OAuth 2.0 API here.
The line that starts with const oidc = new ExpressOIDC(...) created an instance of ExpressOIDC with the option we passed in. It enables your application to participate in the authorization code flow by redirecting the user to Okta for authentication and handling the callback from Okta. Once the flow is completed, a local session is created and the user context is saved for the duration of the session.
The app.use(oidc.router) line is required in order for ensureAuthenticated and isAuthenticated to work. It also adds the following route:
Now that work is done, you can move on to the next step. In package.json, add "start": "./node_modules/.bin/nodemon index.js" under scripts. This will allow you to simply type npm start to start the application. nodemon is a utility that will monitor for any changes in your source and automatically reload, so you don’t have to restart your server manually.
"scripts": { "start": "nodemon index.js", "test": "echo \"Error: no test specified\" && exit 1" },
Run npm start then go to http://localhost:3000/ and you should see:
Now you have your Express.js app set up! Next you’ll integrate with OpenID Connect (OIDC) to let the user sign in.
Because we have added app.use(oidc.router) to our index.js, we got the /login route for free, which means we don't have to set it up ourselves. Anytime a request goes to /login, Okta middleware will take care for us.
Let's add a link to let user login. In index.js, replace:
app.get('/', (req, res) => { res.send('<h1>Welcome!!</h1>'); });
With:
app.get('/home', (req, res) => { res.send('<h1>Welcome!!</div><a href="/login">Login</a>'); }); app.get('/admin', (req, res) =>{ res.send('Admin page'); });
Now, when you visit http://localhost:3000/home, you should see the login link. Be aware that we added /home after http://localhost:3000.
Click on the login link, you will see the Okta login page. If you log in successfully, you will be redirected back to the URL that you provided to OIDC middleware (the defaultRedirect URL)
const oidc = new ExpressOIDC({ issuer: `${process.env.OKTA_ORG_URL}/oauth2/default`, client_id: process.env.OKTA_CLIENT_ID, client_secret: process.env.OKTA_CLIENT_SECRET, redirect_uri: process.env.REDIRECT_URL, scope: 'openid profile', routes: { callback: { path: '/callback', defaultRedirect: '/admin' } } });
In our application set up, we provided /admin as the defaultRedirect URL in the ExpressOIDC setup, so now you should see the admin page.
Add oidc.ensureAuthenticated() to the /admin route, so if someone gets the link, he/she still can't see the page without logging in.
app.get('/admin', oidc.ensureAuthenticated(), (req, res) =>{ res.send('Admin page'); })
Add the following routes after get('/admin', …), so the user can log out and when they visit other unexpected pages, we will redirect them back to home page.
app.get('/logout', (req, res) => { req.logout(); res.redirect('/home'); }); app.get('/', (req, res) => { res.redirect('/home'); });
You've now got an app built using Express.js and Okta (for OIDC), so let's move on to the next step.
We are going to use SQLite as our database for storage, and Sequelize as our ORM. We are also going to use Epilogue to generate the REST endpoints. Without Epilogue, you would need to manually setup the POST, GET, PUT, and DELETE API endpoints for blog posts manually.
Install the following modules:
And then add the following code to the top of your index.js after the require section:
const Sequelize = require('sequelize'); const epilogue = require('epilogue'), ForbiddenError = epilogue.Errors.ForbiddenError;
Add the following code to the bottom of your index.js after the routes:
const database = new Sequelize({ dialect: 'sqlite', storage: './db.sqlite', operatorsAliases: false, }); const Post = database.define('posts', { title: Sequelize.STRING, content: Sequelize.TEXT, }); epilogue.initialize({ app, sequelize: database }); const PostResource = epilogue.resource({ model: Post, endpoints: ['/posts', '/posts/:id'], }); PostResource.all.auth(function (req, res, context) { return new Promise(function (resolve, reject) { if (!req.isAuthenticated()) { res.status(401).send({ message: "Unauthorized" }); resolve(context.stop); } else { resolve(context.continue); } }) }); database.sync().then(() => { oidc.on('ready', () => { app.listen(port, () => console.log(`My Blog App listening on port ${port}!`)) }); }); oidc.on('error', err => { // An error occurred while setting up OIDC console.log("oidc error: ", err); });
You have just set up the database using Sequelize and created the REST endpoints using Epilogue. I will explain more below.
The line that starts with const database = new Sequelize(...) sets up a connection with the SQLite database and tells the database to store the data in ./db.sqlite.
The line that starts with const Post = database.define('posts'...) defines the model which represents a table in the database. We are going to store the title and the context as strings in the database.
The line epilogue.initialize({ app, sequelize: database }) initializes Epilogue with our Express.js app and the database we just set up.
The line that starts with const PostResource = epilogue.resource created the REST resource, so now we have the create, list, read, update, and delete controllers with corresponding endpoints for our post.
We also added an authentication check to all CRUD routes using the code in PostResource.all.auth section so that all endpoints are protected:
PostResource.all.auth(function (req, res, context) { return new Promise(function (resolve, reject) { if (!req.isAuthenticated()) { res.status(401).send({ message: "Unauthorized" }); resolve(context.stop); } else { resolve(context.continue); } }) });
By now, your index.js file should look like this:
require('dotenv').config(); const express = require('express'); const path = require('path'); const cors = require('cors'); const bodyParser = require('body-parser'); const session = require('express-session'); const { ExpressOIDC } = require('@okta/oidc-middleware'); const Sequelize = require('sequelize'); const epilogue = require('epilogue'), ForbiddenError = epilogue.Errors.ForbiddenError; const app = express(); const port = 3000; // session support is required to use ExpressOIDC app.use(session({ secret: process.env.RANDOM_SECRET_WORD, resave: true, saveUninitialized: false })); const oidc = new ExpressOIDC({ issuer: `${process.env.OKTA_ORG_URL}/oauth2/default`, client_id: process.env.OKTA_CLIENT_ID, client_secret: process.env.OKTA_CLIENT_SECRET, redirect_uri: process.env.REDIRECT_URL, scope: 'openid profile', routes: { callback: { path: '/authorization-code/callback', defaultRedirect: '/admin' } } }); // ExpressOIDC will attach handlers for the /login and /authorization-code/callback routes app.use(oidc.router); app.use(cors()); app.use(bodyParser.json()); app.get('/home', (req, res) => { res.send('<h1>Welcome!!</h1><a href="/login">Login</a>'); }); app.get('/admin', oidc.ensureAuthenticated(), (req, res) => { res.send('Admin page'); }); app.get('/logout', (req, res) => { req.logout(); res.redirect('/home'); }); app.get('/', (req, res) => { res.redirect('/home'); }); const database = new Sequelize({ dialect: 'sqlite', storage: './db.sqlite', operatorsAliases: false, }); const Post = database.define('posts', { title: Sequelize.STRING, content: Sequelize.TEXT, }); epilogue.initialize({ app, sequelize: database }); const PostResource = epilogue.resource({ model: Post, endpoints: ['/posts', '/posts/:id'], }); PostResource.all.auth(function (req, res, context) { return new Promise(function (resolve, reject) { if (!req.isAuthenticated()) { res.status(401).send({ message: "Unauthorized" }); resolve(context.stop); } else { resolve(context.continue); } }) }); database.sync().then(() => { oidc.on('ready', () => { app.listen(port, () => console.log(`My Blog App listening on port ${port}!`)) }); }); oidc.on('error', err => { // An error occurred while setting up OIDC console.log("oidc error: ", err); });
The backend setup is now done, but we still need to build the UI to demonstrate it's working. Let's create the UI using React.
If you are going to build a production-ready app, you should ideally install React in your repo, but since our app is for demo purposes, we'll use React via a content delivery network (CDN) instead, to keep things simple.
Create a public folder under myblog, then create the files admin.html, admin.js, home.html, home.js in the public folder. Your folder structure should look like this:
myblog ├── .env ├── index.js ├── package.json └── public ├── admin.html ├── admin.js ├── home.html └── home.js 1 directory, 7 files
Because you are using React, you’ll also use JavaScript to create the UI component. admin.js will contain the React code that we need for the admin page. This page will only be visible to the user after they log in. It will display all the blog posts and allow the user to create new posts, as well as update or delete existing posts. home.js will contain the welcome page and a login button. You are going to embed admin.js in admin.html and home.js in home.html.
Paste the following code in home.html. It will load the React JavaScript and bootstrap CSS files for the home page.
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Home Page</title> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous"> <script src="https://unpkg.com/react@16/umd/react.production.min.js" crossorigin></script> <script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js" crossorigin></script> <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> </head> <body class="bg-light"> <div class="container"> <div id="root"></div> </div> <script src="home.js" type="text/babel"></script> <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script> </body> </html>
Paste the following code in home.js. It will render the navigation menu with the Login button.
const e = React.createElement; const AppNav = () => ( <nav class="navbar navbar-dark bg-dark"> <a class="navbar-brand" href="#">My Blog</a> <a role="button" class="btn btn-outline-info navbar-btn" href="/login">Login</a> </nav> ); class Home extends React.Component { constructor(props) { super(props); } render() { return ( <div> <AppNav /> <div class="card mt-4" Style="width: 100%;"> <div class="card-body"> Please login to see your posts. </div> </div> </div> ); } } const domContainer = document.querySelector('#root'); ReactDOM.render(e(Home), domContainer);
Paste the following code in admin.html. This will load the necessary files for React and Bootstrap in the admin page.
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Admin Page</title> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous"> <script src="https://unpkg.com/react@16/umd/react.production.min.js" crossorigin></script> <script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js" crossorigin></script> <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> </head> <body class="bg-light"> <div class="container"> <div id="root"></div> </div> <script src="admin.js" type="text/babel"></script> <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script> </body> </html>
Paste the following code into admin.js. It will render the navigation menu with the Logout button. It will also render the Add New Post button.
'use strict'; const e = React.createElement; const AppNav = () => ( <nav class="navbar navbar-dark bg-dark"> <a class="navbar-brand" href="#">My Blog</a> <a role="button" class="btn btn-outline-info navbar-btn" href="/logout">Logout</a> </nav> ); const Card = ({ item, handleSubmit, handleEdit, handleDelete, handleCancel }) => { const { title, content, editMode } = item; if (editMode) { return ( <div class="card mt-4" Style="width: 100%;"> <div class="card-body"> <form onSubmit={handleSubmit}> <input type="hidden" name="id" value={item.id} /> <div class="input-group input-group-sm mb-3"> <input type="text" name="title" class="form-control" placeholder="Title" defaultValue={title} /> </div> <div class="input-group input-group-sm mb-3"> <textarea name="content" class="form-control" placeholder="Content" defaultValue={content}></textarea> </div> <button type="button" class="btn btn-outline-secondary btn-sm" onClick={handleCancel}>Cancel</button> <button type="submit" class="btn btn-info btn-sm ml-2">Save</button> </form> </div> </div> ) } else { return ( <div class="card mt-4" Style="width: 100%;"> <div class="card-body"> <h5 class="card-title">{title || "No Title"}</h5> <p class="card-text">{content || "No Content"}</p> <button type="button" class="btn btn-outline-danger btn-sm" onClick={handleDelete}>Delete</button> <button type="submit" class="btn btn-info btn-sm ml-2" onClick={handleEdit}>Edit</button> </div> </div> ) } } class Admin extends React.Component { constructor(props) { super(props); this.state = { data: [] }; } componentDidMount() { this.getPosts(); } getPosts = async () => { const response = await fetch('/posts'); const data = await response.json(); data.forEach(item => item.editMode = false); this.setState({ data }) } addNewPost = () => { const data = this.state.data; data.unshift({ editMode: true, title: "", content: "" }) this.setState({ data }) } handleCancel = async () => { await this.getPosts(); } handleEdit = (postId) => { const data = this.state.data.map((item) => { if (item.id === postId) { item.editMode = true; } return item; }); this.setState({ data }); } handleDelete = async (postId) => { await fetch(`/posts/${postId}`, { method: 'DELETE', headers: { 'content-type': 'application/json', accept: 'application/json', }, }); await this.getPosts(); } handleSubmit = async (event) => { event.preventDefault(); const data = new FormData(event.target); const body = JSON.stringify({ title: data.get('title'), content: data.get('content'), }); const headers = { 'content-type': 'application/json', accept: 'application/json', }; if (data.get('id')) { await fetch(`/posts/${data.get('id')}`, { method: 'PUT', headers, body, }); } else { await fetch('/posts', { method: 'POST', headers, body, }); } await this.getPosts(); } render() { return ( <div> <AppNav /> <button type="button" class="mt-4 mb-2 btn btn-primary btn-sm float-right" onClick={this.addNewPost}> Add New Post </button> { this.state.data.length > 0 ? ( this.state.data.map(item => <Card item={item} handleSubmit={this.handleSubmit} handleEdit={this.handleEdit.bind(this, item.id)} handleDelete={this.handleDelete.bind(this, item.id)} handleCancel={this.handleCancel} />) ) : ( <div class="card mt-5 col-sm"> <div class="card-body">You don't have any posts. Use the "Add New Post" button to add some new posts!</div> </div> ) } </div > ); } } const domContainer = document.querySelector('#root'); ReactDOM.render(e(Admin), domContainer);
I have attached a handler function to each button. For example, for the Save button, I have attached the handleSubmit function. With this function, when the user clicks the Save button in a card, it checks whether the current card has an ID to tell if it’s a new post or the user is trying to update an existing post. If it’s a new post, it will make a POST API call to the /posts API with the title and the context as POST body. If it’s an existing post, it will make a PUT API call to the /posts/:id API with the updated title and context as PUT body.
NOTE: It’s important to add a handler function to each button because when the user clicks the button, some actions should be triggered, and you want to execute these actions in the handler function.
Now you need to update your index.js file to use admin.html whenever a user visits /admin and use home.html when a user visits /home.
Add this line after the line app.use(bodyParser.json()):
app.use(express.static(path.join(__dirname, 'public')));
Update the app.get('/home', ...) and app.get('/admin', ...) route to:
app.get('/home', (req, res) => { res.sendFile(path.join(__dirname, './public/home.html')); }); app.get('/admin', oidc.ensureAuthenticated(), (req, res) => { res.sendFile(path.join(__dirname, './public/admin.html')); });
Now, if you go to http://localhost:3000/home, run npm start and log in, you should able to add new posts, update, and delete existing posts!
Woohoo! You have now built a fully functioning single page blog app, connected it to a REST API server, and secured it with authentication via Okta’s ExpressOIDC. You can hopefully see here how easy it is to implement the authorization code flow using Okta’s oidc-middleware library.
I hope you found this post helpful. If you want to learn more about Node.js, Express.js, or React, there are many great posts on the Okta developer blog. Here are a few to get you started:
Andddddd. If you liked this post, please tweet us and let us know! We love hearing from you! =)
via Scotch.io http://bit.ly/2D91inl
0 notes
t-baba · 7 years ago
Photo
Tumblr media
Build a Simple API Service with Express and GraphQL
This article was originally published on the Okta developer blog. Thank you for supporting the partners who make SitePoint possible.
GraphQL has become an immensely popular alternative to REST APIs. The flexibility you get from using GraphQL makes it easier for developers to get any information they need for an app, and just the information they need for that portion of the app. That gives you the feel of a very customized API and can help cut down on bandwidth.
In this tutorial, I’ll show you how to write a custom GraphQL API using Node and Express. I’ll also show you how to secure parts of the API while making other parts open to the public.
Create the GraphQL API with Express
To create the API, start by creating a new folder and creating a package.json file to manage your dependencies. You’ll also need to install a few dependencies to get GraphQL with Express up and running:
mkdir graphql-express cd graphql-express npm init -y npm install [email protected] [email protected] [email protected] [email protected] [email protected]
Now create a file named index.js. This will be your main entry point:
const express = require('express') const cors = require('cors') const graphqlHTTP = require('express-graphql') const gql = require('graphql-tag') const { buildASTSchema } = require('graphql') const app = express() app.use(cors()) const schema = buildASTSchema(gql` type Query { hello: String } `) const rootValue = { hello: () => 'Hello, world' } app.use('/graphql', graphqlHTTP({ schema, rootValue })) const port = process.env.PORT || 4000 app.listen(port) console.log(`Running a GraphQL API server at localhost:${port}/graphql`)
This is about as simple as a GraphQL server gets. All this does is return “Hello, world” when you query “hello”, but it’s a start. To take it for a test spin, run node ., then in another tab open your browser to the GraphQL Playground. Once there, enter http://localhost:4000/graphql to access your GraphQL server.
The GraphQL Playground will help explore your schema and test out queries. It even automatically creates some documentation for you.
Try querying for hello using the following query:
query { hello }
Improve Your GraphQL Developer Experience
Here are a couple quick tips to help make your development experience a little better:
1. Install a linter to help catch bugs in your editor. This will help keep your styling consistent and catch any easily-avoidable bugs.
To install StandardJS, type npm install --save-dev [email protected]. Most editors will be able to show you warnings and errors as you type.
You can also edit the scripts object of your package.json so that you can run the linter at any time with npm test:
"scripts": { "test": "standard" },
2. Automatically restart the server when you make changes.
Install nodemon with npm install --save-dev [email protected].
Add another script to package.json, so you can run the server with npm start. Combined with the above, your scripts object should look like this:
"scripts": { "test": "standard", "start": "nodemon ." },
Go ahead and close the server you had run with node . and now type npm start to restart the development server. From now on, any changes you make will automatically restart the server.
Create the GraphQL Queries
To get something a little more useful, let’s make a post editor. GraphQL is strongly typed, allowing you to create a type for each object and connect them. A common scenario might be to have a post with some text, that was written by a person. Update your schema to include these types. You can also update your Query type to utilize these new types.
type Query { posts: [Post] post(id: ID): Post authors: [Person] author(id: ID): Person } type Post { id: ID author: Person body: String } type Person { id: ID posts: [Post] firstName: String lastName: String }
Even though the resolvers aren’t set up, you can already go back to GraphQL Playground and refresh the schema by clicking the circular arrow icon next to the localhost URL.
The schema explorer is really useful for figuring out how to create your query. Click the green SCHEMA button to check out your new schema.
You’ll need some way to store the data. To keep it simple, use JavaScript’s Map object for in-memory storage. You can also create some classes that will help connect the data from one object to another.
const PEOPLE = new Map() const POSTS = new Map() class Post { constructor (data) { Object.assign(this, data) } get author () { return PEOPLE.get(this.authorId) } } class Person { constructor (data) { Object.assign(this, data) } get posts () { return [...POSTS.values()].filter(post => post.authorId === this.id) } }
Now if you have an instance of a Person, you can find all of their posts by simply asking for person.posts. Since GraphQL lets you only ask for the data you want, the posts getter will never get called unless you ask for it, which could speed up the query if that’s an expensive operation.
You’ll also need to update your resolvers (the functions in rootValue) in order to accommodate these new types.
const rootValue = { posts: () => POSTS.values(), post: ({ id }) => POSTS.get(id), authors: () => PEOPLE.values(), author: ({ id }) => PEOPLE.get(id) }
This is great, but there’s no data yet. For now, stub in some fake data. You can add this function and the call to it right after the assignment to rootValue.
const initializeData = () => { const fakePeople = [ { id: '1', firstName: 'John', lastName: 'Doe' }, { id: '2', firstName: 'Jane', lastName: 'Doe' } ] fakePeople.forEach(person => PEOPLE.set(person.id, new Person(person))) const fakePosts = [ { id: '1', authorId: '1', body: 'Hello world' }, { id: '2', authorId: '2', body: 'Hi, planet!' } ] fakePosts.forEach(post => POSTS.set(post.id, new Post(post))) } initializeData()
Now that you have your queries all set up and some data stubbed in, go back to GraphQL Playground and play around a bit. Try getting all the posts, or get all the authors and posts associated with each one.
Or get weird and get a single post by id, then the author for that post, and all of that author’s posts (including the one you just queried).
Add User Authentication to Your Express + GraphQL API
One simple way to add authentication to your project is with Okta. Okta is a cloud service that allows developers to create, edit, and securely store user accounts and user account data, and connect them with one or multiple applications. If you don’t already have one, sign up for a forever-free developer account.
You’re going to need to save some information to use in the app. Create a new file named .env. In it, enter in your organization URL.
HOST_URL=http://localhost:4000 OKTA_ORG_URL=https://{yourOktaOrgUrl}
You will also need a random string to use as an App Secret for sessions. You can generate this with the following command:
echo "APP_SECRET=`openssl rand -base64 32`" >> .env
Next, log in to your developer console, navigate to Applications, then click Add Application. Select Web, then click Next.
The page you come to after creating an application has some more information you need to save to your .env file. Copy in the client ID and client secret.
OKTA_CLIENT_ID={yourClientId} OKTA_CLIENT_SECRET={yourClientSecret}
The last piece of information you need from Okta is an API token. In your developer console, navigate to API -> Tokens, then click on Create Token. You can have many tokens, so just give this one a name that reminds you what it’s for, like “GraphQL Express”. You’ll be given a token that you can only see right now. If you lose the token, you’ll have to create another one. Add this to .env also.
OKTA_TOKEN={yourOktaAPIToken}
Create a new file named okta.js. This is where you’ll create some utility functions, as well as get the app initialized for Okta. When authenticated through Okta, your app will authenticate through an access token using JWT. You can use this to determine who a user is. To avoid dealing directly with authentication in your app, a user would sign in on Okta’s servers, then send you a JWT that you can verify.
okta.js
const session = require('express-session') const OktaJwtVerifier = require('@okta/jwt-verifier') const verifier = new OktaJwtVerifier({ clientId: process.env.OKTA_CLIENT_ID, issuer: `${process.env.OKTA_ORG_URL}/oauth2/default` }) const { Client } = require('@okta/okta-sdk-nodejs') const client = new Client({ orgUrl: process.env.OKTA_ORG_URL, token: process.env.OKTA_TOKEN }) const { ExpressOIDC } = require('@okta/oidc-middleware') const oidc = new ExpressOIDC({ issuer: `${process.env.OKTA_ORG_URL}/oauth2/default`, client_id: process.env.OKTA_CLIENT_ID, client_secret: process.env.OKTA_CLIENT_SECRET, redirect_uri: `${process.env.HOST_URL}/authorization-code/callback`, scope: 'openid profile' }) const initializeApp = (app) => { app.use(session({ secret: process.env.APP_SECRET, resave: true, saveUninitialized: false })) app.use(oidc.router) app.use('/access-token', oidc.ensureAuthenticated(), async (req, res, next) => { res.send(req.userContext.tokens.access_token) }) } module.exports = { client, verifier, initializeApp }
The initializeApp function adds some middleware to allow you to log in with Okta. Whenever you go to the http://localhost:4000/access-token, it will first check that you’re logged in. If you aren’t, it will first send you to Okta’s servers to authenticate. Once authentication is successful, it returns you to the /access-token route and will print out your current access token, which will be valid for about an hour.
The client that you’re exporting allows you to run some administrative calls on your server. You’ll be using it later to get more information about a user based on their ID.
the verifier is what you use to verify that a JWT is valid, and it gives you some basic information about a user, like their user ID and email address.
Now, in index.js, you’ll need to import this file and call the initializeApp function. You also need to use a tool called dotenv that will read your .env file and add the variables to process.env. At the very top of the file, add the following line:
require('dotenv').config({ path: '.env' })
Just after the app.use(cors()) line, add the following:
const okta = require('./okta') okta.initializeApp(app)
To make this all work, you’ll also need to install a few new dependencies:
You should now be able to go to http://localhost:4000/access-token to log in and get an access token. If you were just at your developer console, you’ll probably find you’re already logged in. You can log out of your developer console to ensure the flow works properly.
Create GraphQL Mutations
Now it’s time to use real data. There may be some real John and Jane Does out there, but chances are they don’t have an account on your application yet. Next, I’ll show you how to add some mutations that will use your current user to create, edit, or delete a post.
To generate IDs for a post, you can use uuid. Install it with npm install [email protected], then add it to index.js with:
const uuid = require('uuid/v4')
That should go near the top of the file, next to the other require statements.
While still in index.js, add the following types to your schema:
The post Build a Simple API Service with Express and GraphQL appeared first on SitePoint.
by Braden Kelley via SitePoint https://ift.tt/2QpUoxA
0 notes
codeonedigest · 2 years ago
Video
youtube
Reading Environment Variable in Nodejs JavaScript Application Using dote... Full Video Link                https://youtu.be/dxrNopL1sbQHello friends, new #video on #reading #accessing #environmentvariables in #nodejs #projeect #application #tutorial #examples is published on #codeonedigest #youtube channel.  @java #java #aws #awscloud @awscloud @AWSCloudIndia #salesforce #Cloud #CloudComputing @YouTube #youtube #azure #msazure  #codeonedigest @codeonedigest  #environmentvariables #nodejs #dotenv #setenvironmentvariables #linuxenvironmentvariables #pythonenvironmentvariables #dotenvnodejs #dotenvinnodejs #dotenvtutorial #dotenvtutorialnodejs #dotenvexample #dotenvmodule #dotenvmoduleinnodejs #dotenvmodulenotfound #dotenvpackage #dotenvpackagenodejs #dotenvpackage.json #dotenvpackageinstall #nodejsjavascript #environmentvariable #nodejsjavascripttutorial #javascriptenvironmentvariable #javascript
1 note · View note
mbaljeetsingh · 7 years ago
Text
Build a Simple API Service with Express and GraphQL
This article was originally published on the Okta developer blog. Thank you for supporting the partners who make SitePoint possible.
GraphQL has become an immensely popular alternative to REST APIs. The flexibility you get from using GraphQL makes it easier for developers to get any information they need for an app, and just the information they need for that portion of the app. That gives you the feel of a very customized API and can help cut down on bandwidth.
In this tutorial, I’ll show you how to write a custom GraphQL API using Node and Express. I’ll also show you how to secure parts of the API while making other parts open to the public.
Create the GraphQL API with Express
To create the API, start by creating a new folder and creating a package.json file to manage your dependencies. You’ll also need to install a few dependencies to get GraphQL with Express up and running:
mkdir graphql-express cd graphql-express npm init -y npm install [email protected] [email protected] [email protected] [email protected] [email protected]
Now create a file named index.js. This will be your main entry point:
const express = require('express') const cors = require('cors') const graphqlHTTP = require('express-graphql') const gql = require('graphql-tag') const { buildASTSchema } = require('graphql') const app = express() app.use(cors()) const schema = buildASTSchema(gql` type Query { hello: String } `) const rootValue = { hello: () => 'Hello, world' } app.use('/graphql', graphqlHTTP({ schema, rootValue })) const port = process.env.PORT || 4000 app.listen(port) console.log(`Running a GraphQL API server at localhost:${port}/graphql`)
This is about as simple as a GraphQL server gets. All this does is return “Hello, world” when you query “hello”, but it’s a start. To take it for a test spin, run node ., then in another tab open your browser to the GraphQL Playground. Once there, enter http://localhost:4000/graphql to access your GraphQL server.
The GraphQL Playground will help explore your schema and test out queries. It even automatically creates some documentation for you.
Try querying for hello using the following query:
query { hello }
Improve Your GraphQL Developer Experience
Here are a couple quick tips to help make your development experience a little better:
1. Install a linter to help catch bugs in your editor. This will help keep your styling consistent and catch any easily-avoidable bugs.
To install StandardJS, type npm install --save-dev [email protected]. Most editors will be able to show you warnings and errors as you type.
You can also edit the scripts object of your package.json so that you can run the linter at any time with npm test:
"scripts": { "test": "standard" },
2. Automatically restart the server when you make changes.
Install nodemon with npm install --save-dev [email protected].
Add another script to package.json, so you can run the server with npm start. Combined with the above, your scripts object should look like this:
"scripts": { "test": "standard", "start": "nodemon ." },
Go ahead and close the server you had run with node . and now type npm start to restart the development server. From now on, any changes you make will automatically restart the server.
Create the GraphQL Queries
To get something a little more useful, let’s make a post editor. GraphQL is strongly typed, allowing you to create a type for each object and connect them. A common scenario might be to have a post with some text, that was written by a person. Update your schema to include these types. You can also update your Query type to utilize these new types.
type Query { posts: [Post] post(id: ID): Post authors: [Person] author(id: ID): Person } type Post { id: ID author: Person body: String } type Person { id: ID posts: [Post] firstName: String lastName: String }
Even though the resolvers aren’t set up, you can already go back to GraphQL Playground and refresh the schema by clicking the circular arrow icon next to the localhost URL.
The schema explorer is really useful for figuring out how to create your query. Click the green SCHEMA button to check out your new schema.
You’ll need some way to store the data. To keep it simple, use JavaScript’s Map object for in-memory storage. You can also create some classes that will help connect the data from one object to another.
const PEOPLE = new Map() const POSTS = new Map() class Post { constructor (data) { Object.assign(this, data) } get author () { return PEOPLE.get(this.authorId) } } class Person { constructor (data) { Object.assign(this, data) } get posts () { return [...POSTS.values()].filter(post => post.authorId === this.id) } }
Now if you have an instance of a Person, you can find all of their posts by simply asking for person.posts. Since GraphQL lets you only ask for the data you want, the posts getter will never get called unless you ask for it, which could speed up the query if that’s an expensive operation.
You’ll also need to update your resolvers (the functions in rootValue) in order to accommodate these new types.
const rootValue = { posts: () => POSTS.values(), post: ({ id }) => POSTS.get(id), authors: () => PEOPLE.values(), author: ({ id }) => PEOPLE.get(id) }
This is great, but there’s no data yet. For now, stub in some fake data. You can add this function and the call to it right after the assignment to rootValue.
const initializeData = () => { const fakePeople = [ { id: '1', firstName: 'John', lastName: 'Doe' }, { id: '2', firstName: 'Jane', lastName: 'Doe' } ] fakePeople.forEach(person => PEOPLE.set(person.id, new Person(person))) const fakePosts = [ { id: '1', authorId: '1', body: 'Hello world' }, { id: '2', authorId: '2', body: 'Hi, planet!' } ] fakePosts.forEach(post => POSTS.set(post.id, new Post(post))) } initializeData()
Now that you have your queries all set up and some data stubbed in, go back to GraphQL Playground and play around a bit. Try getting all the posts, or get all the authors and posts associated with each one.
Or get weird and get a single post by id, then the author for that post, and all of that author’s posts (including the one you just queried).
Add User Authentication to Your Express + GraphQL API
One simple way to add authentication to your project is with Okta. Okta is a cloud service that allows developers to create, edit, and securely store user accounts and user account data, and connect them with one or multiple applications. If you don’t already have one, sign up for a forever-free developer account.
You’re going to need to save some information to use in the app. Create a new file named .env. In it, enter in your organization URL.
HOST_URL=http://localhost:4000 OKTA_ORG_URL=https://{yourOktaOrgUrl}
You will also need a random string to use as an App Secret for sessions. You can generate this with the following command:
echo "APP_SECRET=`openssl rand -base64 32`" >> .env
Next, log in to your developer console, navigate to Applications, then click Add Application. Select Web, then click Next.
The page you come to after creating an application has some more information you need to save to your .env file. Copy in the client ID and client secret.
OKTA_CLIENT_ID={yourClientId} OKTA_CLIENT_SECRET={yourClientSecret}
The last piece of information you need from Okta is an API token. In your developer console, navigate to API -> Tokens, then click on Create Token. You can have many tokens, so just give this one a name that reminds you what it’s for, like “GraphQL Express”. You’ll be given a token that you can only see right now. If you lose the token, you’ll have to create another one. Add this to .env also.
OKTA_TOKEN={yourOktaAPIToken}
Create a new file named okta.js. This is where you’ll create some utility functions, as well as get the app initialized for Okta. When authenticated through Okta, your app will authenticate through an access token using JWT. You can use this to determine who a user is. To avoid dealing directly with authentication in your app, a user would sign in on Okta’s servers, then send you a JWT that you can verify.
okta.js
const session = require('express-session') const OktaJwtVerifier = require('@okta/jwt-verifier') const verifier = new OktaJwtVerifier({ clientId: process.env.OKTA_CLIENT_ID, issuer: `${process.env.OKTA_ORG_URL}/oauth2/default` }) const { Client } = require('@okta/okta-sdk-nodejs') const client = new Client({ orgUrl: process.env.OKTA_ORG_URL, token: process.env.OKTA_TOKEN }) const { ExpressOIDC } = require('@okta/oidc-middleware') const oidc = new ExpressOIDC({ issuer: `${process.env.OKTA_ORG_URL}/oauth2/default`, client_id: process.env.OKTA_CLIENT_ID, client_secret: process.env.OKTA_CLIENT_SECRET, redirect_uri: `${process.env.HOST_URL}/authorization-code/callback`, scope: 'openid profile' }) const initializeApp = (app) => { app.use(session({ secret: process.env.APP_SECRET, resave: true, saveUninitialized: false })) app.use(oidc.router) app.use('/access-token', oidc.ensureAuthenticated(), async (req, res, next) => { res.send(req.userContext.tokens.access_token) }) } module.exports = { client, verifier, initializeApp }
The initializeApp function adds some middleware to allow you to log in with Okta. Whenever you go to the http://localhost:4000/access-token, it will first check that you’re logged in. If you aren’t, it will first send you to Okta’s servers to authenticate. Once authentication is successful, it returns you to the /access-token route and will print out your current access token, which will be valid for about an hour.
The client that you’re exporting allows you to run some administrative calls on your server. You’ll be using it later to get more information about a user based on their ID.
the verifier is what you use to verify that a JWT is valid, and it gives you some basic information about a user, like their user ID and email address.
Now, in index.js, you’ll need to import this file and call the initializeApp function. You also need to use a tool called dotenv that will read your .env file and add the variables to process.env. At the very top of the file, add the following line:
require('dotenv').config({ path: '.env' })
Just after the app.use(cors()) line, add the following:
const okta = require('./okta') okta.initializeApp(app)
To make this all work, you’ll also need to install a few new dependencies:
You should now be able to go to http://localhost:4000/access-token to log in and get an access token. If you were just at your developer console, you’ll probably find you’re already logged in. You can log out of your developer console to ensure the flow works properly.
Create GraphQL Mutations
Now it’s time to use real data. There may be some real John and Jane Does out there, but chances are they don’t have an account on your application yet. Next, I’ll show you how to add some mutations that will use your current user to create, edit, or delete a post.
To generate IDs for a post, you can use uuid. Install it with npm install [email protected], then add it to index.js with:
const uuid = require('uuid/v4')
That should go near the top of the file, next to the other require statements.
While still in index.js, add the following types to your schema:
The post Build a Simple API Service with Express and GraphQL appeared first on SitePoint.
via SitePoint https://ift.tt/2DUdVF0
0 notes