redirect uri mismatch passport js - node.js

I'm trying to do a google login using passport js and express but I'm getting redirect uri mismatch error over and over again. I checked multiple times that redirect uri in my code is the same as in the google developer console but the issue still persists.
passport js config:
import passport from "passport";
import { Strategy } from "passport-google-oauth20";
import db from "../../lib/database.js";
export default function (passport: passport.PassportStatic) {
passport.use(
new Strategy(
{
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: "http://127.0.0.1:3000/auth/google/callback",
},
async (accessToken, refreshToken, profile, done) => {
const user = db.user.findFirst({
where: {
authProvider: "google",
authProviderId: profile.id,
},
});
if (!user) {
const newUser = db.user.create({
data: {
authProvider: "google",
authProviderId: profile.id,
name: profile.displayName,
email: profile.emails[0].value,
},
});
return done(null, newUser);
}
return done(null, user);
}
)
);
}
auth router:
import passport from "passport";
import express from "express";
import passportConfig from "../configs/passportConfig.js";
passportConfig(passport);
const router = express.Router();
router.get(
"/auth/google",
passport.authenticate("google", { scope: ["profile"] })
);
router.get(
"/auth/google/callback",
passport.authenticate("google", { failureRedirect: "/login" }),
function (req, res) {
// Successful authentication, redirect home.
res.redirect("/");
}
);
export default router;
authorized redirect uris in google console:

Related

How to integrate Passport Google OAuth 2.0 with Passport JWT?

I'm currently working on my first MERN project, with Passport Local, Google OAuth 2.0 and JWT. All my routes are protected with passport.authenticate("jwt", { session: false }), (req, res) => {}). I know Google Strategy relies on Express session and here's where my issue starts.
I don't know if this is the correct approach, but I want to generate a JWT token from Google OAuth login to handle the user session with it and keep all my routes requests working properly. From the past issues opened here I've found this one to be the most recent and practical solution, but it seemed to me that it would introduce a new problem - since the local login already generates the JWT token, wouldn't it conflict with the request on the step 4 from the linked solution? If I'm thinking right, the token would be generated two times when using the standard login - one right at the authentication and another when reaching the app's home page, not to mention that it looks like the request to /auth/login/success will be made every time the home page is reached, which doesn't seem right to me.
So, what would be the best and cleaner way to approach this? I'd also like to validate everything on the server side and avoid appending the token to the URL, if possible. Anyway, here is my code:
passport.js
const GoogleStrategy = require("passport-google-oauth20").Strategy;
const JwtStrategy = require("passport-jwt").Strategy;
const User = require("../models/user.model");
require("dotenv").config();
module.exports = function (passport) {
// CONFIGURE STRATEGIES
// Local
passport.use(User.createStrategy());
// Google
passport.use(
new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: "http://localhost:5000/auth/google/movie-log",
},
function (accessToken, refreshToken, profile, cb) {
User.findOrCreate(
{ googleId: profile.id },
{ first_name: profile.displayName, email: profile._json.email },
function (err, user) {
return cb(err, user);
}
);
}
)
);
// JWT
const cookieExtractor = (req) => {
let token = null;
if (req && req.cookies) {
token = req.cookies["access_token"];
}
return token;
};
passport.use(
new JwtStrategy(
{
jwtFromRequest: cookieExtractor,
secretOrKey: process.env.SECRET,
},
(payload, done) => {
User.findById(payload.sub, (err, user) => {
if (err) {
return done(err, false);
}
if (user) {
return done(null, user);
} else {
return done(null, false);
}
});
}
)
);
// CONFIGURE AUTHENTICATED SESSION PERSISTENCE
passport.serializeUser(function (user, done) {
done(null, user.id);
});
passport.deserializeUser(function (id, done) {
User.findById(id, function (err, user) {
done(err, user);
});
});
};
auth.js (for Google authentication)
const express = require("express");
const passport = require("passport");
const router = express.Router();
const login = "http://localhost:3000/login";
const diary = "http://localhost:3000/diary";
router.get(
"/google",
passport.authenticate("google", {
scope: ["email", "profile"],
})
);
router.get(
"/google/movie-log",
passport.authenticate("google", {
successRedirect: diary,
failureRedirect: login,
})
);
module.exports = router;
login.js (for local authentication)
const express = require("express");
const passport = require("passport");
const JWT = require("jsonwebtoken");
const router = express.Router();
const signToken = (userID) => {
return JWT.sign(
{
iss: "Movie.log",
sub: userID,
},
process.env.SECRET,
{ expiresIn: "1h" }
);
};
router
.route("/")
.post(passport.authenticate("local", { session: false }), (req, res) => {
if (req.isAuthenticated()) {
const { _id, first_name } = req.user;
const token = signToken(_id);
res.cookie("access_token", token, { httpOnly: true, sameSite: true });
res.status(200).json({
isAuthenticated: true,
user: first_name
});
}
});
module.exports = router;

