DEV Community

aurel kurtula
aurel kurtula

Posted on

Working with Instagram API and PassportJS in a node application

The usual purpose of using third party authentication, other than the fact that most users now expect it, is that you do not need to handle the registration of new users.

When they arrive at your application users can authenticate themselves with their preferred social media account, by doing so they are giving you some information about themselves which you are free to store in your database.

In this tutorial we are not going to work on storing user's information on the database, we'll just explore what kind of data we'll get from Instagram API once the user has trusted us by agreeing to authenticate.

The end result is going to look something like this

The home screen is going to include just a login button, once users login with their Instagram credentials they'll see the above page populated with their information.

Setting up the application with Express

These are the only packages that we are going to use

  • express - The web framework we'll use
  • pug - The template engine
  • express-session - express middleware to create session
  • passport - the authentication middleware
  • passport-instagram - "Passport strategy for authenticating with Instagram using the OAuth 2.0 API."
  • axios - HTTP client

Let's download them all:

npm install --save express express-session passport passport-instagram axios pug
Enter fullscreen mode Exit fullscreen mode

Using the --save flag ensures that those packages are writen in the package.json file.

Let's create the basic structure for an express application. In server.js add the following code:

import express from 'express';
import session from 'express-session';
import passport from 'passport';
import Instagram from 'passport-instagram';
import axios from 'axios';
const app = express();
const port = process.env.PORT || 5656;

app.use(express.static(__dirname + '/public'));
app.set('view engine', 'pug')

app.get('/', (req,res) => {
  res.render('login')
})

app.listen(port, () => console.log(`http://localhost:${port}`))
Enter fullscreen mode Exit fullscreen mode

That's the absolute minimum, when application runs, on the homepage (/ route) the views/login.pug is rendered, the code for which looks like this.

doctype html 
html
  head
    title=title
    link(rel='stylesheet', href='/style.css')
    meta(name='viewport' content='windth=device-width, initial-scale=1')
body 
  .wrap 
    ul.provider_login 
      li 
        a(href='/auth/instagram') Login with instagram
Enter fullscreen mode Exit fullscreen mode

If you are new at express I'd recommend my tutorial on how to set up a basic website with express

Initialising passport

Passport is an authentication middleware. We have to add it as a middleware to our express application.

// express-session setup 
app.use(session({
  secret: 'sytr456-65tyrd-12wrt',
  resave: true, 
  saveUninitialized: true
}))

app.use(passport.initialize());
app.use(passport.session());

passport.serializeUser((user, done) => {
  done(null, user)
})
passport.deserializeUser((user, done) => {
  done(null, user)
})
Enter fullscreen mode Exit fullscreen mode

In the first use() method we set the express sessions.

The next two lines we initialise passport. Then with serializeUser passport gets a response (we called it user) if the authentication was a success. With done(null, user) we are passing the entire response object into the application session. We are doing so because we are simply displaying the data that comes back from instagram. If we were using passport just to authenticate users, then we would just choose to pass the user's ID to the session done(null, user.id) which we'd add to a database and so on, but for us, we want everything Instagram sends back.

deserializeUser then, simply removes the user information from the session (when a user logs out).

Setting up Instagram Strategy

There are "480+ Strategies" to pick from so it's logical that each one should be installed and setup individually and passed as middleware to passport.

We've already installed passport-instagram so lets set it up.

import Instagram from 'passport-instagram';
const InstagramStrategy = Instagram.Strategy; 
...
passport.use(new InstagramStrategy({
  clientID: "...",
  clientSecret: "....",
  callbackURL: "http://localhost:3000/auth/instagram/callback"
}, (accessToken, refreshToken, profile, done) => {
  done(null, profile)
}))
Enter fullscreen mode Exit fullscreen mode

