Contents
- Introduction
- Creating a new user account
- Introducing OAuth and Passport
- Installing the Meetup.com Passport strategy
- Adding a Meetup.com link to the sign-up and sign-in pages
- Setting up the server-side auth/meetup routes
- Configuring the Meetup strategy
- Getting the Meetup
consumerKey
andconsumerSecret
- Adding the organization OAuth keys to your application
- Conclusion
- Downloadable resources
- Related topics
- Comments
Mastering MEAN
Managing authentication with OAuth and Passport
Content series:
This content is part # of # in the series: Mastering MEAN
This content is part of the series:Mastering MEAN
Stay tuned for additional content in this series.
The User Group List and Information (UGLI) app is beginning to take shape nicely. You can now show the local content that you create via the CRUD screens that you set up in "MEAN and UGLI CRUD with responsive web design." You can also incorporate content from an external site by using the services you developed in "MEAN meets Meetup.com and microdata."
Sharing meeting information with the general public is an important part of this project. But as a user-group leader, I also want to limit some activities to registered members of the group. For example, I might choose to preserve some civility in commenting on our presentations, by turning off anonymous access and requiring a login. So, in this installment, you'll use Meetup.com's OAuth service to provide login capabilities to the UGLI app. See Download to get the sample code.
Creating a new user account
By clicking the Sign up button (shown in Figure 1), your app's users can create a new account that's stored locally in MongoDB. This functionality is built in to your app — no additional programming required.
Figure 1. UGLI sign-up page

