ananay / passport-apple

Passport strategy for Sign in with Apple
https://passport-apple.ananay.dev
142 stars 49 forks source link

passport.authenticate('apple') doesn't call serializeUser function on success #45

Closed paladosss closed 1 year ago

paladosss commented 1 year ago

But everything works with google passport

passport.serializeUser((user, done) => {
    console.log('serializeUser: ', user)
    done(null, user)
})

passport.use(
    new AppleStrategy(
        {
            clientID: process.env['APPLE_CLIENT_ID'],
            teamID: process.env['APPLE_TEAM_ID'],
            keyID: process.env['APPLE_KEY_ID'],
            callbackURL: `${process.env['BASE_URL']}/oauth2/callback/apple`,
            privateKeyLocation: path.join(__dirname, './AuthKey.p8'),
            passReqToCallback: true,
        },
        async (req, accessToken, refreshToken, idToken, profile, cb) => {
            // Here, check if the idToken.sub exists in your database!
            const idTokenDecoded = jwt.decode(idToken)

            const { email } = idTokenDecoded

            let user = await User.findOne({ email: email })

            if (!user) {
                user = User({
                    email: email,
                    name: email,
                    apple: idTokenDecoded.sub,
                })

                await user.save()
            } else if (user && !user.apple) {
                user = await User.findOneAndUpdate(
                    { email: email },
                    {
                        $set: {
                            apple: idTokenDecoded.sub,
                        },
                    },
                    { returnOriginal: false }
                )
            }

            cb(null, user)
        }
    )
)

app.get(`/login/apple`, passport.authenticate('apple'))

app.post(`/oauth2/callback/apple`, function (req, res, next) {
    passport.authenticate('apple', function (err, user, info) {
        if (err) {
            if (err == 'AuthorizationError') {
                res.send(
                    'Oops! Looks like you didn\'t allow the app to proceed. Please sign in again! <br /> \
                <a href="/login/apple">Sign in with Apple</a>'
                )
            } else if (err == 'TokenError') {
                res.send(
                    'Oops! Couldn\'t get a valid token from Apple\'s servers! <br /> \
                <a href="/login/apple">Sign in with Apple</a>'
                )
            }
        } else {
            // Successful authentication, redirect home
            res.redirect('/')
        }
    })(req, res, next)
})
MBerka commented 1 year ago

I am also seeing this - the authentication succeeds but the usual Passport cookie is an empty object and the next authentication after the redirect fails. How did you get past this?

MBerka commented 1 year ago

I got the cookie to set by rejecting the default example which leads people to define a (req, res, next) function which just passes those same arguments to passport.authenticate(). As with other passport modules, it is possible to include passport.authenticate()'s outputted function directly, without immediately invoking it. My redirection happens in a later handler function, but the equivalent for the code above would be:

app.post(`/oauth2/callback/apple`, passport.authenticate('apple', function (err, user, info) {
    if (err) {
        if (err == 'AuthorizationError') {
            res.send(
                'Oops! Looks like you didn\'t allow the app to proceed. Please sign in again! <br /> \
                    <a href="/login/apple">Sign in with Apple</a>'
            )
        } else if (err == 'TokenError') {
            res.send(
                'Oops! Couldn\'t get a valid token from Apple\'s servers! <br /> \
                    <a href="/login/apple">Sign in with Apple</a>'
            )
        }
    } else {
        // Successful authentication, redirect home
        res.redirect('/')
    }
}));

Somehow, the extra function block was interrupting the flow of Express user information.