How to handle twitch passport.js auth code? - node.js

I'm trying to build a simple app that authenticates a twitch account and displays a users information. I'm stuck with how to send along my auth code once a user has successfully logged in.
Server-side, my code looks like this:
---auth-routes.js
// auth with twitch
router.get("/twitch", passport.authenticate("twitch", { scope: "user_read" }), (req, res) => {
res.status(200).json({message: 'Authenticating...'});
console.log('Authenticating...')
});
// redirect to home page after successful login via twitch
router.get(
"/twitch/redirect",
passport.authenticate("twitch", {
successRedirect: "/auth/twitch/redirect",
failureRedirect: "/auth/login/failed"
})
);
---config/passport-setup.js
// Override passport profile function to get user profile from Twitch API
OAuth2Strategy.prototype.userProfile = function(accessToken, done) {
var options = {
url: 'https://api.twitch.tv/helix/users',
method: 'GET',
headers: {
'Client-ID': TWITCH_ID,
'Accept': 'application/vnd.twitchtv.v5+json',
'Authorization': 'Bearer ' + accessToken
}
};
request(options, function (error, response, body) {
if (response && response.statusCode == 200) {
done(null, JSON.parse(body));
} else {
done(JSON.parse(body));
}
});
}
passport.serializeUser(function(user, done) {
done(null, user);
});
passport.deserializeUser(function(user, done) {
done(null, user);
});
passport.use('twitch', new OAuth2Strategy({
authorizationURL: 'https://id.twitch.tv/oauth2/authorize',
tokenURL: 'https://id.twitch.tv/oauth2/token',
clientID: TWITCH_ID,
clientSecret: TWITCH_SECRET,
callbackURL: TWITCH_CB,
state: true
},
function(accessToken, refreshToken, profile, done) {
profile.accessToken = accessToken;
profile.refreshToken = refreshToken;
console.log(profile);
// Securely store user profile in your DB
//User.findOrCreate(..., function(err, user) {
// done(err, user);
//});
done(null, profile);
}
))
I also have a simple profile component that displays when auth/twitch/redirect route is hit
export const AppRouter = () => {
return (
<Router>
<div>
<Route exact path='/' component={HomePage} />
<Route path='/auth/twitch/redirect' component={Profile} />
</div>
</Router>
)
}
According to the twitter documentation, you need to take the access code appended to your redirect URI and make a post request with it. I'm having trouble figuring out how and where to pull that code and send it along. Here's what they say in the documentation:
In our example, your user gets redirected to:
http://localhost/?code=394a8bc98028f39660e53025de824134fb46313
&scope=viewing_activity_read
&state=c3ab8aa609ea11e793ae92361f002671
3) On your server, get an access token by making this request:
POST https://id.twitch.tv/oauth2/token
?client_id=<your client ID>
&client_secret=<your client secret>
&code=<authorization code received above>
&grant_type=authorization_code
&redirect_uri=<your registered redirect URI>
Here is a sample request:
POST https://id.twitch.tv/oauth2/token
?client_id=uo6dggojyb8d6soh92zknwmi5ej1q2
&client_secret=nyo51xcdrerl8z9m56w9w6wg
&code=394a8bc98028f39660e53025de824134fb46313
&grant_type=authorization_code
&redirect_uri=http://localhost
Thanks for any help!

I'm struggeling with the same problem. However, I think I might help a little bit.
I also see that this is a bit old post so you might have figured it out already.
To get the hash or fragment from the URL using router, you can:
import React, { useEffect } from "react";
import { useLocation } from "react-router-dom";
const yourCallbackComponent = () => {
let location = useLocation();
useEffect(() => {
console.log(location)
}, [location])
return ()
}
I wrote this in a hurry, sorry if it's bad. But when a route is loaded you can extract information in the location object.
The expected output of the console.log should be somewhere near this:
{
key: 'ac3df4', // not with HashHistory!
pathname: '/somewhere',
search: '?access_token:jf82rjfj02f0f',
hash: '#access_tokenjf82rjfj02f0f',
state: {
[userDefined]: true
}
}
It's either in the hash or search, depending on what you get back from twitch.
Hope this helps if you didn't find a solution already.
I'm still trying to figure out the next steps myself so I can't be of any assistance there.
Let me know if you're still stuck, then I'll post my findings when I figure out how this works.

