NodeJS Spotify API Implicit Grant flow tokens is invalid - node.js

Using spotify-web-api-node I create AuthorizeURL which works fine, Its going to my callback, then I receive my token, and store it in cookie. But when Im trying to create some request with this token, I get error:
"{"error":{"status":401,"message":"Invalid access token"}}"
Am I missing something? Here is the following code:
// Create and redirect to AuthorizeURL
spotify.get("/spotify/login", async (req, res) => {
let scopes = ['user-read-currently-playing'],
responseType = 'token',
showDialog = true;
let localSpotifyApi = new SpotifyWebApi({
clientId: client_id,
redirectUri: redirect_uri
});
res.redirect(localSpotifyApi.createAuthorizeURL(
scopes,
showDialog,
responseType
));
});
// Handle the callback
spotify.get('/spotify/callback', async (req, res) => {
let access_token = req.query.code;
res.cookie('spotify_access_token', access_token, { maxAge: 3600000});
return res.redirect('/spotify/api/get-currently-playing');
});
// Trying to use stored access token somewhere later
spotify.get('/spotify/api/get-currently-playing', async (req, res) => {
let credentials = {
clientId: client_id,
clientSecret: client_secret,
accessToken: req.cookies.spotify_access_token
};
let localSpotifyApi = new SpotifyWebApi(credentials);
localSpotifyApi.getMyCurrentPlayingTrack()
.then(function(data) {
console.log(data);
return res.json(data);
}, function(err) {
console.log(err.message);
return res.json({'err' : err});
});
});

Related

Authorize Gmail API using Oauth2 failing Error: No access, refresh token, API key or refresh handler callback is set

I've tried the soultion from this answer and it isn't working for me.
I've tried revoking access to my app and reauthorizing and it's not working either. Here is my auth code:
export function handleAuth() {
const oauth2Client = getOAuth2Client();
const url = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: ['https://mail.google.com/'],
});
}
I take the URL returned from this and use it to auth my gmail account. Then I have the auth callback:
app.get('/oauth2callback', async (req, res) => {
const query = req.query;
const code = query.code as string;
const { tokens } = await oauth2Client.getToken(code);
oauth2Client.setCredentials({
refresh_token: tokens.refresh_token,
});
});
And I have a listener waiting for new tokens:
oauth2Client.on('tokens', async (tokens) => {
if (tokens.refresh_token) {
oauth2Client.setCredentials({
refresh_token: tokens.refresh_token,
});
if (tokens.access_token && tokens.refresh_token) {
const tokenRepo = getCustomRepository(GcpTokenRepository);
await tokenRepo.create({
log in my db...
});
}
}
});
Then when I try and run the watch method so I can listen to emails:
async watch() {
const gmail = await this.authGmail(); // method that returns type Promise<gmail_v1.Gmail>
const res = await gmail.users.watch({
userId: 'me',
requestBody: {
labelIds: ['INBOX'],
topicName: `topic name`,
},
});
console.log('👀 Watch re-initialized!', res);
}
And this watch method throws the error: Error: No access, refresh token, API key or refresh handler callback is set
When I console log my auth variable returned from google.auth.OAuth2() I also notice the credentials field is an empty object...
Am I missing anything here?
Where is your callback() ?
app.get('/oauth2callback', async (req, res) => {
const query = req.query;
const code = query.code as string;
const { tokens } = await oauth2Client.getToken(code);
oauth2Client.setCredentials({
refresh_token: tokens.refresh_token,
});
});

onedrive does not return refresh token even with 'offline_access' scope

