A note about this exercise. This is not entirely a "paint by numbers" OAuth exercise. We have left a little room for you to apply the things you already have some experience with:
- routes
- requiring core modules
- middleware
- local environment variables
- deploying to Heroku
What is provided in this exercise is still way friendlier than most OAuth documentation. So, consider this your friendly introduction.
YOU SHOULD:
- Read errors in your server logs
- Double check environment variables
- Read through the documentation here when you are stuck and see what you missed!
- Google that!
Some guiding questions:
- How does Google / Facebook / LinkedIn etc... communicate with your local web app during development? Isn't that private (aka not published on the internet)??
- What part of your existing authentication / authorization flows does this replace?
- Why would you want to authenticate with Google / Facebook instead of storing the emails / passwords yourself?
#1 Create an Express App
Generate an express app that includes a .gitignore
file:
fork / clone this repo
cd into repo
express --git .
npm install
nodemon
Visit http://localhost:3000/ and make sure that the app loads correctly. Then initialize a git repository:
git status
git add -A
git commit -m "Initial commit"
git push origin master
#2 Deploy to Heroku
Create an app on Heroku, deploy to it and verify that your app works on Heroku:
heroku apps:create
git push heroku master
heroku open
Now that you have a Heroku URL:
- add your Heroku URL to the README
- git add, commit and push to Github
#3 Install and configure dotenv and express-session
Passport requires that your app have a req.session
object it can write to. To enable this, install and require express-session
in app.js
. In order to keep your secrets safe, you'll need to also install and config dotenv
. Go to the docs for help with
syntax.
npm install dotenv express-session --save
touch .env
echo .env >> .gitignore
#4 In app.js
, require express-session
and load dotenv:
You've seen this a few times now, so I'm going to let you handle adding the
dotenv
and requiring the express-session
module.
Using the following commands, add SESSION_KEY1
and SESSION_KEY2
to your .env
and set each value to a randomly generated key:
echo SESSION_KEY1=$(node -e "require('crypto').randomBytes(48, function(ex, buf) { console.log(buf.toString('hex')) });") >> .env
echo SESSION_KEY2=$(node -e "require('crypto').randomBytes(48, function(ex, buf) { console.log(buf.toString('hex')) });") >> .env
Go look in your .env
file. What happened?
#5 Add the session middleware to your app:
// after app.use(express.static(path.join(__dirname, 'public')));
app.use(session({
keys: [process.env.SESSION_KEY1, process.env.SESSION_KEY2],
secret: 'bam',
resave: false,
saveUninitialized: true
}))
Ensure that you app still works locally, then:
- git add, commit, push to Github
- deploy to Heroku
Once you've deployed, be sure to set SESSION_KEY1
and SESSION_KEY2
on Heroku
EXAMPLE of how to set an environment variable on Heroku:
heroku config:set GITHUB_USERNAME=joesmith
Verify that your app still works correctly on Heroku.
#6 Set a HOST environment variable
For your app to work both locally and on production, it will need to know what URL it's being served from.
To do so, add a HOST environment variable to .env
.
#1 Set a HOST environment variable locally to your localhost (EXAMPLE: http://localhost:3000
#2 Set a HOST environment variable on Heroku to your Heroku URL
NOTE: do not include the trailing slash. So https://guarded-inlet-5817.herokuapp.com
instead of https://guarded-inlet-5817.herokuapp.com/
There should be nothing to commit at this point.
- Login to https://www.linkedin.com/
- Visit https://www.linkedin.com/developer/apps and create a new app
- For Logo URL, add your own OR you can past this into your browser and use this one. You'll have to resize it to be of equal height and width. https://brandfolder.com/galvanize/attachments/suxhof65/galvanize-galvanize-logo-g-only-logo.png?dl=true
- Under website add your Heroku URL
- Fill in all other required fields and submit
On the "Authentication" screen:
You should see a Client ID
and Client Secret
. Add these to your .env
file, and set these environment variables on Heroku. Your .env
file should look like this
(but with real values):
SESSION_KEY1=your-secret
SESSION_KEY2=your-secret
HOST=http://localhost:3000
LINKEDIN_CLIENT_ID=your-secret
LINKEDIN_CLIENT_SECRET=your-secret
Config those environment variables on Heroku as well.
- Under authorized redirect URLs enter http://localhost:3000/auth/linkedin/callback
- Under authorized redirect URLs enter your Heroku url, plus
/auth/linkedin/callback
There should be nothing to add to git at this point.
#1 Install npm packages passport
and passport-linkedin
#2 add the Passport middleware to app.js
// up with the require statements...
var passport = require('passport');
// above app.use('/', routes);
app.use(passport.initialize());
app.use(passport.session());
Then tell Passport to use the LinkedIn strategy:
// up with the require statements...
var LinkedInStrategy = require('passport-linkedin').Strategy
// below app.use(passport.session());...
passport.use(new LinkedInStrategy({
consumerKey: process.env.LINKEDIN_CLIENT_ID,
consumerSecret: process.env.LINKEDIN_CLIENT_SECRET,
callbackURL: process.env.HOST + "/auth/linkedin/callback"
},
function(token, tokenSecret, profile, done) {
// To keep the example simple, the user's LinkedIn profile is returned to
// represent the logged-in user. In a typical application, you would want
// to associate the LinkedIn account with a user record in your database,
// and return that user instead (so perform a knex query here later.)
done(null, profile)
}
));
Finally, tell Passport how to store the user's information in the session cookie:
// above app.use('/', routes);...
passport.serializeUser(function(user, done) {
// later this will be where you selectively send to the browser an identifier for your user, like their primary key from the database, or their ID from linkedin
done(null, user);
});
passport.deserializeUser(function(user, done) {
//here is where you will go to the database and get the user each time from it's id, after you set up your db
done(null, user)
});
Run the app locally to make sure that it's still functioning (and isn't throwing any errors).
Create a new route file for your authentication routes:
touch routes/auth.js
In routes/auth.js
, you'll need to add a route for logging in
, and one for logging out
. In addition, you'll have to create the route that LinkedIn will call once the user has authenticated properly
:
#1 route for logging in
Add a GET /auth/linkedin
route that takes a middleware argument of passport.authenticate('linkedin')
.
NOTE: This route isn't going to respond with a redirect
or a render
. It's only job is to call the middleware function. You won't pass in a callback
function here.
IT SHOULD LOOK LIKE THIS:
router.get('/auth/linkedin', passport.authenticate('linkedin'));
What else do you need to add to this route file for this function to work? If you don't know yet, don't worry, you'll get an error telling you all about it in a little while. When that happens, check your server logs and see if you can fix it!
#2 In auth.js
add a route for logging out
Write a route that handles a get
request to /auth/logout
. For now, just redirect
to '/'
#3 Create the route that LinkedIn will call once the user has authenticated properly:
The route should be a GET
request to /auth/linkedin/callback
that takes a middleware
argument of passport.authenticate('linkedin', { failureRedirect: '/' })
. Inside the route
you will simply redirect
to /
.
See an above route for help passing in the middleware function. You just did this.
#4 Back in app.js
, be sure to require your auth
routes file
// up with the require statements...
var authRoutes = require('./routes/auth');
// right after app.use('/', routes);
app.use('/', authRoutes);
With this setup, you should be able to login with LinkedIn successfully by visiting the following URL directly:
http://localhost:3000/auth/linkedin
If it's successful, you should be redirected to the homepage. If you check your terminal output, you should see a line in there like:
GET /auth/linkedin/callback?oauth_token=78--3f284b63-1aff-4eb5-b710-104bae4f5413&oauth_verifier=07507 302 791.066 ms - 58
That indicates that LinkedIn successfully authenticated the user.
IF EVERYTHING IS WORKING, NOW WOULD BE A GOOD TIME TO ADD and COMMIT!
TASK LIST
- add a
login with LinkedIn
link (What route should that hit?)- The
Login with LinkedIn
link should not be displayed if a user is logged in
- The
- add a
logout
link- The
logout
link should only be displayed if a user is logged in
- The
- display the name of the currently logged-in user
NOTE:
To do this part, you'll need to access some of the information LinkedIn gave to
you when the user successfully logged in. Check out the chunks of code you added
in app.js
and console.log
some of the results to see what you have to work with.
Upon successful login, Passport sets a req.user
variable.
You can use that object to add middleware that will set the user
local
variable for all views. How could you use this to get all of the above tasks
working?
// right above app.use('/', routes);
app.use(function (req, res, next) {
res.locals.user = req.user
next()
})
FINISH THE LOGOUT ROUTE:
Ok, by now you should have done some exploring to see what the LinkedIn Strategy
is doing, what is being return etc. In your /auth/logout
route, console.log
req.session
and see what's in there. Recall your experience using express-session
.
Go to the docs and see if you can figure out what code to add to your /auth/logout
route to end the current user session.
You should now be able to login and logout with LinkedIn!!!
- Git add, commit and push
- Deploy to Heroku
- Check that your app works on Heroku
Checkout the solution branch if you're really stuck.
#1 Read LinkedIn's API docs to see what else you can do with this authorization.
- Make an API call to LinkedIn on the user's behalf
Install unirest:
npm install unirest --save
#2 Add Postgres and save the user in your database:
#3 Use Passport to implement login with Facebook, or some other 3rd party application
- https://developer.linkedin.com/docs/oauth2
- http://passportjs.org/docs
- https://github.com/jaredhanson/passport-linkedin
- https://github.com/jaredhanson/passport-linkedin/blob/master/examples/login/app.js
- https://github.com/jaredhanson/passport-linkedin#configure-strategy
- http://passportjs.org/docs/configure#configure