node.jsexpressoauth-2.0passport-facebook

How do I serve static files with authentication?


This is my folder structure:

/app
  server.js

  /public
    index.html
    script-a.js

    /scripts
      script-b.js

These are the relevant contents of my index.html:

<!-- index.html -->
.....
...
<script scr="/script-a.js"></script>
<script scr="/script-b.js"></script>

These are the relevant contents of server.js:

import express                          from 'express'
import session                          from 'express-session'
import NedbStore                        from 'nedb'
import passport                         from 'passport'
import { Strategy as FacebookStrategy } from 'passport-facebook'
import { ensureLoggedIn }               from 'connect-ensure-login'
....
..
const db  = new NedbStore({ filename: `${__dirname}/db/nedb.db`, autoload: true })
const DB  = dbAsyncWrapper(db)

app.use(cors({
   origin: '*',
   credentials: true,
   optionSuccessStatus: 200
}))

app.use(function(req, res, next) {
  res.header('Access-Control-Allow-Origin', 'http://localhost:4200')
  res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization')
  res.header('Access-Control-Allow-Methods', 'POST, GET')
  next()
})

app.use(cookieParser())
app.use(express.json({ limit: '50mb' }))
app.use(express.urlencoded({ extended: true, limit: '50mb' }))

app.use(session({
  secret: 'googoogaga',
  resave: false,
  saveUninitialized: false
}))
app.use(passport.initialize())
app.use(passport.session())

passport.use(new FacebookStrategy({
  clientID      : FACEBOOK_APP_ID,
  clientSecret  : FACEBOOK_APP_SECRET,
  callbackURL   : "http://localhost:4200/facebook/callback",
  profileFields : ['id']
}, async(accessToken, refreshToken, profile, done) => {
  let facebookId = profile.id
  let userInDb =  await DB.getUser()
  if (userInDb && userInDb.facebookId === facebookId) {
    await DB.updateUser({ accessToken })
    done(null, userInDb)
  } else {
    let newUser = await DB.updateUser({ facebookId, accessToken })
    done(null, newUser)
  }
}))

passport.serializeUser(function(user, done) {
  done(null, user)
})

passport.deserializeUser(function(user, done) {
  done(null, user)
})

app.use('/', ensureLoggedIn('/auth/facebook'), express.static(__dirname + '/public'))

app.get('/auth/facebook', passport.authenticate('facebook', { scope:'email' }))

app.get('/facebook/callback', passport.authenticate('facebook', {
  successRedirect : '/',
  failureRedirect : '/auth/facebook'
}))

With the above code, I'm expecting to:

  1. go to /
  2. be redirected to facebook for login
  3. go back to /
  4. have index.html served and loaded
  5. have script-a.js and script-b.js load correctly

What happens instead is:

I go to / and I'm redirected to /auth/facebook with a 302 status code.

If I remove:

app.use('/', ensureLoggedIn('/auth/facebook'), express.static(__dirname + '/public'))

and instead declare every single route handler manually, everything works:

....
..
app.get('/', ensureLoggedIn('/auth/facebook'), (req,res) => {
  res.sendFile(__dirname + '/public/index.html')
})
app.get('/script-a.js', ensureLoggedIn('/auth/facebook'), (req,res) => {
  res.sendFile(__dirname + '/public/script-a.js')
})
app.get('/script-b.js', ensureLoggedIn('/auth/facebook'), (req,res) => {
  res.sendFile(__dirname + '/public/scripts/script-b.js')
})

Solution

  • Unfortunately, the only solution I found was to turn my /public folder into a flat structure and do:

    app.get('/', ensureLoggedIn('/auth/facebook'), (req,res) => {
      res.sendFile(__dirname + '/public/index.html')
    })
    
    app.get('/:filename', ensureLoggedIn('/auth/facebook'), (req,res) => {
      res.sendFile(__dirname + `/public/${req.params.filename}`)
    })