I'm trying to get the tokens of user's to integrate one drive in an APP that's I'm building
I first get the auth URL, here's the endpoint implementation
const pca = new msal.ConfidentialClientApplication(config);
exports.getAuthUrl = (req, res) => {
const authCodeUrlParameters = {
scopes: ['user.read', 'files.readwrite', 'offline_access'],
redirectUri: 'https://localhost:8080/onedrive',
};
// get url to sign user in and consent to scopes needed for application
pca
.getAuthCodeUrl(authCodeUrlParameters)
.then((response) => {
res.status(200).send(response);
})
.catch((error) => console.log(JSON.stringify(error)));
};
Then using the code that I got back after the client auth is successful is passed as a parameter in a second endpoint to obtain the tokens
exports.getToken = (req, res) => {
const tokenRequest = {
code: req.query.code,
scopes: ['user.read', 'files.readwrite', 'offline_access'],
redirectUri: 'https://localhost:8080/onedrive',
};
pca
.acquireTokenByCode(tokenRequest)
.then((response) => {
console.log('\nResponse: \n:', response);
res.status(200).send(response);
})
.catch((error) => {
console.log(error);
res.status(500).send(error);
});
};
So as mentioned in the official doc if you add the offline_access scope you get back a refresh token
does anyone have any experience with this ? I used these two libraries that part of the code is already provided by Microsoft const msal = require('#azure/msal-node');
Here is How to get the Refresh and Access token..
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
const express = require("express");
const msal = require('#azure/msal-node');
const SERVER_PORT = process.env.PORT || 3000;
const REDIRECT_URI = "http://localhost:3000/redirect";
// Before running the sample, you will need to replace the values in the config,
// including the clientSecret
const config = {
auth: {
clientId: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
authority: "https://login.microsoftonline.com/84fb56d3-e15d-4ae1-acd7-cbf83c4c0af3",
clientSecret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
},
    system: {
        loggerOptions: {
            loggerCallback(loglevel, message, containsPii) {
                console.log(message);
            },
         piiLoggingEnabled: false,
         logLevel: msal.LogLevel.Verbose,
        }
    }
};
// Create msal application object
const pca = new msal.ConfidentialClientApplication(config);
// Create Express App and Routes
const app = express();
app.get('/', (req, res) => {
const authCodeUrlParameters = {
scopes: ["user.read","offline_access"],
redirectUri: REDIRECT_URI,
prompt:'consent'
};
// get url to sign user in and consent to scopes needed for application
pca.getAuthCodeUrl(authCodeUrlParameters).then((response) => {
res.redirect(response);
}).catch((error) => console.log(JSON.stringify(error)));
});
app.get('/redirect', (req, res) => {
const tokenRequest = {
code: req.query.code,
scopes: ["user.read","offline_access"],
redirectUri: REDIRECT_URI,
accessType: 'offline',
};
pca.acquireTokenByCode(tokenRequest).then((response) => {
const accessToken = response.accessToken;
const refreshToken = () => {
const tokenCache = pca.getTokenCache().serialize();
const refreshTokenObject = (JSON.parse(tokenCache)).RefreshToken
const refreshToken = refreshTokenObject[Object.keys(refreshTokenObject)[0]].secret;
return refreshToken;
}
const tokens = {
accessToken,
refreshToken:refreshToken()
}
console.log(tokens)
res.sendStatus(200);
}).catch((error) => {
console.log(error);
res.status(500).send(error);
});
});
app.listen(SERVER_PORT, () => console.log(`Msal Node Auth Code Sample app listening on port ${SERVER_PORT}!`))
Well, after a few days of research I found out that msal-node does not expose the refresh token to the end-user by design. It is stored and used internally under the hood when you need a new access token. You should call acquireTokenSilent each time you need an access token and msal-node will manage the tokens by either returning a cached token to you or using the refresh token to acquire a new access token.
So what I did is actually make the call directly to the endpoint provided by Microsoft
You should first get the code using the URL provided in this doc [https://learn.microsoft.com/en-us/onedrive/developer/rest-api/getting-started/msa-oauth?view=odsp-graph-online][1]
the take the code and pass it in the next enpoint which is the following
exports.getToken = async (req, res) => {
request.post(
'https://login.live.com/oauth20_token.srf',
{
form: {
code: req.body.code,
client_id: 'CLIENT-ID',
redirect_uri: 'REDIRECT-URI',
client_secret: 'CLIENT-SECRET',
grant_type: 'authorization_code',
},
},
async function (err, httpResponse, body) {
if (err) {
res.status(500).send({ Message: err.message });
} else {
let response = JSON.parse(body);
res.status(200).send({ Body: JSON.parse(body) });
}
}
);
};
and that's how I made it work, using msal there was no way

How to use LinkedIn api with Node js

All I need is to check in the backend side if the user access token is valid and get user email by its access token. It's hard to understand how to use this npm library for these purposes, so please help me.
In the documentation I've found the API address for it, but how to fetch the with Client ID and Client Secret of my app which I created on https://www.linkedin.com/developers/apps/new..
Hopefully, my question makes sense, thanks in advance <3
var passport = require('passport');
var LinkedInStrategy = require('passport-linkedin-oauth2').Strategy;
// linkedin app settings
var LINKEDIN_CLIENT_ID = "CLIENT_ID_HERE";
var LINKEDIN_CLIENT_SECRET = "CLIENT_SECRET_HERE";
var Linkedin = require('node-linkedin')(LINKEDIN_CLIENT_ID, LINKEDIN_CLIENT_SECRET);
passport.serializeUser(function (user, done) {
done(null, user);
});
passport.deserializeUser(function (obj, done) {
done(null, obj);
});
passport.use(new LinkedInStrategy({
clientID: LINKEDIN_CLIENT_ID,
clientSecret: LINKEDIN_CLIENT_SECRET,
callbackURL: "http://127.0.0.1:3000/auth/linkedin/callback",
scope: ['r_emailaddress', 'r_basicprofile', 'rw_company_admin'],
passReqToCallback: true
},
function (req, accessToken, refreshToken, profile, done) {
req.session.accessToken = accessToken;
process.nextTick(function () {
return done(null, profile);
});
}));
// for auth
app.get('/auth/linkedin',
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.
});
// for callback
app.get('/auth/linkedin/callback', passport.authenticate('linkedin', { failureRedirect: '/' }),
function (req, res) {
res.redirect('/');
});
This is a Code Snippet of how i have used it, i guess it will help you on fetching with CLIENT_ID and CLIENT_SECRET.
Note : npm passport and passport-linkedin-oauth2 should already be installed
const accessToken = req.params.accessToken;
const options = {
host: 'api.linkedin.com',
path: '/v2/me',
method: 'GET',
headers: {
'Authorization': `Bearer ${accessToken}`,
'cache-control': 'no-cache',
'X-Restli-Protocol-Version': '2.0.0'
}
};
const profileRequest = https.request(options, function(result) {
let data = '';
result.on('data', (chunk) => {
data += chunk;
console.log(data)
});
result.on('end', () => {
const profileData = JSON.parse(data);
return res.status(200).json({
'status': true,
'message': "Success",
'result': profileData
});
});
});
profileRequest.end();
The existing NodeJS LinkedIn official API is not so straightforward and hardly maintained.
If relevant, you can use this NodeJS LinkedInAPI.
It allows you to easily login via username & password or with a Cookie and make various requests:
import { Client } from 'linkedin-private-api';
const username = process.env.USERNAME as string;
const password = process.env.PASSWORD as string;
(async () => {
// Login
const client = new Client();
await client.login.userPass({ username, password });
// search for companies
const companiesScroller = await client.search.searchCompanies({ keywords: 'Microsoft' });
const [{ company: microsoft }] = await companiesScroller.scrollNext();
// Search for profiles and send an invitation
const peopleScroller = await client.search.searchPeople({
keywords: 'Bill Gates',
filters: {
pastCompany: microsoft.companyId
}
});
const [{ profile: billGates }] = await peopleScroller.scrollNext();
await client.invitation.sendInvitation({
profileId: billGates.profileId,
trackingId: billGates.trackingId,
});
})

