Devlane Blog
>
Tutorials

Node JS and Passport: Authenticate your Users

Have you ever developed code that involves authentication/authorization? Or tried to use some external login such as Facebook, Twitter or Spotify? Then you know this can be tricky and you will have to write code to manage all the necessary steps to accomplish this.In this post we are going to see how you can simplify your code using this npm library called Passport.

Matias Pruyas
by
Matias Pruyas
|
November 2022

What is it?

Passport is a library that aims to make authentication and authorization code smaller and easy to handle. It is express-compatible and it works perfectly as an authentication middleware for nodeJS applications.

How to use it

The purpose of Passport is basically to handle authorization/authentication requests, giving the developer freedom and flexibility on any logic to authenticate a user, routes or database schemas.

With Passport you can specify different ways of authenticating requests, which are called Strategies. Strategies can go from verifying credentials inside your application context to even delegating authentication to external services using OAuth or OpenId.

Example

We are going to create a simple nodejs application and write some code to authenticate a user using Spotify OAuth.

Let’s create our directory structure for the application first. Create a directory called simplexAuthExampleApp. Create a file called package.json with the following content:


{
  "name": "spotify-login",
  "version": "1.0.0",
  "description": "Passport example for Spotify",
  "main": "app.js",
  "scripts": {
    "start": "node app.js",
    "test": "echo \"Error: no test specified\"; && exit 1"
  },
  "dependencies": {
     "express": "^4.16.3",
     "request": "~2.83.0",
     "cookie-parser": "~1.4.3",
     "cors": "^2.8.4",
     "querystring": "~0.2.0"
   },
   "author": "Matias Pruyas",
   "license": "ISC"
 }

Run npm install.


Create a file called app.js with the following content:


const express = require('express')
const app = express()
const port = 3000
const bodyParser = require('body-parser')
const cors = require('cors')
const cookieParser = require('cookie-parser')

app.use(bodyParser.json())
app.use(cors())
app.use(cookieParser())

app.get('/', (req, res) => res.send('Hello Simplex readers!'))

app.listen(port, () => console.log(`App started on port: ${port}!`))

Here is the Spotify authorization guide https://developer.spotify.com/documentation/general/guides/authorization-guide/ for further understanding of how it works.

What you will need to do is create an account and follow the steps to set up an application, get a clientId, clientSecret and finally configure a redirectUri.


The flow goes like this:

  • Our application requests authorization to access data from the Spotify account service (providing the clientId, the clientId secret and the redirectUri created before);
  • The user logs in and authorizes access to the application;
  • The application gets a code which it will use to request access tokens;
  • The application gets the access token and requests user’s data.

Let’s add the code to make our application request authorization access data from the Spotify account service.

Create a folder called controllers and a new file inside the folder called spotify.js with the following content:


const request = require('request')
const querystring = require('querystring')

const clientId = 'You client id from spotify developers dashboard'
const clientIdSecret = 'You client id secret from spotify developers dashboard'
const redirectUri = 'http://localhost:3000/callback'//make you put this redirect uri on your Spotify's developer dashboard
const stateKey = 'spotify_auth_state'

const generateRandomString = function(length) {
  let text = ''
  let possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'

  for (let i = 0; i < length; i++) {
    text += possible.charAt(Math.floor(Math.random() * possible.length))
  }
  return text
}

const login = async function (req, res) {
  const state = generateRandomString(16)
  res.cookie(stateKey, state)
  const scope = `user-read-private user-read-email`

  res.redirect(`https://accounts.spotify.com/authorize?` +
    querystring.stringify({
      response_type: 'code',
      client_id: clientId,
      scope: scope,
      redirect_uri: redirectUri,
      state: state
    })
  )
}

const callback = async function (req, res, next) {
  let code = req.query.code || null
  let state = req.query.state || null
  let storedState = req.cookies ? req.cookies[stateKey] : null

  if (state === null || state !== storedState) {
    next(new Error('state_mismatch'))
  }
  
  res.clearCookie(stateKey)
  let authOptions = {
    url: `https://accounts.spotify.com/api/token`,
    form: {
      code: code,
      redirect_uri: redirectUri,
      grant_type: 'authorization_code'
    },
    headers: {
      'Authorization': 'Basic ' + (new Buffer(clientId + ':' + clientIdSecret).toString('base64'))
    },
    json: true
  }

  request.post(authOptions, function(error, response, body) {
    if (!error && response.statusCode === 200) {

      let access_token = body.access_token
      let refresh_token = body.refresh_token

      let options = {
        url: `https://api.spotify.com/v1/me`,
        headers: { 'Authorization': 'Bearer ' + access_token },
        json: true
      }

      // use the access token to access the Spotify Web API
      request.get(options, async function(error, response, body) {
        if(!!error) {
          console.log(error)
        } else {
          res.json({
            userProfile: body,
            accessToken: access_token,
            refreshToken: refresh_token 
          })
        }
      })
    } else {
      next(error)
    }
  })
}

module.exports = {
  login,
  callback
}

In this file we added two functions to handle the authentication, login and callback.

The Login function will redirect the user to the Spotify account service, also sending the clienteId, scopes and redirect uri, so that Spotify can recognize the application before giving us the chance to get an access token for a particular user.

If the user authorizes the application providing Spotify credentials, then we will receive a request on our redirectUri which we will handle in our callback function.

In our callback function the first thing we need to do is to verify that we have the code and that the state is correct.

Then, we can ask for an access token making a request to the api/token Spotify endpoint, providing the code we got from them and our clientId and clientIdSecret as access token.