Related

Google OAuth 2.0 doesn't call the callback function

I've tested this passport google strategy locally and it works well, the callback function is called successfully and I can login using the google account. However when I push this code to the production server I see that the callback function is not being called. From the user perspective when they click on the link to use the google strategy they are presented with the google 'pick an account' screen, and then it loads for a bit before dumping them back on the page they were on before. I don't see where the problem is. Thank you in advance for your help!
passport.use('google', new GoogleStrategy({
clientID: "redacted",
clientSecret: "redacted",
callbackURL: "/google/callback"
},
function(accessToken, refreshToken, profile, done) {
const userQueryString = `
SELECT firstName, lastName, AgentReference, agent_number, website_password, account_type, status, state, BIN(Flags) as flags
FROM compass.agent
WHERE googleID = ?`;
console.log("google profile: \n", profile);
//pull the user data for the agent with the same googleID as the selected accoutn at login.
db.query(userQueryString,[profile.id],(err, response, fields)=>{
if(err) {
done(err);
} else if(response.length === 0) {
done(null, false);
} else if(response[0].status === 'Terminated') {
console.log('account is terminated.');
done(null, false);
} else {
let user = {
id: response[0].AgentReference
, accountType: response[0].account_type
, agent_number:response[0].agent_number
, name:response[0].firstName + ' ' + response[0].lastName
, state: response[0].state
, flags: response[0].flags
};
let flagRegex = RegExp('^[0-1]*1[0-1]{9}$');
if(flagRegex.test(user.flags)){
console.log(JSON.stringify(user));
return done(null, user);
} else {
return done(null, false, {message: "You do not have access to this feature. Please speak with your manager for more information."})
}
}
});
}
)
);
Adding proxy:true to the google strategy fixed the issue for me. The callback URL was being sent to google as http instead of https because of this.

Passing JWT to UI application after Google Oauth2 login

I have created a MERN application with a separate backend and frontend. I have added support for Google Oauth2 login using passport-google-oauth20 npm package.
So I have exposed an end point in the backend as follows:
class AccountAPIs {
constructor() { }
redirectToSuccess(req, res) {
const accountServiceInst = AccountService.getInst();
let account = req.session.passport.user;
let filePath = path.join(__dirname + '../../../public/views/loginSuccess.html');
let jwt = accountServiceInst.generateJWT(account);
// how do I send this jwt to ui application
res.sendFile(filePath);
}
loadMappings() {
return {
'/api/auth': {
'/google': {
get: {
callbacks: [
passport.authenticate('google', { scope: ['profile', 'email'] })
]
},
'/callback': {
get: {
callbacks: [
passport.authenticate('google', { failureRedirect: '/api/auth/google/failed' }),
this.redirectToSuccess
]
}
},
'/success': {
get: {
callbacks: [this.successfulLogin]
}
}
}
}
};
}
}
Here is the passport setup for reference:
let verifyCallback = (accessToken, refreshToken, profile, done) => {
const accountServiceInst = AccountService.getInst();
return accountServiceInst.findOrCreate(profile)
.then(account => {
return done(null, account);
})
.catch(err => {
return done(err);
});
};
let googleStrategyInst = new GoogleStrategy({
clientID: serverConfig.auth.google.clientId,
clientSecret: serverConfig.auth.google.clientSecret,
callbackURL: 'http://localhost/api/auth/google/callback'
}, verifyCallback);
passport.use(googleStrategyInst);
In the UI application, on button click I am opening a new window which opens the '/api/auth/google' backend API. After authenticating with a google account, the window redirects to the '/api/auth/google/callback' backend API where I am able to generate a JWT. I am unsure about how to transfer this JWT to the frontend application since this is being opened in a separate window.
I know that res.cookie('jwt', jwt) is one way to do it. Please suggest the best practices here..
There are two ways to pass the token to the client :
1- you put the token into a cookie as you have mentioned
2-you pass the token in the redirect URL to the client as a parameter "CLIENT_URL/login/token", that you can extract the token in your front-end client

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

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

401 Unauthorized Request Discord API with OAuth

