404 sent to client (browser) during google callback, but the user data is retrieved.
See original GitHub issueWhen calling passport.authenticate(...) in the OAuth2 callback stage of the authentication sequence for Google OAuth2 as follows:
const GoogleStrategy = require('passport-google-oauth2').Strategy
passport.use(new GoogleStrategy({
    scope: ['email', 'profile'],
    clientID: process.env.GOOGLE_CLIENT_ID,
    clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    callbackURL: 'http://localhost:' + 7000 + '/auth/google/callback'
  },
  (accessToken, refreshToken, profile, done) => {
    console.log(profile)
    fetchUser().then(user => done(null, user))
  }
))
app.use(route.get('/auth/google', async (ctx, next) => {
  console.log(`/api/google/singin called with\n ctx -> ${JSON.stringify(ctx)} && next -> ${next}`)
    console.log(`ctx.req.headers -> ${JSON.stringify(ctx.req.headers)}`)
    console.log(`ctx.req.rawHeaders -> ${ctx.req.rawHeaders}`)
    passport.authenticate('google')(ctx,next)
  }
))
  
app.use(route.get('/auth/google/callback', async (ctx, next) => {
  passport.authenticate('google', async (err, user, info) => {
    console.log(`/auth/google/signin/callback -> passport.authenticate('google') callback called with\nerr -> ${JSON.stringify(err)}\nuser -> ${JSON.stringify(user)}\ninfo -> ${JSON.stringify(info)}`)
    if (user === false) {
      ctx.redirect('/')
      await next()
    } else {
      ctx.login(user)
      ctx.redirect('/app')
      await next()
    }
  })(ctx, next)
}))
Koa is sending 404 headers to the client (browser),
but the anonymous function callback is called with the actual user data.
The debug output shows the sequence of calls (removed personal info):
  <-- GET /favicon.ico
  --> GET /favicon.ico 302 6ms 33b
  <-- GET /
  --> GET / 200 5ms 719b
  <-- GET /auth/google
/api/google/singin called with
 ctx -> {"request":{"method":"GET","url":"/auth/google","header":{"host":"localhost:7000","connection":"keep-alive","upgrade-insecure-requests":"1","user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36","accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8","dnt":"1","referer":"http://localhost:7000/","accept-encoding":"gzip, deflate, sdch, br","accept-language":"en-US,en;q=0.8,ja;q=0.6","cookie":"koa.sid.sig=O9oTTBivYar3gy7JuZtdpDgvTO0"}},"response":{"status":404,"message":"Not Found","header":{}},"app":{"subdomainOffset":2,"proxy":true,"env":"development"},"originalUrl":"/auth/google","req":"<original node req>","res":"<original node res>","socket":"<original node socket>"} && next -> function next() {
          return dispatch(i + 1)
        }