Once we post that request to Spotify, we will get an access token and a refresh token which we will use to ask for the user’s information.

We will use that token to request the user’s profile info and complete the request. Let’s add the controllers to our main app and try it. Our app.js should look like this:

const express = require('express')
const app = express()
const port = 3000
const bodyParser = require('body-parser')
const cors = require('cors')
const cookieParser = require('cookie-parser')
const spotifyController = require('./controllers/spotify.js')

app.use(bodyParser.json())
app.use(cors())
app.use(cookieParser())

app.get('/login', spotifyController.login)
app.get('/callback', spotifyController.callback) 

app.get('/', (req, res) => res.send('Hello Simplex readers!'))

app.listen(port, () => console.log(`App started on port: ${port}!`))

Run npm start and open http://localhost:3000/login in your browser.Furthermore, we could add an endpoint to make use of the refresh token, so we don’t have to go through all the login process once our access token expires, but this is enough to show why passport will make our lives simpler.

Now it’s Passport time

First off, we need to add the necessary dependencies to work with passport to our packaje.json. So, let’s add passport, passport-spotify and express-session to our package.json file and then run npm install.


{
  "name": "spotify-login",
  "version": "1.0.0",
  "description": "Passport example for Spotify",
  "main": "app.js",
  "scripts": {
    "start": "node app.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "dependencies": {
    "express": "^4.16.3",
    "request": "~2.83.0",
    "cookie-parser": "~1.4.3",
    "cors": "^2.8.4",
    "querystring": "~0.2.0",
    "passport-spotify": "^1.0.1",
    "passport": "^0.4.0",
    "express-session": "^1.15.6"
  },
  "author": "Matias Pruyas",
  "license": "ISC"
}

Now, we’ll create a new folder called middleware and a new file inside it called passport.js. We will include the following content inside the file that will take care of all the functionalities we saw above.

As we have said before, passport uses strategies to authenticate requests, which can go from verifying username and password to delegating authentication to OAuth or OpenID.

In our case, we are going to use the spotify strategy to delegate to their OAuth service. Our passport.js file should look like this with the strategy:

const passport = require('passport')
const SpotifyStrategy = require('passport-spotify').Strategy
const clientId = 'Your client id'
const clientSecret = 'Your client secret'
const redirectUri = 'http://localhost:3000/callback'//make you put this redirect uri on your Spotify's developer dashboard


//serialize and deserialize user are functions to handle sessions automatically
passport.serializeUser(function(user, done) {
  done(null, user);
});

passport.deserializeUser(function(obj, done) {
  done(null, obj);
});

passport.use('spotify',
  new SpotifyStrategy(
    {
      clientID: clientId,
      clientSecret: clientSecret,
      callbackURL: redirectUri
    },
    async function(accessToken, refreshToken, expires_in, profile, done) {
      try {
        const userResponse = {
          ...profile,
          accessToken,
          refreshToken,
          expires_in
        }
        done(null, userResponse)
      } catch (err) {
        done(err, null, { message: 'An error ocurred trying to authenticate the user'})
      }
    }
  )
)

We added the strategy providing our client id and secret, along with the redirect uri, and that’s all we have to do. Passport will take care of redirecting the uri and providing the access token, refresh token and user info which we previously had to do ourselves.

After getting all this information from Spotify OAuth, we can verify whatever we need in our own database such as creating the user if it doesn’t exist, or updating the info if necessary.

We have the callback function ‘done,’ which we are going to use to indicate whether that authentication has succeeded or not. So, if the authentication has succeeded, we call the function with done(null, userResponse).

Otherwise, if the authentication has failed, we call the function with done(null, false, {message: ‘why it didn’t succeed’})If an error occurs, we call the function with done(err).

Now, let’s modify app.js file to use passport middleware. We need to include passport imports, initialize them and lastly, declare the two routes needed for OAuth authentication: one to redirect the user to the service provider and the other will be the url where the user is going to be redirected after authenticating with the provider.

const express = require('express')
const app = express()
const port = 3000
const bodyParser = require('body-parser')
const cors = require('cors')
const cookieParser = require('cookie-parser')
//passport imports
const passport = require('passport')
const session = require('express-session')
require('./middleware/passport')

app.use(bodyParser.json())
app.use(cors())
app.use(cookieParser())

//passport initialization
app.use(session({ secret: 'secret', resave: true, saveUninitialized: true }))
app.use(passport.initialize())
app.use(passport.session())

app.get('/login',
  passport.authenticate('spotify', {
    scope: ['user-read-email', 'user-read-private']
  }),
  function(req, res) {
  // The request will be redirected to spotify for authentication, so this
  // function will not be called.
})

app.get(
  '/callback',
  function(req, res, next) {
    passport.authenticate('spotify', function(err, user, info) {
      if (err) {
        return next(err)
      }
      if (!user) {
        return res.redirect('/login')
      }

      return res.json({user, info})
    })(req, res, next)
  }
)

app.get('/', (req, res) => res.send('Hello Simplex readers!'))

app.listen(port, () => console.log(`App started on port: ${port}!`))

Go ahead and run npm start to test it.

Conclusion

Taking everything into consideration, passport is simple to use, flexible and easy to integrate with frameworks like Express. You can handle not only authentication but also authorization (sessions, jwt tokens).

Its has a lot of Strategies to make use of (more than 500). For more information, you can visit http://www.passportjs.org/ and take a look at the official documentation.

Also, if you need help developing a custom solution or anything related to software development you can reach out Devlane.

Things like this is one of many things we create every day for companies that need to boost their business or create a customized solutions. You can review all our services over here or maybe you can directly contact us and ask anything you want.