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
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}`))
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
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)
})
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)
}))
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'))
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: '/'
}))
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)
})
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)
}))
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)
}))
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)
})
})
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
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)
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.
fantastic, nice job
love it Aurel! simple, dry and cool. v.helpful
Oh no, out of date :c