how to use passport-google-oauth20 twice in the same app

I'm trying to use the passport-google-oauth20 twice in my node app but it's not working. Still learning passport authentication.
So here's the problem:
i made a button for the user to login using google(/auth.google) and a button for a user to register using google(/users/google). I want it so that if the user is already registered on my app using google and he goes on a presses "register using google" button, he should be redirected back to the register page(auth fails). In my case when i add the strategy once and add a login button only(without register), auth works and users is redirected to dashboard. However, when i add the strategy again in passport.js and the 'already registered user" tries to login using google, he gets redirected to register page(where the register using google button is found) instead of dashboard. I know there's no need to do such thing and no need to use the same strat again for registeration as it works the same for logging in. just wondering if it's possible to work.
Here's my code:
Passport.js
const GoogleStrategy = require("passport-google-oauth20").Strategy;
const myClientId = require("./keys").clientID;
const myClientSecret = require("./keys").clientSecret;
// get the user schema
const User = require("../models/User");
// passport google authentication
module.exports = function (passport) {
passport.use(
new GoogleStrategy(
{
clientID: myClientId,
clientSecret: myClientSecret,
callbackURL: "/auth/google/callback",
},
(accessToken, refreshToken, profile, done) => {
const newUser = new User({
googleId: profile.id,
displayName: profile.displayName,
firstName: profile.name.givenName,
lastName: profile.name.familyName,
image: profile.photos[0].value,
});
User.findOne({ googleId: profile.id })
.then((user) => {
if (user) {
done(null, user);
} else {
newUser.save().then((userd) => {
done(null, userd);
});
}
})
.catch((err) => console.log(err));
}
)
);
// using same strategy again but different callback URL
passport.use(
new GoogleStrategy(
{
clientID: myClientId,
clientSecret: myClientSecret,
callbackURL: "/users/google/callback",
},
(accessToken, refreshToken, profile, done) => {
const newUser = new User({
googleId: profile.id,
displayName: profile.displayName,
firstName: profile.name.givenName,
lastName: profile.name.familyName,
image: profile.photos[0].value,
});
User.findOne({ googleId: profile.id })
.then((user) => {
if (user) {
done(null, false);
} else {
newUser.save().then((userd) => {
done(null, userd);
});
}
})
.catch((err) => console.log(err));
}
)
);
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser((id, done) => {
User.findById(id, (err, user) => {
done(err, user);
});
});
};
app.js
/* initalized passport, session and other stuff here but just showing you the routes and the passport
function in app.js */
require("./config/passport")(passport);
app.use("/auth", require("./routes/auth"));
app.use("/users", require("./routes/users"));
auth.js
const express = require("express");
const passport = require("passport");
const router = express.Router();
//? register and login handle
router.get(
"/google",
passport.authenticate("google", {
scope: ["profile"],
})
);
//? google auth callback
router.get(
"/google/callback",
passport.authenticate("google", {
successRedirect: "/dashboard",
failureRedirect: "/",
})
);
module.exports = router;
users.js
const express = require("express");
const router = express.Router();
const passport = require("passport");
const { ensureAuthenticated, forwardAuth } = require("../config/checkAuth");
//? register page
router.get("/register", forwardAuth, (req, res) => {
res.render("register");
});
//? login page
router.get("/login", forwardAuth, (req, res) => {
res.render("login");
});
//? logout handle
router.get("/logout", (req, res) => {
req.logOut();
res.redirect("/");
});
router.get(
"/google",
passport.authenticate("google", {
scope: ["profile"],
})
);
router.get(
"/google/callback",
passport.authenticate("google", {
successRedirect: "/dashboard",
failureRedirect: "/users/register",
})
);
module.exports = router;
So if the users logs in on /auth/google ...he gets redirected to the register page(/users/register)! Can't know why
I solved my problem.
the solution was actually simple. Just using the same passport.js file.
in passport.js
const GoogleStrategy = require("passport-google-oauth20").Strategy;
module.exports = function (passport) {
passport.use("firstUse", new GoogleStrategy(....))
passport.use("secondUse", new GoogleStrategy(...))
}
so the thing is that you can register the strategy with any name you want and then when you want to handle the authorization middleware in the routes you just do this:
in users and auth.js
app.get("/auth/google/callback", passport.authenticate("firstUse"));
app.get("/users/google/callback", passport.authenticate("secondUse"));
just add the name that you registered with. I thought that it is necessary to only use passport.authenticate("google") and not any name else than "google".

