I am writing a node.js app to authenticate with LinkedIn and it isn't working. The problem is that I am redirecting to (what appears to be) the correct URL, but instead of being forwarded to a page that queries the user to authorize their credentials, I get a "page not found" message.
I have created a LinkedIn "App". Below are my "authorized redirect URLs":
HTML
<div id="root">
<button id="auth-button"> Login </button>
</div>
Client JS
function onSignInButtonClick() {
// Open the Auth flow in a popup.
window.open('/redirect', 'firebaseAuth', 'height=315,width=400');
};
var button = document.getElementById("auth-button");
button.addEventListener("click",function(){
onSignInButtonClick();
});
Server code
const credentials = {
client: {
id: "LINKEDIN_CLIENT_ID-1-2-3-4",
secret: "LINKEDIN_CLIENT_SECRET-1-2-3-4",
},
auth: {
tokenHost: 'https://www.linkedin.com/oauth/v2/authorization'
}
};
const oauth2 = require('simple-oauth2').create(credentials);
var express = require("express");
var app = express();
app.use(express.static("public"));
app.get('/', (req, res) => {
res.sendFile('landing.html',{
root:'public'
})
});
app.get('/redirect', (req, res) => {
const redirectUri = oauth2.authorizationCode.authorizeURL({
response_type:"code",
redirect_uri: "http://www.localhost:3000/callback",
state: "some-cryptic-stuff-98471871987981247"
});
res.redirect(redirectUri);
});
app.get('/callback',(req, res) => {
console.log("linkedin-callback route invoked");
res.send("linked in callback working")
});
app.listen(3000, function(err) {
console.log('Server works');
});
When the user clicks the button they are redirected to a URL that is identical in structure to the one that is given as a "sample call" (below) in the LinkedIn developer reference.
https://developer.linkedin.com/docs/oauth2#
However instead of seeing the prompt in the image above, my code gives them this:
The redirect_uri you have registered in LinkedIn (http://localhost:3000/callback) is different to what you are actually sending (http://www.localhost:3000/callback). This might be the issue as it causes an invalid redirect_uri error.
Related
I implement a payment service which depend on one of my express route as a callback route, so whenever a user want to make a payment, they will be redirected to this payment service link which entirely different my backend/frontend domain. After a successful payment, user will then be redirected to my express GET route (callback route), in this route is where I give users their asset and then redirect them to the frontend.
EXPECTATION
My expectation is, whenever a user make a purchase, I want a real time update on the frontend for others to see some details about the purchase without refreshing their browser.
WHAT I'VE TRIED
I had think socket.io would solve this, like adding a socket connection in the route to then push the data to the frontend. But after making lot of research, no solution seems to work for me.
HERE IS A SIMPLE CODE OF WHAT I'VE TRIED
=============================== server.js ========================
const express = require("express")
const app = express()
const http = require("http")
const cors = require("cors")
const session = require("express-session")
const runSocket = require("./runSocket")
const { Server } = require("socket.io")
app.use(cors())
app.use(express.json())
const server = http.createServer(app)
server.listen(3004, () => {
console.log("SERVER IS RUNNING")
})
const io = new Server(server, {
cors: {
origin: "http://localhost:3000",
methods: ["GET", "POST"],
},
})
const postRoute = require("./routes/postData")(io)
app.use("/post-data", postRoute)
==================================== postData Route ======================================
module.exports = function (io) {
router.post("/", async (req, res) => {
const data = req?.body?.data.message
const room = req?.body?.data?.room
io.on("connection", (socket) => {
console.log("Socket Running...")
socket.to(room).emit("the_message", data)
})
console.log("Under socket...")
return res.status(200).json({ data: req.body.data })
})
return router
}
This log: in postData route is not printing console.log("Socket Running...")
EXPECTATION
My expectation is, whenever a user make a purchase, I would like to make a real time update on the frontend for others to see some details about the purchase.
UPDATE: The Payment Gateway config looks somthing like this:
const { body } = await got.post("https://payment-provider-link", {
headers: { Authorization: "Bearer token for payment" },
json: {
email: "email#gmail.com",
amount: amount * 100,
initiate_type: "inline",
callback_url: `${BackendBaseUrl}/payment-callback`, // <<<============
},
})
Okay so you don't need the io.on("connection") in ur route. Remove that piece of code and simply change it to io.to(room).emit("the_message", data). Also make sure to have the other sockets joined the room ur trying to emit to otherwise they won't receive the data.
I am trying to add a Webinterface to my NodeJS Discord-Bot and decided to use Express for this. Now I have succesfully managed to set things up an managed to connect the Web-Socket with the Bot and was able to send my first Message through the Websocket into a Discord-Channel. Now I want to create different Sites in the Webinterface with each different uses. These are supposed to be linked through a neat Navbar at the Side of the Page. Once clicked, the user should be redirected to the new site without losing the Token he is authenticated with. For the Purpose of Testing the Token is '123456'. The User is supposed to be redirected by clicking on this Button
(layout.hbs)
<form action = "redirectInfo">
<div class = "sidenav-element"><button type="submit">General Informations</button></div><br>
</form>
By clicking, the action "redirectInfo" is being triggered, which looks like this:
(webs.js)
this.app.get('/redirectInfo', (req, res) => {
res.redirect(301, 'infoSite')
})
I have tried using it both with the 301 and without which both leaves out the token .
This then redirects me to the 'infoSite' which is displayed using the following:
(webs.js)
this.app.get('/infoSite', (req, res) => {
var _token = req.query.token
if(!this.checkToken(_token)) {
res.render('error', { title: 'Error', errtype: 'The given Token is invalid.'})
return
}
res.render('infoSite', {
title: 'Webinterface',
token: _token
})
})
However this results in the infoSite telling me my Token is invalid, while the Default Page perfectly works with the Same Methods. This is the Code from the Default Page:
(webs.js)
this.app.get('/', (req, res) => {
var _token = req.query.token
if(!this.checkToken(_token)) {
res.render('error', { title: 'Error', errtype: 'The given Token is invalid.'})
return
}
var chans = []
this.client.guilds.cache.first().channels.cache
.filter(c => c.type == 'text')
.forEach(c => {
chans.push({ id: c.id, name: c.name})
})
res.render('index', {
title: 'Webinterface',
token: _token,
chans
})
})
In this Case "chans" can be ignored, as it's used to send Messages to Specific Channels in my Discord Server.
In both Cases _token is supposed to be Defined by The constructor and a function named checkToken (Code Attached)
constructor(token, port, client) {
this.token = token
this.token = token
this.client = client
this.app = express()
this.app.engine('hbs', hbs({
extname: 'hbs',
defaultLayout: 'layout',
layoutsDir: __dirname + '/layout'
}))
this.app.set('views', path.join(__dirname, 'views'))
this.app.set('view engine', 'hbs')
this.app.use(express.static(path.join(__dirname, 'public')))
this.app.use(bodyParser.urlencoded({ extended: false}))
this.app.use(bodyParser.json())
this.registerRoots()
this.server = this.app.listen(port, () => {
console.log(`Webinterface started on Port: ${this.server.address().port}`)
})
}
checkToken(_token) {
return (_token == this.token)
}
My Problem is, that whenever I leave the Default Page, the Token isn't being redirected with me and the Website tells me I've got an Invalid Token.
My Question is therefore, how can I redirect a client between multiple Sites, without him loosing the Token he is authenticated with. Thank you in Advance.
My application is a Node.js API with a client inside the same application.
I'm trying to implement a simple auth login that uses a JWT token generated by a Node.js API.
My logic is as follows:
Client: User submits login information to /auth/login route.
$.ajax({
url: "/auth/login",
type: "POST",
data: formData,
dataType: "json",
success: function(data, textStatus, jqXHR) {
if (typeof data.redirect == "string") {
window.location = data.redirect;
}
},
error: function(data) {
if (typeof fail === "function") fail(data);
}
});
API: Verify user and on success generates JWT and sends back to the client.
router.post("/login", async (req, res) => {
var login = { UID: req.body.UID, password: req.body.password };
AU.manualLogin(login)
.then(result => {
res.header("x-auth-token", result.token).json({
status: 200,
message: "success",
data: result.data,
redirect: "/dashboard"
});
})
.catch(err => next({ status: 400, message: err.message }));
});
Client: Saves JWT to the header and checks for redirect - In this case, I use window.location to direct to /dashboard after successful login. (this part I'm not sure about)
API: Middleware checks valid JWT on protected routes.
module.exports = function auth(req, res, next) {
const token = req.headers["x-auth-token"];
if (!token)
return res.status(401).send("Access denied. No token provided.");
try {
const decoded = jwt.verify(token, "jwtPrivateKey");
req.user = decoded;
next(); //pass control to next middleware
} catch (ex) {
res.status(400).send("Invalid token.");
}
};
The Problem:
The token is definitely being sent from API -> Client. But I have no idea how to handle the token from the client-side. I think the issue might be to do with the window.location redirect as at this point it does not seem to be sending the x-auth-token to the API.
What I have tried
I have tested the solution with Postman from end-to-end and it works fine. That probably proves that it isn't the API side that has the issue.
I've also tried these sources:
Pass request headers in a jQuery AJAX GET call
Adding custom header in HTTP before redirect
How to add header to request in Jquery Ajax?
jwt on node - how does the client pass the token back to the server
You need kind of a storage to keep the token. Otherwise the user has always to login again after he closes the browser/tab. So it's quite common to keep the token in local or session storage.
Approach 1: Use a single page application (SPA) framework like angular, vue.js, react etc. to protect your routes client-side
Approach 2: You can request only html and css (view) from your backend and then store the token after a login procedure. With a valid token, fetch the (protected) data with ajax requests. Redirect to the login page if a ajax request returns the status code 401 (unauthorized) or a user wants to access the protected route without having a token stored. This is perhaps the most suitable for you.
Approach 3: Use Node.js with a backend framework like express and store auth information in a server side session
index.js
const express = require('express');
const session = require('express-session');
const app = express();
app.use(require("cookie-parser")());
app.use(session({ secret: 'aslwezoweasdfasdlkfalksdfhweelaerfcv', resave: false, saveUninitialized: true}));
routes/protectedRoutes.js
const express = require('express');
const router = express.Router();
router.all("/*", util.handleAuthenticate); // check auth on every request
// other routes
indexController.js (login functionality)
module.exports.login = function(req, res) {
if(!req.session.name) {
// check username/password --> db lookup
// if valid:
req.session.name = ...
// redirect to home or backref
// else: redirect to login
}
}
util/security.js
function isLoggedIn(req) {
return !!req.session.name;
}
function handleAuthenticate(req, res, next) {
if(isLoggedIn(req))
{
next();
}
else
{
// redirect to login page
}
}
I write an example about google api using. Google NodeJS Client library. I have followed the instruction set access_type : 'offline', however the object return doesn't contains refresh_token.
My Code:
var http = require('http');
var express = require('express');
var Session = require('express-session');
var google = require('googleapis');
var plus = google.plus('v1');
var OAuth2 = google.auth.OAuth2;
const ClientId = "251872680446-rvkcvm5mjn1ps32iabf4i2611hcg086e.apps.googleusercontent.com";
const ClientSecret = "F1qG9fFS-QwcrEfZbT8VmUnx";
const RedirectionUrl = "http://localhost:8081/oauthCallback";
var app = express();
app.use(Session({
secret: 'raysources-secret-19890913007',
resave: true,
saveUninitialized: true
}));
function getOAuthClient () {
return new OAuth2(ClientId , ClientSecret, RedirectionUrl);
}
function getAuthUrl () {
var oauth2Client = getOAuthClient();
// generate a url that asks permissions for Google+ and Google Calendar scopes
var scopes = [
'https://www.googleapis.com/auth/plus.me'
];
var url = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: scopes // If you only need one scope you can pass it as string
});
return url;
}
app.use("/oauthCallback", function (req, res) {
var oauth2Client = getOAuthClient();
var session = req.session;
var code = req.query.code;
oauth2Client.getToken(code, function(err, tokens) {
console.log("tokens : ", tokens);
// Now tokens contains an access_token and an optional refresh_token. Save them.
if(!err) {
oauth2Client.setCredentials(tokens);
session["tokens"]=tokens;
res.send(`
<html>
<body>
<h3>Login successful!!</h3>
Go to details page
<body>
<html>
`);
}
else{
res.send(`
<html>
<body>
<h3>Login failed!!</h3>
</body>
</html>
`);
}
});
});
app.use("/details", function (req, res) {
var oauth2Client = getOAuthClient();
oauth2Client.setCredentials(req.session["tokens"]);
var p = new Promise(function (resolve, reject) {
plus.people.get({ userId: 'me', auth: oauth2Client }, function(err, response) {
console.log("response : " , response);
resolve(response || err);
});
}).then(function (data) {
res.send(`<html><body>
<img src=${data.image.url} />
<h3>Hello ${data.displayName}</h3>
</body>
</html>
`);
})
});
app.use("/", function (req, res) {
var url = getAuthUrl();
res.send(`
<html>
<body>
<h1>Authentication using google oAuth</h1>
<a href=${url}>Login</a>
</body>
</html>
`)
});
var port = 8081;
var server = http.createServer(app);
server.listen(port);
server.on('listening', function () {
console.log(`listening to ${port}`);
});
The refresh token is only sent once the first time user login to your application after approving the scopes you have specified.
Edit 08/2018 : Using approval_prompt:'force' no longer works, you need to use prompt:'consent' (check #Alexander's answer)
If you want to get the refresh token each time user login (even if user has already login before and approved the scopes), you have to specify prompt:'consent' in Oauth2Client configuration :
var url = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: scopes,
prompt : 'consent'
});
Note that this will require user to accept the specified scope each time he/she will click on your link to authenticate :
You can also disable manually the application in your account permission settings, this will revoke the application and the user will have to accept the scopes again that will trigger the refresh_token to be sent the next time you authenticate :
FYI, if you need to use access_token offline, you have to store the refresh_token server side, and refresh the access_token with the stored refresh_token when you receive status 401 from Google API. So, if you store refresh_token as you should, there is actually no need to use prompt:'consent' and force user to approve the scopes each time he/she connects to your application.
According the documentation the refresh_token is only returned on the first authorization.
You can remove the permissions manually on: https://myaccount.google.com/permissions
Also you can force the user to see the consent screen again by passing &prompt=consent in the authorization URL, just add this parameter:
var url = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: scopes,
prompt: 'consent'
});
It worked just fine for me :)
In some cases (Web clients mostly) the refresh token is only sent the first time the user is authenticated.
If you go to apps connected to your account remove the app in question. Then try and authenticated again. Check and there should now be a refresh token.
I am building a slideshow that pulls pictures with a certain tag on instagram. The Instagram API requires me to make a call to their auth URL to receive an access token. Using node js and express I built out the backend like so:
var express = require('express');
var app = express();
app.use(express.static('public'));
app.listen(4000,function(){
console.log("Listening to app on localhost 4000");
})
app.get('/',function(req,res){
1. make call to Instagram authorization URL:
https://api.instagram.com/oauth/authorize/?client_id=CLIENT-ID&redirect_uri=http://localhost:4000&response_type=code
2. URL will be redirected with access code parameter
3. Use access code to make POST request to receive access token to be able to make GET requests.
})
My question is how do I make a request to visit that url within NodeJS/Express? Is it just a normal http.request()?
I don't want to user to go through the redirect process so that's why I want to put it in Node. I'm following these instructions https://www.instagram.com/developer/authentication/
You can do a redirect or use a npm library like instagram-node-lib
var express = require('express');
var request = require('request');
var app = express();
app.use(express.static('public'));
app.listen(4000, function () {
console.log("Listening to app on localhost 4000");
})
app.get('/', function (req, res) {
res.redirect('https://api.instagram.com/oauth/authorize/?client_id=CLIENT-ID&redirect_uri=http://localhost:4000/mycallback&response_type=code')
})
app.get('/mycallback', function (req, res) {
//handle token retrieval here
//do a get request as per the instagram documentation using the code sent back
var code = req.query.code
var url = 'https://api.instagram.com/oauth/access_token'
var options = {
method: 'post',
body: {
client_secret: 'CLIENT_SECRET',
grant_type: 'authorization_code',
redirect_uri: 'AUTHORIZATION_REDIRECT_URI',
code: code
},
json: true,
url: url
}
request(options, function (err, res, body) {
//body should look something like this
// {
// "access_token": "fb2e77d.47a0479900504cb3ab4a1f626d174d2d",
// "user": {
// "id": "1574083",
// "username": "snoopdogg",
// "full_name": "Snoop Dogg",
// "profile_picture": "..."
// }
// }
})
})
You will always require the redirect as that is how oAuth works. The user enters a password on the Instagram site. A code is sent back to your server via a callback url (redirect). You then use that code to retrieve the user token. You can then use the authorization token for subsequent calls.