Roughly speaking, when the user clicks on "sign in with Instagram" the above code is triggered. User is directed to Instagram in order to confirm that they want to allow us access, then they are redirected to /auth/instagram/callback. Further, some data comes back with the approved request and InstagramStrategy is passing that data to passport, which in turn injects it into the session (as we already covered passport.serializeUser((user, done) => { done(null, user) })

Creating our developer (Application) clientID and clientSecret

Make sure you, as the developer, are logged into Instagram then navigate to the area for developers and click on "register a new client" and fill in the form.

Make absolutely sure that the website URL matches your local host, and the "redirect URI" matches what we specified as callbackURL above.

After completing the registration you'll see your newly created client, you need to click on "manage" and you'll see the "Client ID" and "Client Secret" which you need to copy and paste above.

Configuring routes

The home route (/) is the login page. /auth/instagram will attempt to authenticate the user. /auth/instagram/callback is where Instagram will redirect itself when it completes authentication. /users is the landing page if the user is authenticated successfully.

app.get('/', (req, res) => {
  res.render('login')
})

app.get('/auth/instagram', passport.authenticate('instagram'))
Enter fullscreen mode Exit fullscreen mode

For the homepage we render a login.pug file. We call passport to attempt authentication when on /auth/instagram. At this point, the user is taken to the Instagram page and asked if they want to give us access. Then Instagram redirects them back to our site, at /auth/instagram/callback:

 app.get('/auth/instagram/callback', passport.authenticate('instagram', {
   successRedirect: '/users',
   failure: '/'
 })) 
Enter fullscreen mode Exit fullscreen mode

Very self explanatory, if authentication was a success we redirect the user to /users:

app.use('/users', (req,res, next) => {
  if(!req.user){
    res.redirect('/')
  }
  next()
})
app.get('/users', (req, res) => {
  res.json(req.user)
})
Enter fullscreen mode Exit fullscreen mode

To make sure the /users route is private, that no one without authentication has access to, we add a simple middleware whereby we check if the user (which would come from an Instagram authentication) exists, if not, we redirect to the home/login page. Else, we are rendering the entire response in the browser (This is useful for you to see everything you get back - I find it helpful whilst developing)

Let's make the /users page look nice

Here we are going to start doing some refactoring. When a user is authenticated we are storing the entire response in the session (and therefore is available at req.user)

passport.use(new InstagramStrategy({
  ...
}, (accessToken, refreshToken, profile, done) => {
  done(null, profile)
}))
Enter fullscreen mode Exit fullscreen mode

But we do not need to store everything that comes back. Lets instead store just what we need

passport.use(new InstagramStrategy({
  clientID: "****",
  clientSecret: "****",
  callbackURL: "http://localhost:3000/auth/instagram/callback"
}, (accessToken, refreshToken, profile, done) => {
    let user = {};
    user.name = profile.displayName;
    user.homePage = profile._json.data.website;
    user.image = profile._json.data.profile_picture;
    user.bio = profile._json.data.bio;
    user.media = `https://api.instagram.com/v1/users/${profile.id}/media/recent/?access_token=${accessToken}&count=8`

    done(null, user)  
}))
Enter fullscreen mode Exit fullscreen mode

Now we just got user's basic information. Further, in user.media we created the API endpoint which we'll later use to access the user's photos. Note, the API needs user's ID (which we have access through profile.id) and the user's access token (which we have access through accessToken). I also chose to limit the number of entries we'll get back to 8 entries.

Finally, it's the user object that's stored in the application session.

Creating the /user page

Now we are able to make a call to the Instagram API, get the 8 images back and pass them all to the instagram.pug template

app.get('/users', (req, res) => {
  axios.get(req.user.media)
  .then(function (response) {
    const data = response.data.data;
    let user = req.user;
    user.images = data.map(img => img.images);
    res.render('instagram', user)  
  })
})
Enter fullscreen mode Exit fullscreen mode

I chose to structure the views/instagram.pug like so

doctype html 
html
  head
    title=title
    link(rel='stylesheet', href='/style.css')
    meta(name='viewport' content='windth=device-width, initial-scale=1')
body 
  .wrap 
    img.cover(src=images[1].standard_resolution.url)
    .content
      h1=name
      a(href=homePage) website
      p=bio
      each image, i in images
        img.shots(src=image.thumbnail.url)  
      a(href='/logout') Logout
Enter fullscreen mode Exit fullscreen mode

That's it

I have added all the Node/JavaScript code to server.js, this is to avoid distraction and keep to the point. However you can, and should split the code which ever way it feels right to you. One way to split the code, to account for larger project is to add the routes and strategies in separate files. You can checkout the github repository to see one way of doing it

Top comments (4)

Collapse
 
mrm8488 profile image
Manuel Romero

Great tutorial, Aurel. Don't forget to manage the case of rejected promise when you do the axios.get request. If you don't do it, the application may crash.

Collapse
 
caldaravi profile image
Carlos

fantastic, nice job

Collapse
 
halfbeep profile image
Shaun Cains

love it Aurel! simple, dry and cool. v.helpful

Collapse
 
fmgordillo profile image
Facundo Martin Gordillo

Oh no, out of date :c