How to use passport with next.js API?

I'm trying to use passport-spotify with next.js's pages/api (the one where you use export default (req, res)...), but I can't get it to redirect to Spotify's authorization page. This is my code for pages/api/spotify.js:
var passport = require('passport');
const SpotifyStrategy = require('passport-spotify').Strategy;
passport.use(
new SpotifyStrategy(
{
clientID: clientid,
clientSecret: clientsecret,
callbackURL: 'http://localhost:3000/auth/spotify/callback'
},
function(accessToken, refreshToken, expires_in, profile, done) {
User.findOrCreate({ spotifyId: profile.id }, function(err, user) {
return done(err, user);
});
}
)
);
export default (req, res, next) => {
passport.authenticate('spotify', {
scope: ['user-read-email', 'user-read-private'],
showDialog: true
}),
res.end()
}
I've tried using express to test it, and it works there. Here's my code:
const express = require('express')
const app = express()
const port = 8000
var passport = require('passport');
const SpotifyStrategy = require('passport-spotify').Strategy;
passport.use(
new SpotifyStrategy(
{
clientID: clientid,
clientSecret: clientsecret,
callbackURL: 'http://localhost:3000/api/callback'
},
function(accessToken, refreshToken, expires_in, profile, done) {
User.findOrCreate({ spotifyId: profile.id }, function(err, user) {
return done(err, user);
});
}
)
);
app.get(
'/auth/spotify',
passport.authenticate('spotify', {
scope: ['user-top-read'],
showDialog: true
}),
function(req, res) {
// The request will be redirected to spotify for authentication, so this
// function will not be called.
}
);
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
Is there a way to have Next.js work with Passport?
I got it! I used next-connect. Here's an example Spotify authentication request with passport-spotify and next.js:
// pages/api/spotify.js
import nc from 'next-connect';
var passport = require('passport');
const SpotifyStrategy = require('passport-spotify').Strategy;
passport.use(
new SpotifyStrategy(
{
clientID: clientid,
clientSecret: clientsecret,
callbackURL: 'http://localhost:3000/api/callback'
},
function(accessToken, refreshToken, expires_in, profile, done) {
User.findOrCreate({ spotifyId: profile.id }, function(err, user) {
return done(err, user);
})
}
))
const handler = nc()
.get(passport.authenticate('spotify', {
scope: ['user-top-read']
}), (req, res) => {
})
export default handler;

SSL is blocking login with Passport in production, fully working on localhost

