Can't authenticate with adal.js without angular - azure-active-directory

my project is composed from a webapi and a SPA application.
I try to implement authentication using Adal.js.
this is my javascript code:
$(function () {
var endpoints = {
"https://demowebapi2017.azurewebsites.net/api/values/7": "WEB API ID"
};
window.config = {
tenant: '7dda5c2-2fb6-4f82-...',
clientId: 'CLIENT ID',
endpoints: endpoints
};
window.authContext = new AuthenticationContext(config);
$("#login").click(function () {
window.authContext.login();
});
$("#logout").click(function () {
window.authContext.logOut();
});
$("#clickMe").click(function () {
var user = window.authContext.getCachedUser();
console.log(user);
window.authContext.acquireToken('https://demowebapi2017.azurewebsites.net', function (error, token) {
console.log(error);
console.log(token);
}
);
});
});
Login works fine, I can see the login IFRAME for entering my credentials.
When I click 'clickMe' I get the error message: 'User login is required' and user is null.
Everything works fine using Angular and Adal-angular.js, so I thing all Azure configuration is fine.
Has anybody have an idea about what happen?

After you login, the app need to init the user from parsing the id_token from the hash. Here is the demo code for your reference:
$(function () {
var endpoints = {
"https://graph.windows.net": "https://graph.windows.net"
};
window.config = {
tenant: 'xxxx.onmicrosoft.com',
clientId: 'aac92cf9-32ab-4004-aeab-1046389dff79',
endpoints: endpoints
};
window.authContext = new AuthenticationContext(config);
$("#login").click(function () {
window.authContext.login();
});
$("#logout").click(function () {
window.authContext.logOut();
});
$("#clickMe").click(function () {
var user = window.authContext.getCachedUser();
console.log(user);
window.authContext.acquireToken('https://graph.windows.net', function (error, token) {
console.log(error);
console.log(token);
}
);
});
function init(){
if(window.location.hash!="")
window.authContext.handleWindowCallback(window.location.hash);
}
init();
});

Related

React profile page, how to avoid 'GET http://localhost:3001/users/profile 401 (Unauthorized)' when trying to get JSON data from back end