This default behavior is certainly the easiest solution from a development perspective, but it leaves something to be desired in terms of the user experience. Your user-group members already have an account on Meetup.com that they use to RSVP for upcoming meetings. Asking them to create and maintain a duplicate set of credentials isn't only annoying — it's a blatant violation of the Don't Repeat Yourself (DRY) principle.
Luckily, with the MEAN stack you're using, you can set up a distributed authentication and authorization solution that uses OAuth and Passport. In simple terms, your users can log in (authenticate) to the UGLI app with the same credentials they use to log in to Meetup.com. But this can't be done without users' permission; they must allow (authorize) the UGLI app to use their Meetup.com credentials.
Even after the UGLI app is authorized via OAuth, the user credentials aren't shared with the authorized app. You don't store a duplicate set of user names and passwords locally in UGLI. The application hoping to be authorized (UGLI) redirects users to the OAuth provider (Meetup.com), where they provide their credentials (user name and password). After a user authenticates successfully, an access token is returned to the authorized application.
This scheme cuts a huge swath of code and logic out of your application. You no longer need to worry about storing encrypted passwords on your server — that's now the OAuth provider's problem to deal with. Similarly, you no longer must write algorithms to enforce strong passwords, or deal with forgotten passwords, or force users to change their passwords on a periodic basis.
So, OAuth gives your users fewer passwords to memorize and gives you significantly less code to write. If that's not the textbook definition of a win/win scenario, I don't know what is.
Introducing OAuth and Passport
“You no longer need to write algorithms to enforce strong passwords, or deal with forgotten passwords, or force users to change their passwords on a periodic basis.”
OAuth is an open standard for distributed authentication and authorization. It was developed in 2006 by Twitter and business partner Ma.gnolia to facilitate the creation of desktop widgets that display information from authenticated services. Since then, OAuth has been adopted by hundreds of major websites, from Google to Facebook to Twitter, GitHub, LinkedIn, and more. (See the List of notable OAuth service providers.)
Passport is an OAuth library written for Node.js. Specifically, it is middleware meant to be seamlessly incorporated with Express applications. More than 140 Passport plugins (called strategies) are available, tailor-made for each OAuth provider.
If you open the UGLI app's package.json file in a text editor (as shown in Listing 1), you can see Passport strategies for four major services — Facebook, Twitter, LinkedIn, and Google — along with a local strategy for storing credentials directly in MongoDB.
Listing 1. Passport strategies in package.json
"dependencies": { "passport": "~0.2.0", "passport-local": "~1.0.0", "passport-facebook": "~1.0.2", "passport-twitter": "~1.0.2", "passport-linkedin": "~0.1.3", "passport-google-oauth": "~0.1.5" }
Using the existing examples as a guide, you'll add a sixth strategy to incorporate Meetup.com as an OAuth provider for UGLI.
Installing the Meetup.com Passport strategy
If you visit Authenticating with the Meetup API, you'll see that Meetup.com
offers OAuth services. A quick web search for
meetup.com passport.js strategy
yields a link to the library
you're looking for: passport-meetup
.
Type npm install passport-meetup --save
to download the
library to node_modules and update the dependencies block in package.json.
That was the easy part. With the strategy in place, your next step is to incorporate it into the sign-up and sign-in pages.
Adding a Meetup.com link to the sign-up and sign-in pages
Open public/modules/users/views/signup.client.view.html in a text editor. At the top of the file, you can see links to the various OAuth providers (as shown in Listing 2).
Listing 2. public/modules/users/views/signup.client.view.html
<h3 class="col-md-12 text-center">Sign up using your social accounts</h3> <div class="col-md-12 text-center"> <a href="/auth/facebook" class="undecorated-link"> <img src="/modules/users/img/buttons/facebook.png"> </a> <a href="/auth/twitter" class="undecorated-link"> <img src="/modules/users/img/buttons/twitter.png"> </a> <a href="/auth/google" class="undecorated-link"> <img src="/modules/users/img/buttons/google.png"> </a> <a href="/auth/linkedin" class="undecorated-link"> <img src="/modules/users/img/buttons/linkedin.png"> </a> </div>
Replace the existing links with one that points to your yet-to-be-created /auth/meetup route and displays the yet-to-be-downloaded Meetup.com icon (as shown in Listing 3).
Listing 3. Link to auth/meetup
<h3 class="col-md-12 text-center">Sign up using your Meetup.com account</h3> <div class="col-md-12 text-center"> <a href="/auth/meetup" class="undecorated-link"> <img src="/modules/users/img/buttons/meetup.png"> </a> </div>
Visit the Meetup Icon page and save the 128x128 pixel image to public/modules/users/img/buttons/, where the other social media icons are stored.
Now that the sign-up page is stubbed out, open public/modules/users/views/signin.client.view.html in a text editor and adjust it the same way you did for the sign-up page (as shown in Listing 4).
Listing 4. public/modules/users/views/signin.client.view.html
<h3 class="col-md-12 text-center">Sign in using your Meetup.com account</h3> <div class="col-md-12 text-center"> <a href="/auth/meetup" class="undecorated-link"> <img src="/modules/users/img/buttons/meetup.png"> </a> </div>
If everything goes as planned, your new sign-up page looks like Figure 2. Of course without the route in place, you'll get a 404 Page Not Found error if you click the link. You'll fix this next.
Figure 2. New UGLI sign-up page

