I am trying to authenticate my Azure Web App with passport-azure-ad-oauth2 in Node JS using express
I have tried to follow along with the documentation found here: https://github.com/auth0/passport-azure-ad-oauth2. I believe I have gotten the client ID, secret and callback URI correct...
When I go to localhost:3000, it redirects successfully to Office365 sign in. When I choose the pre-selected account, it just keeps looping back to the "select account"
When trying to sign in using an incognito window in Chrome it gives me the error: The reply URL specified in the request does not match the reply URLs configured for the application: '***appID'.
My code is obviously wrong and am hoping someone is able to possibly help me get it set up correctly.
Thanks in advance!!
My code is here
const express = require("express");
const bodyParser = require("body-parser")
const session = require('express-session');
const passport = require("passport");
const ejs = require("ejs");
const jwt = require("jwt-simple")
const AzureAdOAuth2Strategy = require('passport-azure-ad-oauth2').Strategy;
const app = express();
app.use(express.static("public"));
app.set('view engine', 'ejs');
app.use(bodyParser.urlencoded({
extended: true
}));
app.use(passport.initialize());
app.use(bodyParser.urlencoded({ extended: false }));
passport.use(new AzureAdOAuth2Strategy({
clientID: 'azure client ID',
clientSecret: 'secret',
callbackURL: 'http://localhost:3000/auth/aad/callback',
// resource: '00000002-0000-0000-c000-000000000000',
// tenant: 'contoso.onmicrosoft.com'
},
function (accessToken, refresh_token, params, profile, done) {
var waadProfile = profile || jwt.decode(params.id_token, '', true);
console.log(waadProfile);
User.findOrCreate({ id: waadProfile.upn }, function (err, user) {
done(err, user);
});
}));
app.get("/",passport.authenticate('azure_ad_oauth2'));
app.get('/auth/aad/callback',
passport.authenticate('azure_ad_oauth2', { failureRedirect: '/login' }),
function (req, res) {
console.log(req);
console.log(res);
res.render('index');
});
app.listen(process.env.PORT || 3000, function() {
console.log("Server started on Port 3000");
});
The callback URL in your code is not the same as the one set on Azure.
That' why it says:
The reply URL specified in the request does not match the reply URLs configured for the application: '***appID'.
Set the correct URL on Azure to fix this.
For the infinite redirection issue, clear the cache and cookie in your browser and it should work.
However, if you don't correct the callback URL, it's going to happen again.
Related
I want to have an online map which publicly loads and shows all my activities from my Strava account.
I have found a web app on GitHub which does what I want but the user has to log in with the Strava log in screen before he than can see his own activities: https://github.com/nsynes/ActivityMap
It seems it uses Passport Strava to authenticate with Strava: https://www.passportjs.org/packages/passport-strava-oauth2/
Is it possible to adjust the script so it always logs in automatically my account and than shows my activities publicly to everyone who visits the map?
The full script is here: https://github.com/nsynes/ActivityMap/blob/master/activity-map-app.js
My STRAVA_CLIENT_ID and STRAVA_CLIENT_SECRET are from my Strava account and saved in a .env file.
const express = require('express')
const passport = require('passport')
const util = require('util')
const StravaStrategy = require('passport-strava-oauth2').Strategy
const dotenv = require('dotenv');
const path = require('path');
const cookieParser = require('cookie-parser')
const bodyParser = require('body-parser');
const session = require('express-session');
var strava = require('strava-v3');
const decode = require('geojson-polyline').decode
const geodist = require('geodist');
dotenv.config();
const port = process.env.PORT || 3000
const app = express();
// configure Express
//app.use(express.logger());
app.use(cookieParser());
app.use(bodyParser.urlencoded({ extended: false }));
//app.use(express.methodOverride());
app.use(session({
secret: 'monkey tennis',
resave: true,
saveUninitialized: true,
maxAge: 1800 * 1000
}));
app.use(passport.initialize());
app.use(passport.session());
app.use('/css', express.static(path.join(__dirname, 'css')));
app.use('/fontawesome', express.static(path.join(__dirname, 'fontawesome')));
app.use('/js', express.static(path.join(__dirname, 'js')));
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
// Passport session setup.
passport.serializeUser(function(user, done) { done(null, user) });
passport.deserializeUser(function(obj, done) {done(null, obj) });
passport.use(new StravaStrategy({
clientID: process.env.STRAVA_CLIENT_ID,
clientSecret: process.env.STRAVA_CLIENT_SECRET,
callbackURL: "/auth/strava/callback"
},
function(accessToken, refreshToken, profile, done) {
// asynchronous verification, for effect...
process.nextTick(function () {
// To keep the example simple, the user's Strava profile is returned to
// represent the logged-in user. In a typical application, you would want
// to associate the Strava account with a user record in your database,
// and return that user instead.
return done(null, profile);
});
}
));
app.get('/', ensureAuthenticated, function(req, res){
pagePath = path.join(__dirname, '/index.html');
res.sendFile(pagePath);
});
app.get('/userPhoto', ensureAuthenticated, function(req, res){
if ( req.user ) {
res.json({ 'photo': req.user.photos[req.user.photos.length-1].value });
} else {
res.sendStatus(404);
}
});
// Use passport.authenticate() as route middleware to authenticate the
// request. Redirect user to strava, then strava will redirect user back to
// this application at /auth/strava/callback
app.get('/auth/strava',
passport.authenticate('strava', { scope: ['activity:read'], approval_prompt: ['force'] }),
function(req, res){
// The request will be redirected to Strava for authentication, so this
// function will not be called.
});
// GET /auth/strava/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/strava/callback',
passport.authenticate('strava', { failureRedirect: '/login' }),
function(req, res) {
res.redirect('/');
});
app.get('/logout', function(req, res){
req.logout();
res.cookie("connect.sid", "", { expires: new Date() });
res.render('login', { user: req.user });
});
app.get('/login', function(req, res){
res.render('login', { user: req.user });
});
This question already has answers here:
Access-Control-Allow-Origin denied spotify api [duplicate]
(1 answer)
Allowing frontend JavaScript POST requests to https://accounts.spotify.com/api/token endpoint
(1 answer)
Spotify Auth + frontend JavaScript + Client Credential Flow
(1 answer)
Getting Spotify API access token from frontend JavaScript code
(1 answer)
Authentication Request to Spotify Web API Rejected
(1 answer)
Closed 1 year ago.
Any other requests through my react app seems to be working fine, until I try to res.redirect to somewhere else.
I somewhat understand why it does not work, but I cant figure out how to properly implement something similar to that.
Code for references:
React app.js:
function loginToSpotify() {
axios.get('http://localhost:4000/login')
.then(res => {
console.log(res)
})
.catch(err => {
console.log("error!")
})
}
express server.js:
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var cors = require('cors');
var app = express();
app.use(cors())
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', indexRouter);
app.use('/users', usersRouter);
module.exports = app;
express index.js login call:
const { default: axios } = require('axios');
var express = require('express');
var router = express.Router();
var querystring = require('querystring')
var SpotifyWebApi = require('spotify-web-api-node');
/* GET home page. */
let spotifyApi = new SpotifyWebApi({
clientId:'c2b60e83cbdb4b5ba923140f0c32ac8f',
clientSecret:'d45b554801ab4333b89a36bdbf04fad7'
})
let clientId = 'c2b60e83cbdb4b5ba923140f0c32ac8f'
let clientSecret = 'd45b554801ab4333b89a36bdbf04fad7'
let scopes = 'user-read-playback-state user-read-currently-playing'
let redirectUri = 'http://localhost:4000/token'
router.get('/login', (req, res) => {
res.redirect('https://accounts.spotify.com/authorize' + '?response_type=code&client_id=' + clientId + '&scope=' + encodeURIComponent(scopes) + '&redirect_uri=' + encodeURIComponent(redirectUri))
})
It never even gets redirected back, It just throws this error every time:
Access to XMLHttpRequest at 'https://accounts.spotify.com/authorize?response_type=code&client_id=<removed by me just in case>&scope=user-read-playback-state%20user-read-currently-playing&redirect_uri=http%3A%2F%2Flocalhost%3A3000' (redirected from 'http://localhost:4000/login') from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
I thought that if I make a call from my react app to my server, and then my server redirects to spotify's api it would be fine, but it still gives me this error.
also note that if I access the login page of my server from the browser, it will work but it does not like the "two jumps" I guess
I have seen this thread and am still a bit confused:
Access-Control-Allow-Origin denied spotify api
If i make the call from my client to my server, then the server makes the call to the token, shouldn't it work?
How can I implement the login properly?
Step one to avoid CORS issues is to make sure your website on Spotify is 'localhost:4000'.
The redirect is most likely causing the concern. You don't want to use a redirect. You want Spotify to authenticate the user and then redirect them back to your website with a token.
I would suggest using passportJS, this is a much more secure way if you are new to handling tokens and such.
Create a strategy as such
const passport = require('passport');
const SpotifyStrategy = require('passport-spotify').Strategy;
passport.serializeUser(function(user, done) {
done(null, user);
});
passport.deserializeUser(function(user, done) {
done(null, user);
});
passport.use(new SpotifyStrategy({
clientID: "c2b60e83cbdb4b5ba923140f0c32ac8f",
clientSecret: "d45b554801ab4333b89a36bdbf04fad7",
callbackURL: "http://localhost:4000/auth/spotify/callback"
},
function(accessToken, refreshToken, profile, done) {
return done(null, profile);
}
));
PassportJS Spotify Docs
const passport = require('passport');
require('./passport')
app.use(passport.initialize());
app.use(passport.session());
app.get('/auth/spotify',passport.authenticate('spotify'));
app.get('/auth/spotify/callback',passport.authenticate('spotify', { failureRedirect: '/auth/error' }),
function(req, res) {
res.redirect('/');
});
app.get('/logout', (req, res) => {
req.session = null;
req.logout();
res.redirect('/');
})
I am using PassportJS to authenticate users in my application. After a user logs in, the session is created, but soon after being redirected, the session appears to become undefined once again because it hasn't been saved. I found online that often times with redirects, the redirect completes before the session is saved, and so it's as if authentication never happened. The apparent solution is to use the req.session.save function so that redirects will only happen after the session is saved. However, I am getting an error log of "TypeError: req.session.save is not a function." Can somebody please help?
Here is my code for app.js.
var express = require('express'),
passport = require('passport'),
session = require('express-session'),
bodyParser = require('body-parser'),
RedisStore = require('connect-redis')(session),
redis = require('redis'),
logger = require('morgan'),
errorHandler = require('express-error-handler'),
site = require('./site'),
oauth2 = require('./oauth2'),
port = process.env.PORT || 8080;
var app = express();
var redisClient = redis.createClient(8080, 'localhost');
// use sessions for tracking logins
app.use(session({
secret: 'keyboard cat',
resave: true,
saveUninitialized: true,
store: new RedisStore({
client: redisClient,
host: "pub-redis-14280.us-central1-1-1.gce.garantiadata.com",
port: 12543,
ttl: 260
})
}));
app.use(logger('dev'));
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json({ type: 'application/json' }));
app.use(passport.initialize());
app.use(passport.session());
app.use(errorHandler({ dumpExceptions: true, showStack: true }));
// use ejs as file extension for views
app.set('view engine', 'ejs');
app.use(express.static(__dirname + '/views'));
// use passport
require('./auth');
// Account linking
app.get('/', site.index);
app.get('/login', site.loginForm);
app.post('/login', site.login);
app.get('/logout', site.logout);
app.get('/authorize', oauth2.authorization);
app.post('/authorize/decision', oauth2.decision);
// set up local server
if (module === require.main) {
// [START server]
// Start the server
var server = app.listen(process.env.PORT || 8080, function () {
var port = server.address().port;
console.log('App listening on port %s', port);
});
// [END server]
}
module.exports = app;
site.js:
var passport = require('passport');
var login = require('connect-ensure-login');
// get layout
exports.index = function (req, res) {
console.log("layout loaded");
res.render('layout');
}
// get login form
exports.loginForm = function (req, res) {
console.log("login page loaded");
res.render('login');
}
// post login form
exports.login = [
passport.authenticate('local'),
function (req, res) {
req.session.save(function (err) {
res.redirect('/');
});
}
]
// logout
exports.logout = function (req, res) {
req.logout();
res.redirect('/');
}
Passport serialize/deserialize user:
passport.serializeUser(function(id, done) {
console.log("serializing user");
done(null, id);
});
passport.deserializeUser(function(id, done) {
console.log("deserializing user");
done(null, id);
});
In my passport authentication, I return the user id for simplicity, since that's all I need to represent users in my system.
In case anybody else is still having this issue (like me), try following Nathan's comment above and debug your connection to your redis/mongo/etc store.
What worked for me was I had my redis host set to http://localhost so I swapped it to 127.0.0.1 (local development of course) and everything immediately worked.
I'm using PassportJS and NodeJS as backend for a website server. It uses the Passport-Local strategy. I develop the website in a MacOSX environment.
The server serves pages for information about our company and our commercial product.
It also serves help files (HTML web help) generated using Help and Manual.
The software that we sell runs on Windows and is programmed in Delphi VCL. We set it up so that it will redirect to our web help when pressing F1 using contextual ids like such :
procedure MyForm.OnHelp(blabla)
begin
ShellExecute( blabla, www.mywebsite.com/secure/help?contextid=202383)
end
All paths in www.mywebsite.com/secure are secure, and will require authentication with the passport.js middleware.
This is the relevant routing / passport configuration.
// CHECK AUTH METHOD
function ensureAuthenticated(req, res, next) {
if (req.isAuthenticated()) {
return next();
}
req.session.target = req.originalUrl;
req.session.save(function (err) {
res.redirect('/login')
});
}
// PASSPORT LOGIN STRATEGY
passport.use(
new passportLocal.Strategy({
usernameField: "username",
passwordField: "password"
},
function (email, password, done) {
if (!email) {
return done(null, false);
}
if (!password) {
return done(null, false);
}
if (password.toLowerCase() === 'ASTUPIDPASSWORD') {
return done(null, {email: email});
} else {
return done(null, false);
}
}
));
// PASSPORT SERIALIZE
passport.serializeUser(function (user, done) {
done(null, user.email);
});
// PASSPORT DESERIALIZE
passport.deserializeUser(function (email, done) {
done(null, {email: email});
});
// OTHER AMAZING ROUTES
router.use('/secure', ensureAuthenticated);
router.get('/secure/help', function (req, res) {
res.redirect('/secure/help/index.html');
});
This is the express configuration
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var compression = require('compression');
var fs = require('fs');
var passport = require('passport');
var session = require('express-session');
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'client', 'views'));
app.set('view engine', 'jade');
app.use(compression());
app.use(favicon(__dirname + '/client/images/favicon.png'));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true})); //
app.use(cookieParser());
app.use(require('less-middleware')(path.join(__dirname, 'client')));
app.use(express.static(path.join(__dirname, 'client')));
app.use(session({
saveUninitialized : true,
resave : false,///
secret: 'adamngoodsecret
}));
app.use(passport.initialize());
app.use(passport.session());
var routes = require('./routes/index');
app.use('/', routes);
When I develop in the MacOSX environment, I will login once. Then, I will be able to return to the secure zone because I guess I will have a cookie set up, regardless if I'm using safari or chrome.
Though, when I test it in a Windows environment, the problem occurs.
I open the broswer, type in www.mywebsite.com/secure/help?blabla
I am asked for login. OK
I enter login. OK
I am redirected to the initial requested url. OK
I close the tab WITHOUT CLOSING THE BROWSER. OK
I request the www.mywebsite.com/secure/help?blabla URL. OK
I can access the resource. OK
I close the browser. OK.
I open a new broser window. OK.
I request the www.mywebsite.com/secure/help?blabla. OK
I get asked for credentials. FAIL
So, whenever I close the browser window (Safari, Chrome) in Windows 10 environment, I get asked for the credentials again.
In MacOSX, even if I close the browser, I do not get asked the credentials again.
It is very unconvenient for my users to have to enter their credentials each time on the Windows environment.
Can anyone point me in the right direction in order to solve this? It seems the cookies in the Windows 10 environment are reset whenever the browser window is closed, though I'm not sure.
Thanks.
Is it possible to do basic auth in Node.js just like in Apache?
http://doc.norang.ca/apache-basic-auth.html
I know that if using Express or Connect I can add middle-ware functionality and do user verification, but I'm trying to restrict the whole area (I don't need to authenticate users from a database just a couple of defined users) - I'm using Ubuntu.
https://github.com/kaero/node-http-digest
That's something I can do, but I'm not sure if "exposing" or directly writing the user and password in the code is secure enough.
Many thanks.
Passport provides a clean mechanism to implement basic auth. I use it in my Node.js Express app to protect both my Angularjs-based UI as well as my RESTful API. To get passport up and running in your app do the following:
npm install passport
npm install passport-http (contains "BasicStrategy" object for basic auth)
Open up your app.js and add the following:
var passport = require('passport')
var BasicStrategy = require('passport-http').BasicStrategy
passport.use(new BasicStrategy(
function(username, password, done) {
if (username.valueOf() === 'yourusername' &&
password.valueOf() === 'yourpassword')
return done(null, true);
else
return done(null, false);
}
));
// Express-specific configuration section
// *IMPORTANT*
// Note the order of WHERE passport is initialized
// in the configure section--it will throw an error
// if app.use(passport.initialize()) is called after
// app.use(app.router)
app.configure(function(){
app.use(express.cookieParser());
app.use(express.session({secret:'123abc',key:'express.sid'}));
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.set('view options', {
layout: false
});
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.static(__dirname + '/public'));
app.use(passport.initialize());
app.use(app.router);
app.use(logger);
});
// Routes
app.get('/', passport.authenticate('basic', { session: false }), routes.index);
app.get('/partials/:name', routes.partials);
// JSON API
app.get('/api/posts', passport.authenticate('basic', { session: false }), api.posts);
app.get('/api/post/:id', passport.authenticate('basic', { session: false }), api.post)
// --Repeat for every API call you want to protect with basic auth--
app.get('*', passport.authenticate('basic', { session: false }), routes.index);
Put this
app.use(express.basicAuth(function(user, pass) {
return user === 'test' && pass === 'test';
}));
before the line to
app.use(app.router);
to protect all routes with http basic auth
Have a look at:
user authentication libraries for node.js?
It does not answer your question 100% - but maybe it helps.
I think good choice could be http-auth module
// Authentication module.
var auth = require('http-auth');
var basic = auth.basic({
realm: "Simon Area.",
file: __dirname + "/../data/users.htpasswd" // gevorg:gpass, Sarah:testpass ...
});
// Application setup.
var app = express();
app.use(auth.connect(basic));
// Setup route.
app.get('/', function(req, res){
res.send("Hello from express - " + req.user + "!");
});