Next.js implementing google signin with /api routes and passport - passport.js

I'm trying to implement Google signin in my serverless (/api routes) next.js app.
I'm using #passport-next/passport-google-oauth2, next-connect and passport packages.
I searched a lot and found a few helpful links online but I couldn't make it work, and I'm not sure about the whole flow that should happen here.
For example, I found those:
https://github.com/andycmaj/nextjs-passport-session-auth
https://todayilearned.io/til/nextjs-with-passport-oauth-cookie-sessions
I have /api/auth/login route for regular login. If login was successful, I'm setting JWT cookie on user response.
For Google login, I added /api/auth/social/google route, with the following code:
import passport from 'passport';
import { Strategy as GoogleStrategy } from '#passport-next/passport-google-oauth2';
import nextConnect from 'next-connect';
passport.use(new GoogleStrategy({
clientID: process.env.OAUTH_GOOGLE_CLIENT_ID,
clientSecret: process.env.OAUTH_GOOGLE_CLIENT_SECRET,
callbackURL: process.env.OAUTH_GOOGLE_CALLBACK_URL,
scope: "https://www.googleapis.com/auth/plus.login",
},
(accessToken, refreshToken, googleUserInfo, cb) => {
console.log('accessToken, refreshToken, googleUserInfo');
cb(null, googleUserInfo);
}
));
export default nextConnect()
.use(passport.initialize())
.get(async (req, res) => {
passport.authenticate('google')(req, res, (...args) => {
console.log('passport authenticated', args)
})
})
and /api/auth/social/callback/google route, with the following code:
import passport from 'passport';
import nextConnect from 'next-connect';
passport.serializeUser((user, done) => {
console.log('serialize')
done(null, user);
});
export default nextConnect()
.use(passport.initialize())
.get(async (req, res) => {
passport.authenticate('google', {
failureRedirect: '/failure/success',
successRedirect: '/auth/success',
})(req, res, (...args) => {
console.log('auth callback')
return true;
})
})
So what happens is that the user is redirected to /auth/success after signin to his google account, and console logs are:
accessToken, refreshToken, googleUserInfo
serialize
So my questions are:
When and how can I set the JWT cookie on the response to "login" the user?
Why the line console.log('auth callback') never runs? when it should run?
The same for console.log('passport authenticated', args)
How is a complete flow should look like in my app?
Thanks !

I had the same problem. I'm still working on the full implementation, but according to this thread it looks like the problem might be that passport.authenticate is supposed to be used as a middleware:
// api/auth/social/callback/google
export default nextConnect()
.use(passport.initialize())
.get(passport.authenticate("google"), (req, res) => {
console.log('auth callback')
res.writeHead(302, {
'Location': '/auth/success'
});
res.end();
})