ctx.req.headers -> {"host":"localhost:7000","connection":"keep-alive","upgrade-insecure-requests":"1","user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36","accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8","dnt":"1","referer":"http://localhost:7000/","accept-encoding":"gzip, deflate, sdch, br","accept-language":"en-US,en;q=0.8,ja;q=0.6","cookie":"koa.sid.sig=O9oTTBivYar3gy7JuZtdpDgvTO0"}
ctx.req.rawHeaders -> Host,localhost:7000,Connection,keep-alive,Upgrade-Insecure-Requests,1,User-Agent,Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36,Accept,text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8,DNT,1,Referer,http://localhost:7000/,Accept-Encoding,gzip, deflate, sdch, br,Accept-Language,en-US,en;q=0.8,ja;q=0.6,Cookie,koa.sid.sig=O9oTTBivYar3gy7JuZtdpDgvTO0
  --> GET /auth/google 302 4ms 0b
  <-- GET /auth/google/callback?code=4/vE4A-Yl3nZS07-x61cgapHEf2UhX3b-ptn4K0wdSLv0
  --> GET /auth/google/callback?code=4/vE4A-Yl3nZS07-x61cgapHEf2UhX3b-ptn4K0wdSLv0 404 9ms -
  <-- GET /favicon.ico
  --> GET /favicon.ico 302 2ms 33b
  <-- GET /
  --> GET / 200 22ms 719b
{ provider: 'google',
  id: 'XXXXXXXXXXXXXXX',
  displayName: XXXXXXXXXXXXXXX',
  name: { familyName: 'XXXXXXXXXXXXXXX', givenName: 'XXXXXXXXXXXXXXX' },
  isPerson: true,
  isPlusUser: true,
  language: 'en',
  emails: [ { value: 'XXXXXXXXXXXXXXX', type: 'account' } ],
  email: 'XXXXXXXXXXXXXXX',
  gender: 'XXXXXXXXXXXXXXX',
  photos: [ { value: 'https://lh5.googleusercontent.com/XXXXXXXXXXX/photo.jpg?sz=50' } ],
  _raw: '{\n "kind": "plus#person",\n "etag": "\\"XXXXXXXXXXXXXX\\"",\n "gender": "XXXXXXXXX",\n "emails": [\n  {\n   "value": "XXXXXXXXXXXXXXX",\n   "type": "account"\n  }\n ],\n "objectType": "person",\n "id": "XXXXXXXXXXXXXXX",\n "displayName": "XXXXXXXXXXXXXXX",\n "name": {\n  "familyName": "XXXXXXXXXXXXXXX",\n  "givenName": "XXXXXXXXXXXXXXX"\n },\n "url": "https://plus.google.com/XXXXXXXXXXXXXXX",\n "image": {\n  "url": "https://lh5.googleusercontent.com/XXXXXXXXXXXXXXX/photo.jpg?sz=50",\n  "isDefault": false\n },\n "isPlusUser": true,\n "language": "en",\n "circledByCount": 0,\n "verified": false\n}\n',
  _json: 
   { kind: 'plus#person',
     etag: '"XXXXXXXXXXXXXXX"',
     gender: 'XXXXXXXXXXXXXXX',
     emails: [ [Object] ],
     objectType: 'person',
     id: 'XXXXXXXXXXXXXXX',
     displayName: 'XXXXXXXXXXXXXXX',
     name: { familyName: 'XXXXXXXXXXXXXXX', givenName: 'XXXXXXXXXXXXXXX' },
     url: 'https://plus.google.com/XXXXXXXXXXXXXXX',
     image: 
      { url: 'https://lh5.googleusercontent.com/XXXXXXXXXXXXXXX/photo.jpg?sz=50',
        isDefault: false },
     isPlusUser: true,
     language: 'en',
     circledByCount: 0,
     verified: false } }
/auth/google/signin/callback -> passport.authenticate('google') callback called with
err -> null
user -> {"id":1,"username":"test","password":"test"}
info -> {}
(node:54360) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 4): Error: Can't set headers after they are sent.
Issue Analytics
- State:
 - Created 6 years ago
 - Comments:7 (3 by maintainers)
 
Top Results From Across the Web
404 sent to client (browser) during google callback, but the ...
404 sent to client (browser) during google callback, but the user data is retrieved. #25. Panoplos opened this issue on Apr 17, 2017...
Read more >Google OAuth works fine in localhost but not on Heroku, it ...
I've implemented Google OAuth sign in in my PERN stack application successfully in localhost, now deploying it to heroku almost all works ...
Read more >404. That's an error. The requested URL was not found on this ...
As you might know, a 404 error is "File not found." This can happen when a file is deleted or there is an...
Read more >Using OAuth 2.0 for Web Server Applications | Authorization
This document explains how web server applications use Google API Client Libraries or Google OAuth 2.0 endpoints to implement OAuth 2.0 ...
Read more >How the fix redirect_uri_mismatch error. Part 2 server sided ...
The redirect_uri_mismatch error message is quite common when working with the Google APIs. Google APIs support authorization to private user ...
Read more >
Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free
Top Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found

And we have liftoff!!
For documentation purposes, here is the code that works:
It is sent to the function returned by
passport.authenticate(...).