node package manager
Love JavaScript? Your insights can make it even better. Take the 2017 JavaScript Ecosystem Survey »


Artsy Passport


Wires up the common auth handlers, and related security concerns, for Artsy's Ezel-based apps using passport. Used internally at Artsy to DRY up authentication code.

Breaking changes in 2.0

  • The app is now shipped as a single JS file.


Make sure you first mount session, body parser, and start artsy-xapp.

app.use express.bodyParser()
app.use express.cookieParser('foobar')
app.use express.cookieSession()
artsyXapp.init -> app.listen()

Then mount Artsy Passport passing a big configuration hash.

Values indicate defaults.

app.use artsyPassport
  CurrentUser: # The CurrentUser Backbone model 
  # Pass in env vars 
  # ---------------- 
  FACEBOOK_ID: # Facebook app ID 
  FACEBOOK_SECRET: # Facebook app secret 
  TWITTER_KEY: # Twitter consumer key 
  TWITTER_SECRET: # Twitter consumer secret 
  TWITTER_KEY: # Twitter consumer key 
  TWITTER_SECRET: # Twitter consumer secret 
  LINKEDIN_KEY: # Linkedin app key 
  LINKEDIN_SECRET: # Linkedin app secret 
  ARTSY_ID: # Artsy client id 
  ARTSY_SECRET: # Artsy client secret 
  ARTSY_URL: # SSL Artsy url e.g. 
  APP_URL: # Url pointing back to your app e.g. 
  SEGMENT_WRITE_KEY: # Segment write key to track signup 
  # Defaults you probably don't need to touch 
  # ----------------------------------------- 
  # Social auth 
  linkedinPath: '/users/auth/linkedin'
  linkedinCallbackPath: '/users/auth/linkedin/callback'
  facebookPath: '/users/auth/facebook'
  facebookCallbackPath: '/users/auth/facebook/callback'
  twitterPath: '/users/auth/twitter'
  twitterCallbackPath: '/users/auth/twitter/callback'
  twitterLastStepPath: '/users/auth/twitter/email'
  twitterSignupTempEmail: (token) ->
    hash = crypto.createHash('sha1').update(token).digest('hex')
    "#{hash.substr 012}@artsy.tmp"
  # Landing pages 
  loginPagePath: '/log_in'
  signupPagePath: '/sign_up'
  settingsPagePath: '/user/edit'
  afterSignupPagePath: '/personalize'
  # Misc 
  logoutPath: '/users/sign_out'
  userKeys: [

The keys are cased so it's convenient to pass in a configuration hash. A minimal setup could look like this:

app.use artsyPassport _.extend config,
  CurrentUser: CurrentUser

Note: CurrentUser must be a Backbone model with typical get and toJSON methods.

Create a login form pointing to your paths.

h1 Login
pre!= error
a( href=ap.facebookPath ) Login via Facebook
a( href=ap.twitterPath ) Login via Twitter
form( action=ap.loginPagePath, method='POST' )
  h3 Login via Email
  input( name='name' )
  input( name='email' )
  input( name='password' )
  input( type="hidden" name="_csrf" value=csrfToken )
  button( type='submit' ) Login

And maybe a signup form...

h1 Signup
pre!= error
a( href=ap.facebookPath ) Signup via Facebook
a( href=ap.twitterPath ) Signup via Twitter
form( action=ap.signupPagePath, method='POST' )
  h3 Signup via Email
  input( name='name' )
  input( name='email' )
  input( name='password' )
  input( type="hidden" name="_csrf" value=csrfToken )
  button( type='submit' ) Signup

And maybe a settings page for linking accounts...

h2 Linked Accounts
pre!= error
- providers = user.get('authentications').map(function(a) { return a.provider })
if providers.indexOf('facebook') > -1
  | Connected Facebook
  a( href=ap.facebookPath ) Connect Facebook
if providers.indexOf('twitter') > -1
  | Connected Twitter
  a( href=ap.twitterPath ) Connect Twitter
if providers.indexOf('linkedin') > -1
  | Connected LinkedIn
  a( href=ap.linkedinPath ) Connect LinkedIn

Finally there's this weird "one last step" UI for twitter to store emails after signup.

h1 Just one more step
pre!= error
form( method='post', action=ap.twitterLastStepPath )
  input( type="hidden" name="_csrf" value=csrfToken )
  input.bordered-input( name='email' )
  button( type='submit' ) Join Artsy

Render the pages

{ loginPagePathsignupPagePathsettingsPagePath,
  afterSignupPagePathtwitterLastStepPath } = artsyPassport.options
app.get loginPagePath(req, res) -> res.render 'login'
app.get signupPagePath(req, res) -> res.render 'signup'
app.get settingsPagePath(req, res) -> res.render 'settings'
app.get afterSignupPagePath(req, res) -> res.render 'personalize'
app.get twitterLastStepPath(req, res) -> res.render 'twitter_last_step'

Access a logged in Artsy user in a variety of ways...

In your server-side templates

h1 Hello #{user.get('name')}

In your client-side code

CurrentUser = require '../models/'
sd = require('sharify').data
user = new CurrentUser(sd.CURRENT_USER)

In your routers

app.get '/'(req, res) ->
  res.send 'Hello ' + req.user.get('name')

These forms of user will be null if they're not logged in.

Sanitize Redirect

If you implement a fancier auth flow that involves client-side redirecting back, you may find this helper useful in avoiding "open redirect" attacks.

sanitizeRedirect = require 'artsy-passport/sanitize-redirect'
location.href = sanitizeRedirect ""
# Notices the url isn't pointing at, so just redirects to / 


Add a entry into your /etc/hosts localhost

Install node modules npm install then write a ./ that looks something like this:

module.exports =
  ARTSY_ID: ''
  APP_URL: ''
  # An Artsy user that's linked to Facebook and Twitter 

Then you can check the example by running npm run example and opening localhost:4000.

The tests are a combination of integration and middleware unit tests. To run the whole suite use npm test.