Ok, I'm not a Next.js expert but I had a similar "problem", only using other authentication method.
You see, the framework has a method that is really important in the authentication process, they are called: getInitialProps, which is an async function that can be added to any page as a static method to trigger before the initial render;
it looks like this:
static async getInitialProps(ctx) {
const res = await fetch('https://api.github.com/repos/vercel/next.js')
const json = await res.json()
return { stars: json.stargazers_count }
}
It takes this ctx prop, which is a object with {req, res} that you can use to make your request to authenticate the user and, then, you may use req with the lib next-cookies to get the JWT token and check if it is valid and, then, you may return an object, which will be used as a prop of your page (or all the pages, if you wrap your _app.js around a Context of Authentication.
Also, your 'auth callback' is not being called because you redirect the user before it triggers, which is not happening. The same for 'passport authenticated'
This article may also help you.
https://dev.to/chrsgrrtt/easy-user-authentication-with-next-js-18oe

Related

Authentication using linkedin in a mean stack application

so I've been trying to implement login with linkedin in my application, and I couldn't find anything online that could show me the steps from A to Z
I implemented the backend and the frontend separateley, however, I don't know how to link them together
In the backend, I'm using passportjs
So here's what I've done so far:
FRONTEND
app.component.html
<button click="loginWithLinkedin()">Linkedin</button>
app.component.ts
window.location.href = `https://www.linkedin.com/uas/oauth2/authorization?response_type=code&state=true&client_id=${environment.LINKEDIN_API_KEY}&redirect_uri=${environment.LINKEDIN_REDIRECT_URL}&scope=${environment.LINKEDIN_SCOPE}`;
redirect.component.ts
const linkedInToken = this.route.snapshot.queryParams["code"];
this.http.get('http://localhost:3000/user/auth/linkedin',
{ params: { token: linkedinToken }}).subscribe(res => {
console.log(res);
});
BACKEND
passport.use(new LinkedInStrategy({
clientID: LINKEDIN_CLIENT_ID,
clientSecret: LINKEDIN_CLIENT_SECRET,
callbackURL: "http://127.0.0.1:8000/user/auth/linkedin/callback",
scope: ['r_emailaddress', 'r_basicprofile'],
passReqToCallback: true
},
function (req, accessToken, refreshToken, profile, done) {
req.session.accessToken = accessToken;
process.nextTick(function () {
return done(null, profile);
});
}));
linkedinRouter.route('/auth/linkedin')
.get(passport.authenticate('linkedin', { state: 'SOME STATE' }),
function(req, res){
// The request will be redirected to LinkedIn for authentication, so this
// function will not be called.
});
linkedinRouter.route('/auth/linkedin/callback')
.get( passport.authenticate('linkedin', { failureRedirect: '/' }),
function (req, res) {
return res.send('hello');
});
I don't understand how would passport work and I don't understand how to link backend and frontend.
I don't know if this is the correct way to implement linkedin authentication
If you have any articles that could guide, or if you can correct that would really help, I've been stuck for a couple of days now.
Thank you very much
I apologize if I formulated the question in a bad way or I didn't know how to ask the question. However, I managed to make it work and I wrote a medium article about it.
here's the link:
https://nour-karoui.medium.com/linkedin-authentication-for-a-mean-stack-application-bd8542b2dc7f
I hope it can help somebody out there !

How can I get the user profile data in passport google auth callback function?

I'm using Passport Google auth for authentication. I am just confused about how I can pass the user profile data to the callback URL passed to passport google strategy.
In reference to the above picture, I have passed a callback URL to passport google strategy. I want the below user data object on this callback URL. How can I achieve this?
This is my callback path:
And the handler for this route:
I am getting only the code, scope params over there:
In Short, I want to return the custom jwt token to the frontend when the OAuth successfully completes. Can someone please help me achieve this?
Thanks in Advance.
After having a lot of search over the internet, finally I found the solution for this. To achieve this, you have to use the req argument in the callback and attach the user data with it as shown in the screenshot below:
and then in the callback, you can get the user from req._user
Passport google strategy Code:
passport.use(new GoogleStrategy({
clientID: config.googleOAuth.clientId,
clientSecret: config.googleOAuth.clientSecret,
callbackURL: `${config.endpoints.apiUrl}/user/gmail/oauth`,
passReqToCallback: true
},
async function (req, accessToken, refreshToken, profile, done) {
const profileJson = profile._json;
const userData = {
username: profileJson.email,
firstname: profileJson.given_name,
lastname: profileJson.family_name,
picture: profileJson.picture,
authType: 'oauth'
}
const user = await userCtrl.createOrUpdateUser(userData);
req._user = userData;
return done(null, user);
}))
Callback Code:
const OAuthCallback = async (req, res, next) => {
// function called after successfull oauth
console.log(req._user)
}

Using Google function with Node-express-passport

I have used passport-twitter strategy and it so happens that I am unable to see passport-login window on my client side when I execute my stratergy
So this is what I am doing, initially
class Auth extends ParentClass {
constructor(context, options) {
super()
this.app.get('/callback/:service', (req, res) =>
this.callbackEndpoint(req,res))
this.app.get('/', (req, res, next) => this.loginEndpoint(req, res, next))
}
async loginEndpoint (req, res, next) {
if (req.query.Twitter) {
console.log(`Inside Twitter Authentication`)
passport.authenticate('twitter', { scope : ['email'] })(req,res,next);
}
}
where in my ParentClass I am more or less initialising stuff
class ParentClass {
this.use(corsMiddleware(options.CORS_URLS))
this.use(bodyParser.json())
this.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: true,
cookie: { secure: true }
}))
this.use(passport.initialize())
this.use((passport.session()))
}
use (middleware) {
this.app.use(middleware)
}
}
And finally, this is my passport stratergy
passport.use(new TwitterStrategy({
consumerKey: functions.config().twitterCredentials.apikey,
consumerSecret: functions.config().twitterCredentials.apiSecretKey,
callbackURL: redirect_uri,
passReqToCallback: true
}, async (req, accessToken, refreshToken, params, profile, done) => {
console.log(`logging from Passport Twitter ${req.workspace, req.accessToken}`)
done()
}))
From the first code, snippet I see the log Inside Twitter Authentication but thereafter nothing happens.
What I was expecting? A window to show up for me to log into twitter but nothing shows up and after sometime I get this in my console info: Execution took 569847 ms, finished with status: 'timeout'
So here are my Questions:
1. Can we use passport and normal oauth flow with google-functions? (just comment if not answer)
2. If yes, can we use spot the error in my above code.
Update note: While Googling for my problem, I stumbled upon this old question and realised that while it was somewhat similar but the description for the previous one was very vague. I asked a similar question later today but decided to merge it with this one.
In the first part of your code, you need to pass passport.authenticate directly to the get handler. Like this:
class Auth extends ParentClass {
constructor(context, options) {
super()
this.app.get('/callback/:service', (req, res) =>
this.callbackEndpoint(req,res))
this.app.get('/', passport.authenticate('twitter', { scope : ['email'] })(req,res,next))
}
}
I assume this "App" is mounted at some path like /auth/twitter
In your front end, you open a popup window with URL pointing to /auth/twitter. Something like this:
<button onclick="oauthPrompt('twitter')">Login with Twitter</button>
<script>
function oauthPrompt(service) {
const url = '/auth/' + service
var newWindow = window.open(url, 'name', 'height=600,width=450')
if (window.focus) {
newWindow.focus();
}
}
</script>