Setting up the server-side auth/meetup routes
The next step is to create the server-side auth/meetup routes. Recall that all server-side logic is stored in the app directory; client-side logic is stored in the public folder.
Open app/routes/users.server.routes.js in a text editor. Find the block of
code for Facebook and copy/paste it, replacing facebook
with
meetup
(as shown in Listing 5).
Listing 5. app/routes/users.server.routes.js
// Setting the facebook oauth routes app.route('/auth/facebook').get(passport.authenticate('facebook', { scope: ['email'] })); app.route('/auth/facebook/callback').get(users.oauthCallback('facebook')); // Setting the meetup oauth routes app.route('/auth/meetup').get(passport.authenticate('meetup', { scope: ['email'] })); app.route('/auth/meetup/callback').get(users.oauthCallback('meetup'));
Remember the hyperlinks to auth/meetup that you created on the sign-up and
sign-in pages in the previous section? The first route (auth/meetup) will
be triggered when the user sends an HTTP GET
request to the
server by clicking the link. Passport will try to authenticate the user by
using the passport-meetup
strategy. The results of the login
attempt (successful or otherwise) will be sent to the second
auth/meetup/callback route asynchronously.
If you click the Meetup link on the sign-up page now, you get a 500 Server Error instead of a 404. That's not exactly an improvement, but at least it's progress. Next up: configuring the Meetup strategy.
Configuring the Meetup strategy
You can find all of the Passport strategies in the eponymous config/strategies directory. Copy facebook.js to meetup.js, then open meetup.js in a text editor.
As you did in the previous section, you'll go through this file and replace
all instances of facebook
with meetup
. But this
is more than a simple find/replace operation. You also need to make some
minor configuration changes.
To start, change the required library at the top of the file from the Facebook strategy to the Meetup one (as shown in Listing 6).
Listing 6. config/strategies/meetup.js
/** * Module dependencies. */ var passport = require('passport'), url = require('url'), MeetupStrategy = require('passport-meetup').Strategy, config = require('../config'), users = require('../../app/controllers/users');
Next, you need to customize the options block that gets passed into the new strategy. These values vary from strategy to strategy. Listing 7 shows the Facebook strategy options, which won't work for Meetup.
Listing 7. Facebook options that won't work for Meetup
module.exports = function() { // Use facebook strategy passport.use(new FacebookStrategy({ clientID: config.facebook.clientID, clientSecret: config.facebook.clientSecret, callbackURL: config.facebook.callbackURL, passReqToCallback: true },
Thankfully, the passport-meetup
module that you
npm install
ed earlier shipped with example code. Open
node_modules/passport-meetup/examples/login/app.js in a text editor. Look
for the passport.use function call
(shown in Listing 8).
Listing 8. node_modules/passport-meetup/examples/login/app.js
passport.use(new MeetupStrategy({ consumerKey: MEETUP_KEY, consumerSecret: MEETUP_SECRET, callbackURL: "http://127.0.0.1:3000/auth/meetup/callback" },
Copy this snippet over to meetup.js, overwriting the Facebook code. Next, change the values on the right side of the colon to those shown in Listing 9.
Listing 9. Meetup options that will work
passport.use(new MeetupStrategy({ consumerKey: config.meetup.consumerKey, consumerSecret: config.meetup.consumerSecret, callbackURL: config.meetup.callbackURL, },
In the next section, you'll get the consumerKey
and
consumerSecret
from Meetup.com and save them in the config.js
file. But you need to make a couple of additional changes to the current
file before you go.
The function immediately following the new MeetupStrategy
constructor is the event handler that receives the response from
Meetup.com. You are interested in three key pieces of the response: the
access token, the refresh token, and the user profile. (See the OAuth access tokens and refresh tokens sidebar for
details about the tokens.)
The access token and refresh token are strings that you pass through, unaltered, to Passport. Although they're crucial to the success of the OAuth operation, they're pretty boring to look at on their own. (Listing 10 includes examples of both.)
The user profile is more interesting. It's a JSON object returned by the OAuth provider that contains information about the user who successfully authenticated. The specific details will vary from OAuth provider to OAuth provider. Listing 10 shows an example of the user profile returned by Meetup.
Listing 10. User profile returned from Meetup.com OAuth provider
{ provider: 'meetup', id: 13848777, displayName: 'Scott Davis', _raw: '{ "results": [{ "status": "active", "link": "http:\\\/\\\/www.meetup.com\\\/members\\\/13848777", "photo": { "photo_link": "http:\\\/\\\/photos1.meetupstatic.com\\\/photos\\\/member\\\/7\\\/4\\\/d\\\/2\\ \/member_11849906.jpeg", "thumb_link": "http:\\\/\\\/photos3.meetupstatic.com\\\/photos\\\/member\\\/7\\\/4\\\/d\\\/2\\ \/thumb_11849906.jpeg", "photo_id": 11849906 }, "country": "us", "state": "CO", "city": "Denver", "id": 13848777, "joined": 1295844957000, "bio": "Scott Davis is the founder of ThirstyHead.com, a training and consulting company that specializes in leading-edge technology solutions like HTML 5, NoSQL, Groovy, and Grails.", "name": "Scott Davis", "other_services": { "twitter": { "identifier": "@scottdavis99" } } }] }', _json: { results: [ [Object] ], meta: { link: 'https://api.meetup.com/2/members', total_count: 1, url: 'https://api.meetup.com/2/members?order=name&member_id=13848777&offset=0 &format=json&page=800', title: 'Meetup Members v2', updated: 1392763702000, description: 'API method for accessing members of Meetup Groups', method: 'Members', }, accessToken: 'c7b5577bb80aab55439785cd86abcdef', refreshToken: '2af98db68950235a1e2519a734abcdef' } }
As you can see, Meetup returns user details such as name, place of residence, date joined, profile photos, linked social media accounts, and so on.
The final thing you need to do to complete the customization of the Meetup strategy is to map the Meetup profile fields back onto the User Mongoose object defined in app/models/user.server.model.js. Edit the remaining Facebook block in config/strategies/meetup.js, as shown in Listing 11.
Listing 11. Mapping the OAuth user profile to
the User
object
// Create the user OAuth profile var providerUserProfile = { firstName: '', lastName: '', displayName: profile.displayName, email: '', username: profile.id, provider: profile.provider, providerIdentifierField: 'id', providerData: providerData };
If you see fields in the Meetup profile JSON that you'd like to add to the
User
object, this is the perfect time to make your changes.
Don't forget to add the new fields to the HTML forms in
public/modules/users/views.
Your finished config/strategies/meetup.js should look like Listing 12.
Listing 12. The complete config/strategies/meetup.js
'use strict'; /** * Module dependencies. */ var passport = require('passport'), url = require('url'), MeetupStrategy = require('passport-meetup').Strategy, config = require('../config'), users = require('../../app/controllers/users'); module.exports = function() { // Use meetup strategy passport.use(new MeetupStrategy({ consumerKey: config.meetup.clientID, consumerSecret: config.meetup.clientSecret, callbackURL: config.meetup.callbackURL, }, function(req, accessToken, refreshToken, profile, done) { // Set the provider data and include tokens var providerData = profile._json; providerData.accessToken = accessToken; providerData.refreshToken = refreshToken; // Create the user OAuth profile var providerUserProfile = { firstName: '', lastName: '', displayName: profile.displayName, email: '', username: profile.id, provider: profile.provider, providerIdentifierField: 'id', providerData: providerData }; // Save the user OAuth profile users.saveOAuthUserProfile(req, providerUserProfile, done); } )); };
Before you can test this code, you need to do one more thing: Get the
consumerKey
and consumerSecret
from Meetup.
Getting the Meetup consumerKey
and
consumerSecret
I've spent the entire article so far talking about authenticating the user.
But before users can use OAuth to log in to UGLI, you (the developer) must
provide proof that your organization is what it claims to be. You do this
by providing a public key (the consumerKey
) to the user. Your
application also needs to know what its private key (the
consumerSecret
) is.
If you've worked with Public Key Infrastructure (PKI) before, you know that it's crucial to keep your private key hidden and secure. If someone else discovers your private key, they can masquerade as your organization. Conversely, if you don't share your public key with users, they can't prove who you are.
If you are the organizer of a user group on Meetup.com, you can generate
the consumerKey
and consumerSecret
from Meetup's
Your OAuth Consumers page (see Figure 3). I used
HTML5 Denver User Group
for the Consumer Name,
http://www.meetup.com/HTML5-Denver-Users-Group/
for the
Application Website, and
http://localhost:3000/auth/meetup/callback
for the Redirect
URI. After I push the UGLI app into production, I'll change the
Application Website to http://html5denver.com
and the
Redirect URI to http://html5denver.com/auth/meetup/callback
.
Figure 3. Generating the consumerKey
and
consumerSecret
for HTML5 Denver

If you don't run a user group on Meetup.com, your account isn't authorized
to generate OAuth keys on behalf of a group. However, you can still
generate tokens for one of your other social media accounts and adjust the
steps in this article accordingly. See Implementing
Sign in with Twitter to generate application keys for your Twitter
account or Access Tokens to use your Facebook account. A quick web search on
your social media website oauth keys
should yield
step-by-step instructions.
Adding the organization OAuth keys to your application
After you have the two keys (public and private), you'll add them to your
application via environment variables — much as you changed the
PORT
in "Tour a MEAN application."
Recall that you can set variables that change based on the mode you are
running in: development
, production
, or
test
. Environment-specific values are stored in config/env.
Open config/env/development.js in a text editor. Copy/paste the Facebook
block and adjust it accordingly for Meetup (as shown in Listing 13). Be
sure that the attribute names here match the attribute names you used in
the passport.use
function call in
config/strategies/meetup.js.
Listing 13. config/env/development.js
'use strict'; module.exports = { db: 'mongodb://localhost/test-dev', app: { title: 'HTML5 Denver' }, meetup: { consumerKey: process.env.MEETUP_KEY || 'APP_ID', consumerSecret: process.env.MEETUP_SECRET || 'APP_SECRET', callbackURL: 'http://localhost:3000/auth/meetup/callback' }, facebook: { clientID: process.env.FACEBOOK_ID || 'APP_ID', clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET', callbackURL: 'http://localhost:3000/auth/facebook/callback' }, twitter: { clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY', clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET', callbackURL: 'http://localhost:3000/auth/twitter/callback' }, google: { clientID: process.env.GOOGLE_ID || 'APP_ID', clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET', callbackURL: 'http://localhost:3000/auth/google/callback' }, linkedin: { clientID: process.env.LINKEDIN_ID || 'APP_ID', clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET', callbackURL: 'http://localhost:3000/auth/linkedin/callback' } };
You could replace APP_ID
and APP_SECRET
with
hardcoded values for the consumerKey
and
consumerSecret
that you retrieved in the previous section.
But a more secure solution is to provide these values to the UGLI
application via environment variables. To start your application with your
organization's consumerKey
and consumerSecret
,
type:
MEETUP_KEY=l75fkklhurkack36eelfhhfhjc MEETUP_SECRET=abcdeg316jd3ni43f21u1abcde NODE_ENV=development grunt
Don't forget to make similar adjustments to config/env/production.js before you go live. And if you created a user account earlier, be sure to delete it from the html5-denver-dev database in MongoDB so that you can walk through the new-account creation process all over again.
Conclusion
Larry Wall (creator of the Perl programming language) famously said, "Easy things should be easy, and hard things should be possible." I hope this sentiment nicely summarizes your experience getting OAuth and Passport wired up to use Meetup.com for your distributed authentication and authorization needs.
In the next Mastering MEAN installment, I'll walk you through the testing infrastructure built into the MEAN stack. You'll learn about Mocha for server-side testing, Jasmine for client-side testing, and Karma for running your tests across multiple browsers. Until then, have fun mastering MEAN.
Downloadable resources
- PDF of this content
- Sample code (wa-mean5.zip | 1.4MB)
Related topics
- Passport: Visit the website for this authentication library for Node.js.
- OAuth: Read about OAuth on Wikipedia.
- developerWorks Premium: Provides an all-access pass to powerful tools, curated technical library from Safari Books Online, conference discounts and proceedings, SoftLayer and Bluemix credits, and more.
- "Accessing social networking Web sites through OAuth" (Xiaobo Yang, developerWorks, January 2010): Broaden your skills in enabling authentication via social networking sites with OAuth.
- "Build a real-time polls application with Node.js, Express, AngularJS, and MongoDB" (Joe Lennon, developerWorks, June 2014): Check out a MEAN development project that's deployed on IBM Bluemix™.