For this application, I am using React & Express. I have React running on PORT 3000, and Express running on PORT 3001. On the Express side, I have authentication working that uses JWT.
First, here is my auth.js service file:
const jwt = require('jsonwebtoken');
const models = require('../models');
const bcrypt = require('bcryptjs');
var authService = {
signUser: function (user) {
const token = jwt.sign({
Username: user.Username,
UserId: user.UserId
},
'secretkey',
{
expiresIn: '1h'
}
);
return token;
},
verifyUser: function (token) {
try {
let decoded = jwt.verify(token, 'secretkey');
return models.users.findByPk(decoded.UserId);
} catch (err) {
console.log(err);
return null;
}
},
hashPassword: function (plainTextPassword) {
let salt = bcrypt.genSaltSync(10);
let hash = bcrypt.hashSync(plainTextPassword, salt);
return hash;
},
comparePasswords: function (plainTextPassword, hashedPassword) {
return bcrypt.compareSync(plainTextPassword, hashedPassword);
}
}
module.exports = authService;
When a user makes a POST request to the signup route, it works:
router.post('/signup', function (req, res, next) {
models.users.findOrCreate({
where: {
Username: req.body.username
},
defaults: {
FirstName: req.body.firstName,
LastName: req.body.lastName,
Email: req.body.email,
Password: authService.hashPassword(req.body.password)
}
})
.spread(function (result, created) {
if (created) {
res.redirect("http://localhost:3000/login");
} else {
res.send('This user already exist')
}
});
});
Signup works in both Postman and React.
When a user makes a POST request to the login route, it works:
router.post('/login', function (req, res, next) {
models.users.findOne({
where: {
Username: req.body.username
}
}).then(user => {
if (!user) {
console.log('User not found')
return res.status(401).json({
message: "Login Failed"
});
} else {
let passwordMatch = authService.comparePasswords(req.body.password, user.Password);
if (passwordMatch) {
let token = authService.signUser(user);
res.cookie('jwt', token);
res.redirect('http://localhost:3001/users/profile');
} else {
console.log('Wrong Password');
}
}
});
});
Login works in both Postman and React.
When a user makes a GET request to the profile route, it semi-works:
router.get('/profile', function (req, res, next) {
let token = req.cookies.jwt;
if (token) {
authService.verifyUser(token).then(user => {
if (user) {
res.setHeader('Content-Type', 'application/json');
res.send(JSON.stringify(user));
} else {
res.status(401);
res.send('Invalid authentication token');
}
});
} else {
res.status(401);
res.send('Invalid authentication token');
}
});
This works only in Postman, I can see the data that I want using Postman. In React, it will not get the profile route that I request. This is where the error comes in: Console Error
On the React side, this is profile GET component:
import React from 'react';
import axios from 'axios';
class UserProfile extends React.Component {
constructor(props) {
super(props);
this.state = {
profileData: []
}
};
fetchProfileData = () => {
var encodedURI = window.encodeURI(this.props.uri);
return axios.get(encodedURI).then(response => {
this.setState(() => {
return {
profileData: response.data
};
});
});
};
componentDidMount() {
this.fetchProfileData();
}
render() {
console.log(this.state.profileData);
if (this.state.profileData.length === 0) {
return <div>Failed to fetch data from server</div>
}
const profile = this.state.profileData.map(user => (
<div key={user.UserId}>Hello world</div>
));
return <div>{profile}</div>
}
}
export default UserProfile;
Then when I go to render this component, I just:
<UserProfile uri="http://localhost:3001/users/profile" />
Which then will render 'Failed to fetch data from server', then the console will log the '401 (Unauthorized)' error. I just can't get it to render in React.
And if anyone wants my Express app.js file for some extra information:
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var models = require('./models');
var cors = require('cors');
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'hbs');
app.use(function (req, res, next) {
res.header('Access-Control-Allow-Origin', 'http://localhost:3000');
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
next();
});
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(cors());
app.use('/', indexRouter);
app.use('/users', usersRouter);
// catch 404 and forward to error handler
app.use(function (req, res, next) {
next(createError(404));
});
// error handler
app.use(function (err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
models.sequelize.sync().then(function () {
console.log("DB Synced Up");
});
module.exports = app;
Thank you in advanced. I have been struggling to figure this out.
I have tried toying with my UserProfile component. And I've tried toying with my /profile route in Express. The only 2 errors I've gotten is the 401 (Unauthorized) and something about the Headers. I know that my JWT key gets passed onto reacts side, because when I do 'localhost:3000/profile' (react side), I can see that I have the cookie stored. I'm not sure on how to approach authorization on React side. At this point, I am very clueless on what to do. This is the first time I've tried setting up authentication with React. I have always used Express and the .hbs files to render my profile pages. But I've been told that you shouldn't render a profile page in the back-end. So, here I am trying to do it with React.
I have rendered things from the back-end to the front-end, but that's without the use of JWT. I strongly believe that it has something to do with the JWT cookie. I just don't know how to authenticate it in React. Thanks again in advanced.
I fixed it by adding this into my React project:
I added this into my fetchProfileData()
{ withCredentials: true }
fetchProfileData = () => {
var encodedURI = window.encodeURI(this.props.uri);
return axios.get(encodedURI, { withCredentials: true }).then(response => {
this.setState(() => {
return {
profileData: response.data
};
});
});
};
Then in Express, I toyed with my Profile route. Put the data into an array, and sent it on its way:
router.get('/profile', function (req, res, next) {
var userData = [];
let token = req.cookies.jwt;
if (token) {
authService.verifyUser(token).then(user => {
userData.push(user);
res.send(userData);
});
} else {
res.status(401);
res.send('Invalid authentication token');
}
});

Server-side authorization with JWT in SvelteKit

I have an issue sending a JWT token to the server and using it to authorize access in load handlers. I am using Firebase on the client for authentication. When logged in (onAuthStateChanged), I send a POST request with the token to the /api/login endpoint:
export async function post(req) {
const idToken = req.headers['authorization']
try {
const token = await firebase().auth().verifyIdToken(idToken)
req.locals.user = token.uid
} catch (e) {
console.log(e)
return {
status: 500,
body: 'forbidden',
}
}
return {
status: 200,
body: 'ok',
}
}
In hooks.js:
export function getSession(request) {
return {
user: request.locals.user
}
}
export async function handle({ request, resolve }) {
const cookies = cookie.parse(request.headers.cookie || '')
request.locals.user = cookies.user
const response = await resolve(request)
response.headers['set-cookie'] = `user=${request.locals.user || ''}; Path=/; HttpOnly`
return response
}
In load methods:
export async function load({ session }) {
if (!session.user) {
return {
status: 302,
redirect: '/start'
}
}
// ...
}
All of this works fine except that any client-side navigation after a login is rejected because session.user is still undefined. When navigating by typing the URL in the browser, it works correctly and after that the client-side navigation also works.
Any ideas why and what to do?
I have solved this by adding a browser reload on whichever page the user lands on after logging in. The snippet for the reload on the client side handling on a successful response from the login API endpoint looks like this
if (sessionLoginResponse?.status === "success") {
await signOut(auth);
window.history.back();
setTimeout(() => {
window.location.reload();
}, 10);
}

Login to MS Graph with React and MSAL fails with no errors on console

I'm trying to use MSAL and React to login to MSGraph. I get the popup to authenticate when I call userAgentApplication.loginPopup({propt: "select_account", scopes: config.scopes})
After entering my login information, it appears that I authenticated but when I try to make a request the login popup continues to display as if I didn't authenticate already. I get no errors on the console.
I refresh the page and check localStorage and see msal.error = invalid_state_error
I'm using MSAL version v1.4.6
Here is my code
ContextualMenu.js
import { msgraph } from './actions/graphAction';
const graph = useSelector((state) => state.graph);
const userAgentApplication = new UserAgentApplication({
auth: {
clientId: config.appId,
redirectUri: config.redirectUri
},
cache: {
cacheLocation: 'localStorage',
storeAuthStateInCookie: true
}
});
const getUserProfile = async () => {
try {
let accessToken = await userAgentApplication.acquireTokenSilent({
scopes: config.scopes
});
if (accessToken) {
let user = await getUserDetails(accessToken);
let uu = {
displayName: user.displayName,
email: user.mail || user.userPrincipalName,
givenName: user.givenName,
surname: user.surname
}
dispatch(msgraph(true, uu, null));
}
} catch (err) {
console.log(err);
}
};
const msLogin = async () => {
try {
await userAgentApplication.loginPopup({
prompt: "select_account"
});
getUserProfile();
}
catch (err) {
console.log('failed', err);
}
};
const emailFiles = () => {
setRootClassName('close');
if (graph.isAuthenticated) {
checkSelectedFile();
return false;
}
msLogin();
}
After loginPopup is called it never gets to getUserProfile and doesn't error either.
Please any help is appreciated

How access a secret from keyvault?

I created sample react login application where user can login by implict oauth2 login via azure ad by refering to this documentation.
After successful login I am not able to access keyvault secrets by using microsoft graph api with the help of access token.
This is the error I am getting while I am trying to access secrets from keyvault
I also updated the scopes in my code by adding additional keyvault scope in config.js file.
module.exports ={
appId: "6c90510c-82fd-4113-8aa5-6abdac107839",
scopes: [
"user.read",
"https://vault.azure.net/user_impersonation"
]
};
I also added Keyvault Api permissions in app registration from azure portal.
.
Here is my login code
import { UserAgentApplication } from 'msal'
class Login extends React.Component {
constructor(props) {
super(props);
this.submitHandler = this.submitHandler.bind(this);
this.email = React.createRef();
this.password = React.createRef();
this.token = "";
this.expiresOn = 0;
this.userAgentApplication = new UserAgentApplication({
auth: {
clientId: config.appId,
clientSecret: "W~~4iZ.uKv~eoAd-hKKU35WJVv.---83Gm",
redirectUri: "http://localhost:3000/events"
},
cache: {
cacheLocation: "localStorage", // This configures where your cache will be stored
storeAuthStateInCookie: true, // Set this to "true" if you are having issues on IE11 or Edge
}
});
this.state = {
error: null,
isAuthenticated: false,
user: {}
};
}
login = async () => {
try {
// Login via popup
await this.userAgentApplication.loginPopup(
{
scopes: config.scopes,
prompt: "select_account"
});
// After login, get the user's profile
await this.getUserProfile();
var user = this.userAgentApplication.getAccount();
}
catch (err) {
console.log("errror", err.message)
this.setState({
isAuthenticated: false,
user: {},
error: err.message
});
}
}
logout = () => {
this.userAgentApplication.logout();
}
getUserProfile = async () => {
try {
const data = await this.userAgentApplication.acquireTokenSilent({
scopes: config.scopes
});
if (data.accessToken) {
console.log("Token", data.accessToken);
this.token = data.accessToken;
this.expiresOn = data.expiresOn;
}
}
catch (err) {
console.log("err", err.message);
}
}
render() {
const { isLogin } = this.props;
const buttonTitle = isLogin ? "Sign Up" : "Login";
return (
<form className="login-form">
{ !isLogin && <IconButton backgroundColor="#0A66C2" font="14px" width='35%' padding='8px' microsoftLoginHandler={this.login} />}
</form>
);
}
}
After getting access token when I tried the hit api from postman. It is showing some error. Can anyone please guide me to resolve this error as I am new to Azure Ad and Keyvault
Thanks in advance
Take my code into consideration, it offers function to get access token which can used to call api to access key vault:
And pls note, at first I got the same error message as yours when I call api with access token in ["openid", "profile", "User.Read", "https://vault.azure.net/user_impersonation"], then I decode the token and found it didn't contain 'user_impersonation' in claim 'sub', so I changed the scope in the code and then it worked.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<script src="js/jquery-1.10.2.min.js"></script>
<script type="text/javascript" src="https://alcdn.msftauth.net/lib/1.2.1/js/msal.js" integrity="sha384-9TV1245fz+BaI+VvCjMYL0YDMElLBwNS84v3mY57pXNOt6xcUYch2QLImaTahcOP" crossorigin="anonymous"></script>
</head>
<body>
<button id="btn">click</button>
<div>
userName:<input type="text" id="userName" />
</div>
<div id="accessToken"></div>
<script type="text/javascript">
$("#btn").click(function(){
showWelcome();
})
const msalConfig = {
auth: {
clientId: "<your azure ad app id>", // pls add api permission with azure key vault
authority: "https://login.microsoftonline.com/<your-tenant>",
redirectUri: "http://localhost:8848/msalTest/index.html",
},
cache: {
cacheLocation: "sessionStorage", // This configures where your cache will be stored
storeAuthStateInCookie: false, // Set this to "true" if you are having issues on IE11 or Edge
}
};
const myMSALObj = new Msal.UserAgentApplication(msalConfig);
//at first, I used scope like ["openid", "profile", "User.Read", "https://vault.azure.net/user_impersonation"]
//but with this accesstoken, I got the same error as yours
//and I try to use the scope below, and it worked
//I decode the token with jwt, when I get error, the token didn't contains correct scope
const loginRequest = {
scopes: ["openid", "profile", "https://vault.azure.net/user_impersonation"],
};
function showWelcome(){
myMSALObj.loginPopup(loginRequest)
.then((loginResponse) => {
console.info(loginResponse);
console.log("========= myMSALObj.getAccount() =======");
console.log(myMSALObj.getAccount());
$("#userName").val(myMSALObj.getAccount().name);
getAccessToken();
//Login Success callback code here
}).catch(function (error) {
console.log(error);
});
}
function getAccessToken(){
getTokenPopup(loginRequest)
.then(response => {
$("#accessToken").text(response.accessToken);
}).catch(error => {
console.log(error);
});
}
function getTokenPopup(request) {
return myMSALObj.acquireTokenSilent(request)
.catch(error => {
console.log(error);
console.log("silent token acquisition fails. acquiring token using popup");
// fallback to interaction when silent call fails
return myMSALObj.acquireTokenPopup(request)
.then(tokenResponse => {
return tokenResponse;
}).catch(error => {
console.log(error);
});
});
}
</script>
</body>
</html>

Problem Redirecting after google signin in express react app using passport-google-oauth20

I have express backend and create-react-app2 as frontend , I am using setupProxy also. Now I have configured the app for google sign in however I am not getting proper redirect to index page after signin. Here is google oauth setup in console.developer.google.com
I am using passport google oauth20 for authentication:
Here my passport file:
const GoogleStrategy = require('passport-google-oauth20').Strategy;
const keys = require('./../keys/secret');
const {User} = require('./../models/user');
module.exports = function(passport) {
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
done(err, user);
});
});
passport.use(new GoogleStrategy({
clientID: keys.googleClientID,
clientSecret: keys.googleClientSecret,
callbackURL: '/auth/google/callback'
},
async (accessToken, refreshToken, profile, done) => {
const existingUser = await User.findOne({ 'google.id' : profile.id });
if(existingUser) {
done(null, existingUser);
}else {
const user = await new User({
'google.id' : profile.id,
isSocialAccountPresent: true })
.save();
done(null, user);
}
}
));
}
Here are my routes:
router.get('/google',
passport.authenticate('google', { scope: ['profile', 'email'] }));
router.get('/google/callback',
passport.authenticate('google'),
(req, res) => {
// Successful authentication, redirect home.
res.redirect('/');
});
Here is my setupProxy file:
const proxy = require("http-proxy-middleware");
module.exports = function(app) {
app.use(proxy("/auth/*", { target: "http://localhost:5000/" }));
app.use(proxy("/api/*", { target: "http://localhost:5000/" }));
};
Im gettting redirected to following URL :
http://localhost:3000/auth/google/callback?code=4/0gBVQE1R0m7FWFlg_QtXE2n9lvwVHoG_lNNH_zCueOeQAJZc6-8FygiH9u_9BfnQQt8yl2ECmD1gW3Gq9by25D4&scope=email+profile+https://www.googleapis.com/auth/userinfo.profile+https://www.googleapis.com/auth/userinfo.email
instead of http://localhost:5000/auth/google/callback
In your setupProxy.js file try this...
app.use(proxy("/auth/**", { target: "http://localhost:5000/" }));
app.use(proxy("/api/*", { target: "http://localhost:5000/" }));
You'll see I added an additional asterisk. This tells node to go one level deeper for "/callback".
Using res.redirect('/') you've to use the full path (http://localhost:5000....).
None of the answers here has worked for me. I believe that you would already have the file setupProxy.js in the src/ folder of your React client. Then, in that file, if you're setting changeOrigin: true for the object passed in the createProxyMiddleware function, you should remove that and that should fix the problem. At least, that worked for me.
This did the trick for me on
setupProxy.js
const { createProxyMiddleware } = require("http-proxy-middleware");
module.exports = (app) => {
app.use(
["/api", "/auth/google/callback"],
createProxyMiddleware({
target: "http://localhost:5000",
})
);
};

Resources