I've built a small authentication app with Passport. It works just fine on localhost, which by the way is insecure. But on my domain, with Cloudflare SSL I'm not sure what's going on, I can't reach consent screen with Google and GitHub strategies. I don't have much knowledge about ssl certificate and want to know what part is wrong on my server.
I have added app.set('trust proxy'), app.use(cors()), set 'proxy: true' in both strategies.
Here is the relevant part of the code and the live code on my domain https://authapp.mambaoro.com
In index.js
app.set('trust proxy', 1);
app.use(cors());
app.use(
session({
secret: process.env.SESSION_KEY_1,
proxy: true,
saveUninitialized: false,
resave: false,
maxAge: 6.048e8,
}),
);
app.use(passport.initialize());
app.use(passport.session());
app.use('/auth', authRoutes);
app.get('/getUser', (req, res) => {
if (!req.user) {
return res.send({ isAuthenticated: false });
}
res.send(req.user);
});
In passport-setup.js
passport.use(
new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: process.env.GOOGLE_CALLBACK_URL,
proxy: true,
},
async (accesToken, refreshToken, profile, done) => {
try {
await User.sync();
const newUser = await User.findOrCreate({
where: {
googleId: profile.id,
username: profile.displayName,
profileImageUrl: profile.photos[0].value,
},
});
done(null, newUser[0]);
} catch (e) {
done(e);
}
},
),
);
passport.use(
new GithubStrategy(
{
clientID: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
callbackURL: process.env.GITHUB_CALLBACK_URL,
proxy: true,
},
async (accessToken, refreshToken, profile, done) => {
try {
await User.sync();
const newUser = await User.findOrCreate({
where: {
githubId: profile.id,
username: profile.username,
profileImageUrl: profile.photos[0].value,
},
});
done(null, newUser[0]);
} catch (e) {
done(e);
}
},
),
);
In auth-routes.js
// auth with google
router.get(
'/google',
passport.authenticate('google', {
scope: ['profile'],
}),
);
// auth with github
router.get('/github', passport.authenticate('github', { scope:
['profile'] }));
In index.js On the client with React, the code below is used to get the user details with Passport if a cookie exists.
function SignUser() {
const [user, setUserData] = useState(null);
const [isAuthenticated, setIsAuth] = useState(false);
useEffect(() => {
const getUser = async () => {
const res = await axios.get('/getUser');
res.data.user && setUserData(res.data.user);
res.data.isAuthenticated && setIsAuth(true);
};
getUser();
}, []);
...

Linkedin Passport Oauth failureRedirect

Good afternoon,
I'm working in a node application. Concretely I'm working with "passport-linkedin-oauth2".
There is my code.
linkedin/index.js
'use strict';
var express = require('express');
var passport = require('passport');
var auth = require('../auth.service');
var router = express.Router();
router
.get('/', passport.authenticate('linkedin', {
state: 'comienzo'
}),
function(req, res){
// The request will be redirected to Linkedin for authentication, so this
// function will not be called.
})
.get('/callback', passport.authenticate('linkedin', {
failureFlash : true,
failureRedirect: '/login'
}), auth.setTokenCookie);
module.exports = router;
linkedin/passport.js
var passport = require('passport');
var LinkedInStrategy = require('passport-linkedin-oauth2').Strategy;
var models = require('../../api');
exports.setup = function (User, config) {
passport.use(new LinkedInStrategy({
clientID: config.linkedin.clientID,
clientSecret: config.linkedin.clientSecret,
callbackURL: config.linkedin.callbackURL,
scope: [ 'r_basicprofile', 'r_emailaddress'],
state: true
},
function(accessToken, refreshToken, profile, done) {
process.nextTick(function () {
// To keep the example simple, the user's LinkedIn profile is returned to
// represent the logged-in user. In a typical application, you would want
// to associate the LinkedIn account with a user record in your database,
// and return that user instead.
return done(null, profile);
});
models.User.findOrCreate({
where: {
linkedin: profile.id
},
defaults: {
name: profile.displayName,
linkedin: profile.id,
mail: profile.emails[0].value,
password: 'xxxxxxx',
role: 'admin', provider: 'linkedin',
activo: true
}
}).spread(function (user, created) {
console.log("x: " +user.values);
return done(null, user)
}).catch(function (err) {
console.log('Error occured', err);
return done(err);
});
}
));
};
The problem I'm facing is that I'm pretty sure that LinkedIn is logging properly.
In my app when i press login button it redirect me to LinkedIn webpage, I fill the information and then my server receives this answer
GET /auth/linkedin/callback?code=AQTfvipehBLAXsvmTIl1j3ISYCzF03F-EilhiLlfSJNqiwfQsyHeslLONOWY12Br-0dfV1pgkSSpCKlmtpiMVUCufJlatEBswWqfPe6iahoRF8IHIhw&state=comienzo 302 4ms - 68b
I think that this means that it is ok because I get the state that I have sent to LinkedIn API before and the code.
Anyway, every time I login always redirect me to Login page which is failureRedirect: '/login' ( I have tested that if I change this route, the app redirect me where this attribute point)
Also I have checked that it never executes the code that search in the db for the linkedin user.
Remove the state property on the handler or at the strategy instantiation, i'm not sure why but this solves the issue.
exports.setup = function (User, config) {
passport.use(new LinkedInStrategy({
clientID: config.linkedin.clientID,
clientSecret: config.linkedin.clientSecret,
callbackURL: config.linkedin.callbackURL,
scope: [ 'r_basicprofile', 'r_emailaddress'],
state: true // <-- Remove state from here
})
}
and this code
router
.get('/', passport.authenticate('linkedin', {
state: 'comienzo' // <-- Or Remove state from here
}),
You can just set it the state on one of this places but not both, so remove one of them

Resources