Ever wondered how platforms like Google, Facebook, Twitter, GitHub, etc. provide OAuth functionality. How the OAuth functions, behind the scenes. What happens when you click or button. If yes, then this article is for you. Sign in with Google Sign in with Facebook Let’s start by understanding what is: OAuth OAuth is an authorization framework. It enables applications to get limited access to user accounts on an HTTP service. These services include Google, GitHub, Twitter, etc. OAuth delegates the user authentication to the service that hosts the user account. It authorizes the third-party applications to access the user account. OAuth 2 provides authorization flows for web and desktop applications, and mobile devices. I have implemented several OAuth Providers in my applications. I always wondered how can I do the same for the Application Server I created. Why do I have to create a new authentication server every time whenever I create a new application? How can I use the same information user provided in my previous application? This article answers all these questions. I have written this article taking three college students as a reference. They are: — OAuth Provider John — Third-Party Application Developer (Client Application) Kevin — User of Kevin’s Application Alex has developed an application( ) for the college administration. It helps them manage the students and staff data. This application has an account for each member of the college and they can log in and edit their details. John CollegeERP wants to create an app . A college-specific version of . He wants to keep the usage of the app limited to his college students. thought if he could get the details from the Kevin CollegeMeet Tinder Kevin CollegeERP. This would make his task would be much simplified. He could guarantee the usage of the app within the college. He contacts and asks for assistance. John After assessing came up with 2 approaches. First was, he could create a to authenticate to the server. John Rest API CollegeERP can authenticate his users to his application using this API. Kevin CollegeMeet But there were certain to this: downsides It would not be safe to share the API with different students making multiple apps. The client application will take the raw username and password from the user and can save it for future use. Hence leading to password leak. The management and staff account is at risk. was aware of these downsides, so he came up with a second approach. This is where comes into the picture. He decided to add OAuth capability to his application. It will allow app to . It asks the user for his consent. John OAuth CollegeERP Kevin’s authorize on the user’s behalf Once the user approves, Kevin’s app can access information from the CollegeERP. Benefits of OAuth Authorization Framework: Users enter their credentials only on the CollegeERP login page. Hence less to no risk of password leaks.The user consents to the authorization request. If the user declines, the app cannot access the user’s data.It provides limited access to data.Single point of user data update for all third-party applications. CollegeMeet The sequence diagram below provides a generalized overview of the OAuth Flow. I have generalized this based on multiple OAuth Providers. This approach is very similar to what Google uses. Most of the resources on the internet are on how to integrate a certain OAuth provider(Google OAuth 2). The primary focus of this article is on . creating an OAuth Provider For this article, would be creating a simulator. This simulator provides functionality taking minimal user information. He would then add the Project/Application creation functionality. Finally, he would add OAuth Functionality to allow registered and authorized apps(e.g. ) to access user data stored on my server. John CollegeERP register/login CollegeMeet Initial Setup mkdir OAuth2 cd OAuth2 npm init -y npm i express mongoose dotenv body-parser bcryptjs crypto jsonwebtoken ramda The OAuth Flow has 3 simple steps: Kevin and Alex Register to CollegeERP Kevin registers the CollegeMeet app on CollegeERP Alex logs into CollegeMeet by clicking on Sign-in with CollegeERP Folder Structure path = ( ); bodyParser = ( ); express = ( ); app = express(); publicPath = path.join(__dirname, ); userRouter = ( ); projectRouter = ( ); OAuthRouter = ( ); app.use(bodyParser.urlencoded({ : })); app.use(bodyParser.json()); app.use( express.static(publicPath, { : [ ], }) ); app.use( { res.setHeader( , ); res.setHeader( , ); res.header( , ); res.setHeader( , ); next(); }); app.use( , userRouter); app.use( , projectRouter); app.use( , OAuthRouter); .exports = app; //app.js const require "path" const require "body-parser" const require "express" const const "../public" const require "./routes/UserRoutes" const require "./routes/ProjectRoutes" const require "./routes/OAuthRoutes" extended true extensions "html" ( ) function req, res, next "Access-Control-Allow-Origin" "*" "Access-Control-Expose-Headers" "x-auth" "Access-Control-Allow-Methods" "GET, PUT, POST, DELETE" "Access-Control-Allow-Headers" "Origin, X-Requested-With,content-type, Accept , x-auth" "/api/user" "/api/project" "/api/oauth" module app = ( ); http = ( ); server = http.createServer(app); ( ).config({ : }); mongoose = ( ); port = process.env.PORT; server.listen(port, { .log( + port); }); //index.js const require './app' const require 'http' var require 'dotenv' path './.env' const require './db/connectDB' const ( ) function console "Server running at port " Kevin and Alex Register to CollegeERP Kevin and Alex want to register for the CollegeERP app. has to provide the Authentication facility in his app for this. John is the process or action of verifying the identity of a user or process. It allows you to uniquely identify each user of your application. Authentication 3 key functionalities of an Authentication Server. a. Register/Signup b. Login c. Logout The first step to build an Authentication Server is to decide what data you want from the user. For eg. name, email, phone, gender, etc. Then decide on the type of you want to use. This includes email/username — password, phone — OTP, email — verification, etc. authentication scheme Generate a user model accordingly. John has used authentication and stored user’s name, email, and phone data. Find below his User Model file. The method names are self-explanatory. email — password He used the mechanism. A JWT is a mechanism to verify the owner of some JSON data. It’s an encoded string, which is URL safe. It can contain an unlimited amount of data (unlike a cookie), and it’s cryptographically signed. JWT authentication Never store the password as plain text in the DB. Always encrypt it. For this John has used the npm package. bcryptjs The pre-save hook in the file performs the encryption. user.js Next is creating 3 routes — Create a new user account — Login into an existing user account Logout from the current session You should any data before saving (Though John hasn’t done since this was just a prototype.) a. POST /register b. POST /login c. DELETE /logout — Note: validate The function is a middleware function. It retrieves the token from the request object header and finds the user by that token. verifyAuthToken Integrate the API with the frontend of your choice. your authentication server is ready. Hurrah!! mongoose = ( ); jwt = ( ); bcrypt = ( ); ( ).config({ : }); Schema = mongoose.Schema; UserSchema = Schema({ : { : , : , : , }, : { : , : , : , : , }, : { : , : , : , : , }, : { : , : , }, : { : , : .now }, : [ { : { : , : , }, : { : , : , }, }, ], }); UserSchema.methods.generateAuthToken = { user = ; access = ; token = jwt .sign({ : user._id.toHexString(), access }, process.env.JWT_SECRET) .toString(); user.tokens.push({ access, token }); user.save().then( { token; }); }; UserSchema.methods.generateOAuthCode = { user = ; access = ; token = jwt .sign( { access, : user._id.toHexString(), : project.projectID, : project.projectSecret, : project.scope, }, process.env.JWT_SECRET ) .toString(); user.tokens.push({ access, token }); user.save().then( { token; }); }; UserSchema.methods.generateAccessToken = { user = ; access = ; token = jwt .sign( { : user._id.toHexString(), access, scope }, process.env.JWT_SECRET ) .toString(); user.tokens.push({ access, token }); user.save().then( { token; }); }; UserSchema.statics.findByToken = { User = ; decoded; { decoded = jwt.verify(token, process.env.JWT_SECRET); } (e) { .reject({ : , : }); } user = User.findOne({ : decoded._id, : token, : access, }); { decoded, user, }; }; UserSchema.statics.findByCredentials = { User = ; User.findOne({ email }).then( { (!user) { .reject({ : , : }); } ( { bcrypt.compare(password, user.password, { (res) { resolve(user); } { reject(); } }); }); }); }; UserSchema.pre( , { user = ; (user.isModified( )) { bcrypt.genSalt( , { bcrypt.hash(user.password, salt, { user.password = hash; next(); }); }); } { next(); } }); UserSchema.methods.removeToken = { user = ; user.updateOne({ : { : { token }, }, }); }; .exports = mongoose.model( , UserSchema); /* * server/models/user.js * User Model File */ const require "mongoose" const require "jsonwebtoken" const require "bcryptjs" require "dotenv" path "./.env" var var new name type String required true trim true email type String required true unique true trim true phone type Number required true unique true trim true password type String minlength 6 createdAt type Date default Date tokens access type String required true token type String required true ( ) function var this var "auth" var _id return ( ) function return /* * This function will be used later in the article in step 3 while generating * Authorization Code. You can ignore it for now. */ ( ) function project var this var "oauth" var _id projectID projectSecret scope return ( ) function return /* * This function will be used later in the article in step 3 while exchanging access_token * for Authorization Code. You can ignore it for now. */ ( ) function scope var this var "access_token" var _id return ( ) function return async ( ) function token, access var this var try catch return Promise code 401 message "Invalid Code" await _id "tokens.token" "tokens.access" return ( ) function email, password var this return ( ) function user if return Promise code 400 message "Invalid Credentials" return new Promise ( ) function resolve, reject ( ) function err, res if else "save" ( ) function next var this if "password" 10 ( ) function err, salt ( ) function err, hash else ( ) function token var this return $pull tokens module "User" express = ( ); R = ( ); { verifyAuthToken } = ( ); projectMiddleware = ( ); User = ( ); router = express.Router(); router.route( ).post( { body = R.pick([ , , , ], req.body); user = User(body); user .save() .then( { user.generateAuthToken(); }) .then( { res.header( , token).send(R.pick([ ], user)); }) .catch( { .log(e); res.status( ).send({ : , : e }); }); }); router.route( ).post( { body = R.pick([ , ], req.body); { user = User.findByCredentials(body.email, body.password); token = user.generateAuthToken(); res.header( , token).send(R.pick([ ], user)); } (e) { .log(e); res.status( ).send({ : , : e }); } }); router.route( ).delete(verifyAuthToken, { req.user .removeToken(req.token) .then( { res.send({ : }); }) .catch( { .log(e); res.status( ).send({ : , : e }); }); }); .exports = router; /* * server/routes/UserRoutes.js * User Routes File */ const require "express" const require "ramda" const require "../middlewares/authenticate" const require "../middlewares/projectMiddleware" const require "../models/user" const "/register" ( ) function req, res var "name" "phone" "email" "password" var new ( ) function return ( ) function token "x-auth" "name" ( ) function e console 400 code 400 message "/login" async ( ) function req, res var "email" "password" try var await var await "x-auth" "name" catch console 400 code 400 message "/logout" ( ) function req, res ( ) function message "Logout Successfull" ( ) function e console 400 code 400 message module User = ( ); verifyAuthToken = { token = req.header( ); User.findByToken(token, ) .then( { (!data.user) { .reject({ : , : }); } req.user = data.user; req.token = token; next(); }) .catch( { (e.code) { res.status(e.code).send(e); } { .log(e); res.status( ).send({ : , : }); } }); }; verifyOAuthCode = { token = req.query.code; User.findByToken(token, ) .then( { (!data.user) { .reject({ : , : }); } project = req.project; decoded = data.decoded; ( decoded.projectID != project.projectID || decoded.projectSecret != project.projectSecret || decoded.scope != project.scope ) { res .status( ) .send({ : , : , }); } req.user = data.user; req.decoded = decoded; req.token = token; next(); }) .catch( { (e && e.code) { res.status(e.code).send(e); } { .log(e); res.status( ).send({ : , : }); } }); }; verifyAccessToken = { token = req.query.access_token; User.findByToken(token, ) .then( { (!data.user) { .reject({ : , : }); } req.user = data.user; req.decoded = data.decoded; req.token = token; next(); }) .catch( { (e.code) { res.status(e.code).send(e); } { .log(e); res.status( ).send({ : , : }); } }); }; .exports = { verifyAccessToken, verifyAuthToken, verifyOAuthCode }; /* * server/middlewares/authenticate.js * Middleware Functions File */ const require "../models/user" /* * This function takes the x-auth token from header, validates it, * and finds the user by that. */ var ( ) function req, res, next var "x-auth" "auth" ( ) function data if return Promise code 401 message "Invalid X-Auth Token" ( ) function e if else console 500 code 500 message "Unknown Error" /* * This function takes the code token from query, validates it, * and matches it with project data. * This function will be used in step 3 while exchanging access_token for Authorization Code. */ var ( ) function req, res, next var "oauth" ( ) function data if return Promise code 403 message "Invalid code" var var if return 400 code 403 message "The code does not belong to the project" ( ) function e if else console 500 code 500 message "Unknown Error" /* * This function takes the access_token token from query, validates it, * and find the user to which it belongs. * This function will be used in step 3 while getting user info from access_token. */ var ( ) function req, res, next var "access_token" ( ) function data if return Promise code 403 message "Invalid Access Token" ( ) function e if else console 500 code 500 message "Unknown Error" module John has used Node.js to create the Authentication Server. But you can create it in any backend language following the above steps. Note: Kevin and Alex create an account on the app. CollegeERP Kevin registers the CollegeMeet app on CollegeERP needs to provide a set of APIs for project/application creation. These applications will register/authenticate users(like ) using CollegeERP app. Using this Kevin can register CollegeMeet to use CollegeERP OAuth. John Alex John’s The first step towards this, is defining a Project Model. John has created a simple project model that contains the necessary information required. — Name of the project entered my client — Unique Project ID — — This is the code generated id. Make it unique. — Unique encrypted secret — — This is also code generated. It is a secret key and should not be exposed — The scope limits the application’s access to the user’s account — — He has added 4 scopes, you can add as many as you want, depending on your user model The URL to which our provider will redirect with code once the login is successful — — He has defined the “redirectURLs” field as an array. It provides the client, flexibility to add multiple URLs. For e.g. dev, production, etc. Name Project ID Project Secret Scope Redirect URLs: Next create following routes for the project entity. — create/register a new project/application to CollegeERP — get the list of user’s projects/applications — add a new redirectUrl to a project a. POST /project b. GET /project c. POST /project/redirectUrl The module generates the Project Secret hash. crypto Now you can add Project Forms to your UI and integrate the API mongoose = ( ); Schema = mongoose.Schema; ProjectSchema = Schema({ : { : , : , : , }, : { : , : , : , }, : { : , : , : , }, : [ { : , : , }, ], : { : , : [ , , , ], : , }, : { : Schema.ObjectId, : , }, : { : , : .now }, }); .exports = mongoose.model( , ProjectSchema); /* * server/models/project.js * Project Model File */ const require "mongoose" var var new projectID type String required true unique true projectSecret type String required true unique true name type String required true unique true redirectURLs type String required true scope type String enum "default" "email" "phone" "full" default "default" createdBy type required true createdAt type Date default Date module "Project" express = ( ); crypto = ( ); R = ( ); Project = ( ); { verifyAuthToken } = ( ); router = express.Router(); router .route( ) .post(verifyAuthToken, { data = R.pick([ , , ], req.body); data.projectID = data.name.replace( , ) + ; data.createdBy = req.user._id; hash = crypto .createHmac( , process.env.SECRET) .update(data.projectID) .digest( ); data.projectSecret = hash; project = Project(data); { project = project.save(); res.send( R.pick( [ , , , , ], project ) ); } (e) { .log(e); res.status( ).send({ : , : }); } }) .get(verifyAuthToken, { project = Project.find({ : req.user._id }); res.send(project); }) .delete(verifyAuthToken, { { project = Project.findOne({ : req.body._id, : req.user._id, }); (project) { project.remove(); res.send({ : }); } { res .status( ) .send({ : , : , }); } } (e) { res.status( ).send({ : , : }); } }); router.route( ).post(verifyAuthToken, { projectID = req.body.projectID; redirectURL = req.body.redirectURL; { project = Project.findOne({ projectID }); (project) { (project.createdBy.toHexString() != req.user._id.toHexString()) { res .status( ) .send({ : , : , }); } project.redirectURLs.push(redirectURL); project.save(); res.send( R.pick( [ , , , , ], project ) ); } { res.status( ).send({ : }); } } (e) { .log(e); res.status( ).send({ : , : }); } }); .exports = router; /* * server/routes/ProjectRoutes.js * Project Routes File */ const require "express" const require "crypto" const require "ramda" const require "../models/project" const require "../middlewares/authenticate" const "/" async ( ) function req, res var "name" "redirectURLs" "scope" /\s/g "" ".myapp.in" const "sha256" "hex" var new try var await "name" "projectID" "projectSecret" "redirectURLs" "scope" catch console 406 code 406 message "Retry with different name" async ( ) function req, res var await createdBy async ( ) function req, res try var await _id createdBy if await message "Deleted Successfully" else 403 code 403 message "You are not authorised to delete this resource" catch 500 code 500 message "Unknown Error" "/redirectUrl" async ( ) function req, res var var try var await if if return 403 code 403 message "You are not authorized to modify this project" await "name" "projectID" "projectSecret" "redirectURLs" "scope" else 400 message "Invalid Project Id" catch console 500 code 500 message "Unknown Error" module So now Kevin can sign into the app. He can create a new project, view old ones, and add Redirect URLs to old ones. Kevin creates a new project CollegeMeet and stores the credentials. CollegeERP The next is the most crucial step. Alex wants to sign into using his account. Kevin wants to access Alex’s data stored on . CollegeMeet CollegeERP CollegeERP Alex logs into CollegeMeet by clicking on Sign-in with CollegeERP: To provide this functionality John had to perform the following operations. : John had to provide Kevin a Login URL to which he can redirect to authenticate his users. He defined the following template for the login URL. Provide a Login URL https://college_erp.com/login?projectID=<projectID>&scope=<scope>&redirectURL=<redirectURL> : John needs to verify the details of the CollegeMeet app. He created an API for this. This API takes , , and to test whether this project exists. Verify Kevin’s application (GET /oauth/verifyProject) projectID scope redirectURL : John needs to ask Alex for his consent. Giving his consent Alex allows CollegeMeet to access his information on CollegeERP. Ask Alex for consent : John needs to generate an authorization code. This code is exclusive for CollegeMeet. It is a one-time use code. He designed an API This API takes the , , and as parameters. It also takes the token as a header. It then generates a JWT Authorization Code. Generate Authorization Code (GET /oauth/code). projectID scope redirectURL x-auth Now Kevin’s app has to exchange its authorization code for access_token. For this John provides an API . This API takes the , , , , and . The API returns an Kevin’s app must store this token for future use. Some providers provide access_token in the 4th step. In that case we can skip the 5th step. Retrieve Access Token: (GET /oauth/token) projectID scope redirectURL projectSecret code access_token. Note: : The last step is for what we have come so far in the article. After retrieving the wants to access Alex’s data. For this John designed an API This API takes as a parameter and returns user information. The access_token should be temporary. You should provide an extra refresh token to refresh the access_token after expiry. For the simplicity of this article, I have used non-expiring access tokens. Get user info with the access token access_token, Kevin (GET /oauth/userinfo). access_token Note: express = ( ); R = ( ); projectMiddleware = ( ); { verifyAccessToken, verifyAuthToken, verifyOAuthCode, } = ( ); router = express.Router(); scopeMapping = { : [ , , , ], : [ , ], : [ , , ], : [ , , ], }; router .route( ) .get(projectMiddleware, { res.send(R.pick([ , ], req.project)); }); router .route( ) .get(projectMiddleware, verifyAuthToken, { { code = req.user.generateOAuthCode(req.project); redirectURL = ; res.send({ redirectURL }); } (e) { .log(e); res.status( ).send({ : , : }); } }); router .route( ) .get(projectMiddleware, verifyOAuthCode, { (req.project.projectSecret != req.query.projectSecret) { res .status( ) .send({ : , : }); } user = req.user; user .generateAccessToken(req.decoded.scope) .then( { user.removeToken(req.token).then( { token; }); }) .then( { res.send({ : token }); }) .catch( { res .status( ) .send({ : }); }); }); router.route( ).get(verifyAccessToken, { token = req.decoded; user = req.user; res.send(R.pick(scopeMapping[token.scope], user)); }); .exports = router; /* * server/routes/OAuthRoutes.js * OAUth Routes File */ const require "express" const require "ramda" const require "../middlewares/projectMiddleware" const require "../middlewares/authenticate" const const full "_id" "name" "email" "phone" default "_id" "name" email "_id" "name" "email" phone "_id" "name" "phone" "/verifyproject" async ( ) function req, res "name" "scope" "/code" async ( ) function req, res try var await ` ?code= ` ${req.query.redirectURL} ${code} return catch console 500 message "Unknown Error" code 500 "/token" async ( ) function req, res if return 400 code 400 message "Mismatch ProjectID and Secret" ( ) => token return ( ) => e return ( ) => token access_token ( ) => e 400 message "Error while generating access token" "/userinfo" async ( ) function req, res module Project = ( ); projectMiddle = { projectID = req.query.projectID; redirectURL = req.query.redirectURL; scope = req.query.scope; Project.findOne({ projectID }) .then( { (!project) { .reject({ : , : , }); } (!project.redirectURLs.includes(redirectURL)) { .reject({ : , : }); } (project.scope != scope) { .reject({ : , : }); } req.project = project; next(); }) .catch( { (e.code) { res.status(e.code).send(e); } { res.status( ).send({ : }); } }); }; .exports = projectMiddle; /* * server/middlewares/projectMiddleware.js * Project Middleware File */ const require "../models/project" var ( ) function req, res, next var var var ( ) function project if return Promise code 404 message "Project ID does not exist" if return Promise code 400 message "Redirect URL mismatch" if return Promise code 400 message "Invalid Scope" ( ) function e if else 500 message "Unknown Error" module Alex comes to the CollegeMeet website, clicks on button. Then they briefly go to the CollegeERP website and land back into CollegeMeet site but authenticated. Sign in with the CollegeERP Voila!!! There it is… John has created an OAuth Provider for his CollegeERP App. Kevin has created his CollegeMeet App using this OAuth Provider. Alex can now login to CollegeMeet App using his CollegeERP account. You can find the at: . complete code https://github.com/shobhit1997/OAuth2.0 The is available at: CollegeERP Simulator http://college-erp-oauth.herokuapp.com/ The app is available at: CollegeMeet http://college-meet.surge.sh/ This app was only created for this article. The production version must have more levels of security and validations. This describes the basic structure and flow of how OAuth functions behind the scene. I tried to follow the google OAuth 2 flow as closely as possible but there is always a scope of improvement. The frontend part of this application is beyond the scope of this article. Comment, if you are interested in knowing how I implemented the frontend. I will write a separate article explaining the frontend of this application.