I'm wanting to allow users of my site that use Discord to be able to "automatically" join my guild.
I have everything done except I always get a 401: Unauthorized from Discord's API using the following;
router.get("/cb", passport.authenticate("discord", { failureRedirect: "/" }), async function(req, res) {
const data = { access_token: req.user.accessToken };
axios.put(`https://discordapp.com/api/v8/guilds/${config.CyberCDN.server_id}/members/${req.user.id}`, {
headers: {
"Content-Type": "application/json",
"Authorization": `Bot ${config.CyberCDN.bot_token}`
},
body: JSON.stringify(data)
}).then((success) => {
console.log(`[DASHBOARD] ${req.user.username}#${req.user.discriminator} - Logging in...`);
console.log(success.config.data)
console.log(success.response.status)
return res.status(200).redirect("/");
}).catch((error) => {
console.log(`[DASHBOARD] ${req.user.username}#${req.user.discriminator} - Failed Logging in...`);
console.log(error.config.data.replace(config.CyberCDN.bot_token,"TOKEN"))
console.log(error.response.status)
return res.status(403).redirect("/");
});
});
I don't understand how when everything I have done is correct;
I have even asked in the Discord-API server regarding this matter with the same issue,
I did however have it working ONE TIME and now it's broke again, I have 0 clue how it broke.
My scopes are as follow "oauth_scopes": ["guilds.join"]
I found a better solution to this problem I had:
const DiscordOauth2 = require("discord-oauth2");
const discord = new DiscordOauth2();
/**
* Other required stuff for express.js goes here...
*/
router.get("/login", passport.authenticate("discord"));
router.get("/cb", passport.authenticate("discord", { failureRedirect: "/forbidden" }), async function(req, res) {
req.session.user = req.user;
res.redirect('/');
});
router.get("/support", authOnly, async function(req, res) {
discord.addMember({
accessToken: req.session.user.accessToken,
botToken: config.CyberCDN.bot_token,
guildId: config.CyberCDN.server_id,
userId: req.session.user.id,
roles: [config.CyberCDN.site_role]
}).then((r) => {
if(r) {
let date = new Date(r.joined_at);
res.status(200).json({ status: "Joined Server" });
const embed = new Embed()
.title(`New User Joined Via Site\n${r.user.username}#${r.user.discriminator}`)
.colour(16763904)
.thumbnail(`https://cdn.discordapp.com/avatars/${r.user.id}/${r.user.avatar}.webp?size=128`)
.footer(`User joined at: ${date.toLocaleDateString()}`)
.timestamp();
dhooker.send(embed);
console.log(r)
}else{
res.status(401).json({ status: "Already In There?" });
}
});
});
Basically through browsing my initial 401: Unauthorized error stumbled across a nice little OAuth2 NPM For Discord called discord-oauth2 developed by reboxer and numerous other people, which can be found here.
The helpful part was documented further down the README.md of that repo, in relation to my problem. Relation found here
I have also contributed that they add the removeMember feature also.

Google Oauth giving code redeemed error