Integrating json web token in oauth20

I am trying to create an api where user can sign up with an email or can sign in with google, I use json web token for authentication and oauth20, the problem is, can, I pass a jwt with oauth?
I have tried passing it and, I get a token if, I console log, but how do, I pass it to the user, like can i some way attach it to the req.user object in the cb by oauth or something like that?
I am doing this in the google strategy:
async (accessToken, refreshToken, params, profile, cb) => {
const userCheck = await User.findOne({ googleId: profile.id });
if (userCheck) {
const payload = {
user: {
id: userCheck.id
}
};
jwtToken.sign(
payload,
config.get("jwtSecret"),
{ expiresIn: 360000 },
(err, token) => {
if (err) {
throw err;
}
// console.log(token);
return res.json({ token });
},
cb(null, userCheck)
);
My routes are protected like this:
router.get("/", auth, async (req, res)=>{
...some code
}
where auth is a middle ware function
This is the Auth middleware function:
module.exports = function(req, res, next) {
const token = req.header("x-auth-token");
// If no token found
if (!token)
{
return res.status(401).json({ msg: "User not authorized" });
}
// Set token to user
try {
const decoded = jwtToken.verify(token, config.get("jwtSecret"));
req.user = decoded.user;
}
catch (err)
{
res.
status(401)
.json({ msg: "User not authenticated, please login or sign up" });
}
next();
};
I found the solution, you need to pass sign the token in the passport.serializeUser and then send the it with a redirection in response of the redirect url.
The serialize user function:
passport.serializeUser(async (user, cb) => {
const payload = {
user: {
id: user.id
}
};
token = jwtToken.sign(payload, config.get("jwtSecret"), {
expiresIn: 360000
});
console.log("serialize");
cb(null, user.id);
});
The redirection route:
router.get(
"/google/redirect",
passport.authenticate("google", { sessionStorage: false }),
(req, res) => {
res.redirect("/" + token);
}
);

How to bind or pass req parameter to Passport.js JWT Strategy?

I want to store information in the database when user is authenticated. The information is coming form the client in the request. The following code throws error, saying req is not defined.
Controller:
exports.verifySession = async function(req, res, next) {
let responses = [];
passport.authenticate('jwt', async (error, result) => {
if (error) {
email.sendError(res, error);
} else if (result === false) {
responses.push(new CustomResponse(1).get());
return res.status(422).json({ data: { errors: responses } });
}
if (result.SessionToken) {
return res.status(200).json('valid');
} else {
return res.status(401).json();
}
})(req, res, next);
};
And passport.js:
passport.use(
new JWTstrategy(
{
// We expect the user to send the token as a query paramater with the name 'token'
jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(),
// Secret we used to sign our JWT
secretOrKey: config.jwtkey
},
async (token, done) => {
console.log(req.body);
try {
const user = new User();
user.UserID = token.user.UserID;
user.SessionToken = token.user.SessionToken;
user.SessionDate = token.user.SessionDate;
user.ProviderID = token.user.ProviderID;
// Verify session token
await user.verifySessionToken(user, async (error, result) => {
if (error) {
return done(error);
} else if (result.returnValue === 0) {
return done(null, token.user);
} else if (result.returnValue !== 0) {
return done(null, result);
}
});
} catch (error) {
done(error);
}
}
)
);
You can use passReqToCallback feature of passport to pass your request body to passport.
From passport.js official docs :
The JWT authentication strategy is constructed as follows:
new JwtStrategy(options, verify)
options is an object literal
containing options to control how the token is extracted from the
request or verified.
...
...
passReqToCallback: If true the request will be passed to the verify
callback. i.e. verify(request, jwt_payload, done_callback).
You can try this:
passport.use(new JWTstrategy({
// We expect the user to send the token as a query paramater with the name 'token'
jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(),
// Secret we used to sign our JWT
secretOrKey: config.jwtkey,
//this will help you to pass request body to passport
passReqToCallback: true
}, async (req, token,done) => {
//req becomes the first parameter
// now you can access req.body here
})
Note: req becomes the first parameter of callback function instead of token, when you use passReqToCallback

Resources