PassportJS openid-client How to use without a session, save token/user in normal cookie

We are currently using the https://github.com/panva/node-openid-client strategy for passportJS, along with cookie-session.
However we would like to try and move away from sessions and just store either a simple cookie with a token, or attach the token to a header on each request which we then introspect to see whether the token is valid.
I can't figure it out, maybe it's not possible, I just simply don't know where or how I can retrieve the token from the openid-client library, and when and how I should save it in a cookie. Maybe it is only built to use a session.
currently we have:
passport.use(
`oidc.${site}`,
new Strategy(
{
client,
params: getParamsForSite(site),
passReqToCallback,
usePKCE,
},
(tokenset, done) => {
const user = {
token: tokenset,
name: tokenset.claims.sub,
};
return done(null, user);
}
)
);
for the login
app.get(['/login', '/login/:site'], (req, res, next) => {
if (req.params.site) {
passport.authenticate(`oidc.${req.params.site}`)(req, res, next);
} else {
res.end(loginFrontend.success());
}
});
and for the callback
app.get('/auth_callback', (req, res, next) => {
passport.authenticate(`oidc.${req.query.state}`, {
callback: true,
successReturnToOrRedirect: process.env.BASE_URI,
})(req, res, next);
});
We would like to continue using this library as the authentication service we call has a discovery endpoint etc. and wouldn't want to implement all of the features ourselves. If I set session to false, how do I retrieve the token and where for this strategy, can someone help me?

passport: different redirect for login and account registration

i'm using the passport module (github authentication) in my app and i want to redirect depending on the action ... i check if it's just a normal login or if the user logs in for the first time.
passport.use(new GitHubStrategy({
clientID: conf.github.app_id,
clientSecret: conf.github.app_secret,
callbackURL: conf.github.callback_url
},
function(accessToken, refreshToken, profile, done) {
// asynchronous verification, for effect...
process.nextTick(function () {
// To keep the example simple, the user's GitHub profile is returned to
// represent the logged-in user. In a typical application, you would want
// to associate the GitHub account with a user record in your database,
// and return that user instead.
Models_User.findOrCreateUser(profile, function(msg){
console.log("auth type:" + msg);
});
return done(null, profile);
});
}
));
in my findOrCreateUser function i check if it's a new user and do all the db action ... for testing i let the function return a msg variable which is only a string that says "login" or "new_registration".
so my question is how to "transport" that variable that i get from findOrCreateUser so that i can redirect accordingly ("/welcome" or "/back_again") after the passport auth is finished.
the other passport code in my app:
// GET /auth/github
// Use passport.authenticate() as route middleware to authenticate the
// request. The first step in GitHub authentication will involve redirecting
// the user to github.com. After authorization, GitHubwill redirect the user
// back to this application at /auth/github/callback
app.get('/auth/github',
passport.authenticate('github'),
//passport.authenticate('github', { scope: ['user', 'public_repo', 'gist'] }),
function(req, res){
// The request will be redirected to GitHub for authentication, so this
// function will not be called.
});
// GET /auth/github/callback
// Use passport.authenticate() as route middleware to authenticate the
// request. If authentication fails, the user will be redirected back to the
// login page. Otherwise, the primary route function function will be called,
// which, in this example, will redirect the user to the home page.
app.get('/auth/github/callback',
passport.authenticate('github', { successRedirect: '/', failureRedirect: '/login' }),
function(req, res) {
res.redirect('/');
});
In your verify callback, I would change things up so that the findOrCreateUser function supplies the actual record to the callback, and then pass that through to done(), like so:
Models_User.findOrCreateUser(profile, function(user){
console.log("auth type:" + msg);
return done(null, user);
});
// take this out, use the actual model above
//return done(null, profile);
Now, when handling the callback URL after authentication, you can check this user record and see if it was new (I'm assuming it has an isNew property here):
app.get('/auth/github/callback',
passport.authenticate('github', { failureRedirect: '/login' }),
function(req, res) {
// successful auth, user is set at req.user. redirect as necessary.
if (req.user.isNew) { return res.redirect('/back_again'); }
res.redirect('/welcome');
});

Resources