I am using spotify strategy with passport Js for authentication. I then want to use the spotify-web-api-node wrapper library for calls to spotify for the data. However using the access and refresh tokens gained from the authentication for the subsequent calls is not working out. I am getting a 401 - unauthorised when trying to make a call to spotify for user specific data.
I tried instantiating the SpotifyWebApiObject with the refresh token and access token. Although I can see in the library it only needs the access token to make the calls. I have tried logging in and out to get new sets of the tokens as well.
passport.use(new SpotifyStrategy({
clientID: keys.spotifyClientID,
clientSecret: keys.spotifyClientSecret,
callbackURL: '/auth/spotify/callback',
proxy: true
}, async (accessToken, refreshToken, profile, done) => {
const spotifyId = profile.id;
const name = profile.displayName;
const email = profile.emails[0].value;
console.log(profile.id)
const existingUser = await User.findOne({ spotifyId: profile.id });
if (existingUser) {
let userCredentials = await UserCredential.findOne({ userId: spotifyId });
if (!userCredentials) {
return await new UserCredential({ userId: spotifyId, name, accessToken, refreshToken }).save();
}
console.log('always get existing user')
userCredentials.accessToken = accessToken;
userCredentials.refreshToken = refreshToken;
return done(null, existingUser);
}
const user = await new User({ spotifyId }).save();
await new UserCredential({ userId: spotifyId, name, accessToken, refreshToken }).save();
done(null, user);
}));
Once they are stored in the db. I do a look up for the user and use the respective access and refresh tokens.
const getPlaylists = async (user) => {
let spotifyData = await initiateSpotifyWebApi(user);
spotifyData.getUserPlaylists(user.spotifyId).then((res) => {
console.log('response is ===', res)
}).catch((err) => console.error(err));
}
async function initiateSpotifyWebApi(user) {
const creds = await UserCredential.findOne({ userId: user.spotifyId });
const apiCaller = setUpApiObj(creds.refreshToken, creds.accessToken);
return apiCaller
}
function setUpApiObj(refreshTok, accessTok) {
const spotifyApi = new SpotifyWebApi({
accessToken: accessTok,
refreshToken: refreshTok
});
return spotifyApi;
}
getUserPlaylist()
returns an error
{ [WebapiError: Unauthorized] name: 'WebapiError', message: 'Unauthorized', statusCode: 401 }
Any idea why I cannot access the api using this library, the way I am trying?
thanks
Couple of things to check. This is just what springs to mind. Correct me if I'm on the wrong track and I'll try to help further.
Check your 'scopes' when you authenticate via Spotify. You need to have the correct scopes to perform different actions on the API. See here: https://developer.spotify.com/documentation/general/guides/authorization-guide/
If you get a 401, use your refresh token (I can see you're storing it) to automatically request, retrieve and overwrite your current AccessToken then perform the request again.
Related
I have to switch from 'Google Sign In' to 'Sign In With Google' (SIWG).
I use passport.js with passport-google-oidc. SIWG works, I ask for the scopes openid, profile and email, and I get it.
But SIWG does not give me any access token as it did before Google Sign In. I need this access token to continue with 'Subscribe With Google' (SWG).
Is there any scope that gives it to me?
As I didn't get from SIWG any access token but at least a 'code' I tried to work with this code.
With google-auth-library and the code, I tried to get an access token. With this library, I use the same id, secret and callback url as with SIWG.
But oAuth2Client.getToken(code) only gives me {"error": "invalid_grant", "error_description": "Bad Request"}
Any idea how to make the request go through ok?
` // 1. Defining strategy
const GoogleStrategy = require('passport-google-oidc');
const Config_Account_Passport = {
// "google" will refer to strategy passport-google-oidc SIWG
Google : new GoogleStrategy({
clientID : '6...MyClientID0c...apps.googleusercontent.com',
clientSecret : 'a...MyClientSecret...',
callbackURL: "https://...UrlOfMyServer.../google/auth",
passReqToCallback : true
}, async function(req, issuer, profile, done) {
// verified function
try {
// get code
const code = req.query.code;
// ...
// get data for login of user. Store data in database if new user.
const email = profile.emails[0].value;
const oauth_id = profile.id;
// Look for user in database. If new store. In Both cases: Having a user object.
//...
// AND now to entitlements (part 4 below)
await checkEntitlements(user, code);
// following never executed because checkEntitlements fails -> goes to catch
// Everything is fine
return done(null, user)
} catch(e) {
return done(true, false); // error and no user
}
})
}
module.exports = Config_Account_Passport;
// 2. Applying strategy
//...
passport.authenticate('google', { scope: ['openid','profile','email','https://www.googleapis.com/auth/subscribewithgoogle.publications.readonly'], accessType: 'offline'});
// 3. Google calls callbackURL -> Successful verified
//...
function(req, res, next) {
// in callback from google
passport.authenticate('google', {}, function (err, user) {
// verified done
if (err) {
// done(true, ...) was called
// user is stays NOT logged id
return res.redirect('/user/login');
}
return res.redirect('/user/loggedin');
})
}
// 4. Check entitlements
// First get access token from code
const {OAuth2Client} = require('google-auth-library');
function getAccessToken(code) {
return new Promise(async (resolve, reject) => {
try {
const oAuth2Client = new OAuth2Client(
clientID : '6...MyClientID0c...apps.googleusercontent.com',
clientSecret : 'a...MyClientSecret...',
callbackURL: "https://...UrlOfMyServer.../google/auth",
);
// Generate the url that will be used for the consent dialog.
await oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: ['https://www.googleapis.com/auth/subscribewithgoogle.publications.readonly'],
});
const {tokens} = await oAuth2Client.getToken(code);
// following never executed because getToken fails -> goes to catch
// "error": "invalid_grant"
//...get access token from tokens
resolve (token);
} catch (e){
return reject(e);
}
})
}
const Payment_SwG = require('./../modules/Payment/SwG');
// Then Get Entitlement with access token
async function checkEntitlements(user, code) {
return new Promise(async (resolve, reject) => {
try {
// first get access token by using code
const accesstoken = await getAccessToken(code);
// following never executed because getAccessToken fails -> goes to catch
// get entitlements by access token
const entitlements = await Payment_SwG.checkEntitlements(accesstoken);
//...
} catch(e) {
return reject(e);
}
})
}`
I tried different scopes. I examined all parameters I got back from SIWG, looking out for an access token.
The Scopes you see in the program code are accepted by SIWG. Execution stops after successful SIWG when I try to get an access token.
I am new to Okta authentication in nodejs, I've been struggling to get an access token. I am using okta-auth-js's signInWithCredentials.
new OktaAuth({
issuer: OKTA_AUTHORISER_ISSUER_URI,
clientId: OKTA_WEB_APP_CLIENT_ID,
redirectUri: OKTA_REDIRECT_URI,
responseType: "token",
});
await this.authClient.signInWithCredentials({
username: email,
password,
});
But this only gets me a sessionToken. How can I get an access token after this? I think I am missing setting an authState here but I am not sure how to do it.
Thanks
You should use something like that to get back the access token :
await this.authClient.signInWithCredentials({
username: email,
password,
});
this.authClient.isAuthenticated().then(value => {
if (!value) {
console.log('not authenticated');
} else {
authClient.tokenManager.get('accessToken').then(value => {
console.log(value.accessToken);
})
}
})
I read this interesting article about how to link accounts using passport:
https://codeburst.io/account-linking-with-passportjs-in-3-minutes-2cb1b09d4a76
passport.use(
new GitHubStrategy(
{
clientID: process.env.GithubClientID,
clientSecret: process.env.GithubClientSecret,
callbackURL: process.env.GithubCallbackURL,
passReqToCallback: true
},
(req, accessToken, refreshToken, profile, cb) => {
const { email, name, id } = profile._json
// Check if user is auth'd
if (req.user) {
// Link account
} else {
// Create new account
}
}
)
)
The interesting thing is that he check if user is logged or not. If user logged in then link user account
if (req.user) {
// Link account
} else {
// Create new account
}
I dont know why req object has user? Maybe the user comes from session but in my NodeJS application, I use token instead of session. My question is how to attach user object to req without using session (I use jwt)?
I do like this but the req.user is undefined
router
.route('/google')
.get(
checkToken(true),
passport.authenticate('google', {scope: 'profile email'}),
)
checkToken is a function that if we have valid token, then it will use userId (found in token) to get the user object and then attach it to req. Something like this
if (validToken) {
const id = fromToken(token)
const user = await User.findById(id)
req.user = user
next()
}
Using: passport-google-oauth2.
I want to use JWT with Google login - for that I need to disable session and somehow pass the user model back to client.
All the examples are using google callback that magically redirect to '/'.
How do I:
1. Disable session while using passport-google-oauth2.
2. res.send() user to client after google authentication.
Feel free to suggest alternatives if I'm not on the right direction.
Manage to overcome this with some insights:
1. disable session in express - just remove the middleware of the session
// app.use(session({secret: config.secret}))
2. when using Google authentication what actually happens is that there is a redirection to google login page and if login is successful it redirect you back with the url have you provided.
This actually mean that once google call your callback you cannot do res.send(token, user) - its simply does not work (anyone can elaborate why?). So you are force to do a redirect to the client by doing res.redirect("/").
But the whole purpose is to pass the token so you can also do res.redirect("/?token=" + token).
app.get( '/auth/google/callback',
passport.authenticate('google', {
//successRedirect: '/',
failureRedirect: '/'
, session: false
}),
function(req, res) {
var token = AuthService.encode(req.user);
res.redirect("/home?token=" + token);
});
But how the client will get the user entity?
So you can also pass the user in the same way but it didn't felt right for me (passing the whole user entity in the parameter list...).
So what I did is make the client use the token and retrieve the user.
function handleNewToken(token) {
if (!token)
return;
localStorageService.set('token', token);
// Fetch activeUser
$http.get("/api/authenticate/" + token)
.then(function (result) {
setActiveUser(result.data);
});
}
Which mean another http request - This make me think that maybe I didnt get right the token concept.
Feel free to enlighten me.
Initialize passport in index.js:
app.use(passport.initialize());
In your passport.js file:
passport.use(
new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL:
'http://localhost:3000/auth/google/redirect',
},
async (accessToken, refreshToken, profile,
callback) => {
// Extract email from profile
const email = profile.emails![0].value;
if (!email) {
throw new BadRequestError('Login failed');
}
// Check if user already exist in database
const existingUser = await User.findOne({ email
});
if (existingUser) {
// Generate JWT
const jwt = jwt.sign(
{ id: existingUser.id },
process.env.JWT_KEY,
{ expiresIn: '10m' }
);
// Update existing user
existingUser.token = jwt
await existingUser.save();
return callback(null, existingUser);
} else {
// Build a new User
const user = User.build({
email,
googleId: profile.id,
token?: undefined
});
// Generate JWT for new user
const jwt = jwt.sign(
{ id: user.id },
process.env.JWT_KEY,
{ expiresIn: '10m' }
);
// Update new user
user.token = jwt;
await auth.save();
return callback(null, auth);
}
}));
Receive this JWT in route via req.user
app.get('/google/redirect', passport.authenticate('google',
{failureRedirect: '/api/relogin', session: false}), (req, res) => {
// Fetch JWT from req.user
const jwt = req.user.token;
req.session = {jwt}
// Successful authentication, redirect home
res.status(200).redirect('/home');
}
I am having an hard time understanding how passportjs authentication method works, in particular with the http-bearer strategy.
So I have two routes, one for registration and one for accessing user's profile, which goes through passportjs middleware. Have a look at the following code:
exports.register = function(req, res){
User.schema.statics.generateUserToken(function(t){
var user = new User({
token: t,
name: 'john doe',
});
user.save(function(e){
res.json(user)
});
});
};
My authentication strategy is as follow:
var mongoose = require('mongoose'),
passport = require('passport'),
BearerStrategy = require('passport-http-bearer').Strategy;
passport.use(new BearerStrategy(
function(token, done) {
User.findOne({ token: token }, function (err, user) {
if (err) { return done(err); }
if (!user) { return done(null, false); }
return done(null, user, { scope: 'read' });
});
}
));
as you can see, when a user requests the registration, my server returns him his object, with its token that should be locally saved.
Then, in a protected route, I added the passportjs middleware, like this:
app.get('/me', passport.authenticate('bearer', { session: false }), routes.me);
where I obviously get an unauthorized error. Why is this' where does passport.authenticate get the token from my client?! This is really confusing for me and is driving me mad. Any help?
Also, is this the right way of doing token authorization? Or do I also need some more details like timestamp, expires, etc.?
could you please refer http-bearer's sample code: https://github.com/jaredhanson/passport-http-bearer/blob/master/examples/bearer/app.js to refactor your codebase. I think here is very clearly definition.