Hi i am working on a project where a user logs in via google account.(localhost)
I have implemented the google signup.
As soon as I log in from my account I am getting the below error.
TokenError: Code was already redeemed.
at Strategy.OAuth2Strategy.parseErrorResponse (c:\Projects\Internship_rideshare\node_modules\passport-google-oauth\node_modules\passport-oauth\node_modules\passport-oauth2\lib\strategy.js:298:12)
at Strategy.OAuth2Strategy._createOAuthError (c:\Projects\Internship_rideshare\node_modules\passport-google-oauth\node_modules\passport-oauth\node_modules\passport-oauth2\lib\strategy.js:345:16)
at c:\Projects\Internship_rideshare\node_modules\passport-google-oauth\node_modules\passport-oauth\node_modules\passport-oauth2\lib\strategy.js:171:43
at c:\Projects\Internship_rideshare\node_modules\passport-google-oauth\node_modules\passport-oauth\node_modules\passport-oauth2\node_modules\oauth\lib\oauth2.js:176:18
at passBackControl (c:\Projects\Internship_rideshare\node_modules\passport-google-oauth\node_modules\passport-oauth\node_modules\passport-oauth2\node_modules\oauth\lib\oauth2.js:123:9)
at IncomingMessage.<anonymous> (c:\Projects\Internship_rideshare\node_modules\passport-google-oauth\node_modules\passport-oauth\node_modules\passport-oauth2\node_modules\oauth\lib\oauth2.js:142:7)
at IncomingMessage.emit (events.js:129:20)
at _stream_readable.js:908:16
at process._tickCallback (node.js:355:11)
My code is as follows(snippet for google login):-
passport.use(new GoogleStrategy(google, function(req, accessToken, refreshToken, profile, done) {
if (req.user) {
User.findOne({ google: profile.id }, function(err, existingUser) {
if (existingUser) {
console.log('There is already a Google+ account that belongs to you. Sign in with that account or delete it, then link it with your current account.' );
done(err);
} else {
User.findById(req.user.id, function(err, user) {
user.google = profile.id;
user.tokens.push({ kind: 'google', accessToken: accessToken });
user.profile.displayName = user.profile.displayName || profile.displayName;
user.profile.gender = user.profile.gender || profile._json.gender;
//user.profile.picture = user.profile.picture || 'https://graph.facebook.com/' + profile.id + '/picture?type=large';
user.save(function(err) {
console.log('Google account has been linked.');
done(err, user);
});
});
}
});
} else {
User.findOne({ google: profile.id }, function(err, existingUser) {
if (existingUser) return done(null, existingUser);
User.findOne({ email: profile._json.email }, function(err, existingEmailUser) {
if (existingEmailUser) {
console.log('There is already an account using this email address. Sign in to that account and link it with Google manually from Account Settings.' );
done(err);
} else {
var user = new User();
user.email = profile._json.email;
user.google = profile.id;
user.tokens.push({ kind: 'google', accessToken: accessToken });
user.profile.displayName = profile.displayName;
user.profile.gender = profile._json.gender;
//user.profile.picture = 'https://graph.facebook.com/' + profile.id + '/picture?type=large';
user.profile.location = (profile._json.location) ? profile._json.location.name : '';
user.save(function(err) {
done(err, user);
});
}
});
});
}
}));
I am stuck on it.Please help me out..thanks
The problem is not in your "snippet", look at the routes. It should be absolute path on redirect for google.
router.get('/auth/google/callback',
passport.authenticate('google', { failureRedirect: '#/signIn' }),
function(req, res) {
// absolute path
res.redirect('http://localhost:8888/#/home');
});
It's known issue, follow this link to other workarounds
https://github.com/jaredhanson/passport-google-oauth/issues/82
I have come across this issue. The exact problem is your route.
app.get('/auth/google/callback', passport.authenticate('google'), (req, res) => {
res.send('get the data');
});
At this point app had got user permission and google send a code to this url. Now what passport does here it took that code and made a request to google for user details and got it from google. Now we have to do something with this details otherwise you will get the error that you have got.
Now we can use serialiseUser and deserialiseUser of passport to save details in cookie and edit one line of above code to go at some url like that.
app.get('/auth/google/callback', passport.authenticate('google'), (req, res) => {
res.redirect('/servey'); // just a url to go somewhere
});
I also had the same problem since few days. What I figured out is, you just need to complete the process. Until now you have only checked whether the user is present in the database or not. If not then you save the user to the database.
However, after this, when the google tries to redirect the user, the code that google+ API sent is already used or say it is no longer available. So when you check the user in your database, you need to serialize the user i.e store the code into your browser in a cookie so that when google redirects the user, it know who the user is. This can be done by adding the code given below.
//add this in current snippet
passport.serializeUser(function(user,done){
done(null,user.id);
});
To use this cookie, you need to deserialize the user. To deserialize, use the code given below.
//add this in current snippet
passport.deserializeUser(function(id,done){
User.findById(id).then(function(user){
done(null, user);
});
});
Also, you are required to start a cookie session and you can do this by adding the below code in your main app.js file.
const cookieSession = require('cookie-session');
app.use(cookieSession({
maxAge: 24*60*60*1000, // age of cookie, the value is always given in milliseconds
keys:[keys.session.cookiekey]
}));
//initialize passport
app.use(passport.initialize());
app.use(passport.session());
Note that you need to require the cookie-session package. Install it using
npm install cookie-session
Also, you require to write absolute URI in the callbackURL property in your google strategy.
I had the same problem.
Reseting client secret from google console solved the problem.

Resources