How access a secret from keyvault? - reactjs

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>

Related

Getting cors error in express and react app while redirecting when I use passport-local strategy and not when I use passport-google-oauth20

This is my back-end code.
import { config } from "dotenv"
import express,{Request,Response} from "express"
import mongoose, {ConnectOptions, Error} from "mongoose";
import DeckModal from './models/deck';
import cors from "cors"
import session from "express-session";
import passport from "passport";
import User from "./models/user";
import bcrypt from "bcryptjs"
import { UserDetails } from "./types/UserDetails";
import passportLocal from "passport-local"
const LocalStrategy=passportLocal.Strategy
const MongoDBStore = require('connect-mongodb-session')(session);
const GoogleStrategy =require("passport-google-oauth20")
config()
let PORT:number=5000
const app = express()
app.use(express.json())
const store = new MongoDBStore({
uri: process.env.MONGO_URI!,
collection: 'mysessions'
});
store.on('error', function(error:Error) {
console.log(error);
});
app.use(cors({ origin:"http://localhost:5173" ,credentials:true}))
app.set('trust proxy', 1)
// create session
app.use(session({
secret:process.env.SESSION_SECRET!,
resave:true,
saveUninitialized:true,
store:store,
// cookie:{
// httpOnly:true, //An HttpOnly Cookie is a tag added to a browser cookie that prevents client-side scripts from accessing data
// sameSite:"none", // If your frontend and backend is hosted on different origin then use sameSite:none in order to share cookie.
// secure:true, // it allows only https requests
// maxAge: 1000 * 60 * 60 * 24 * 7
// }
}))
app.use(passport.initialize())
app.use(passport.session())
// local strategy
passport.use(new LocalStrategy(
{usernameField:"email"},
function(username, password, done) {
User.findOne({ email: username }, function (err:Error, user:UserDetails) {
if (err) { return done(err); } // If there is error we will return an error to done function.
if (!user) { return done(null, false); } // If we don't find any user then we will return null and as there is no error we will send false
bcrypt.compare(password,user.password!,(err,result)=>{
if(err) throw err
if(result===true){
return done(null,user)
}
else{
return done(null,false)
}
})
});
}
));
//gooogle strategy
passport.use(
new GoogleStrategy(
{
callbackURL: "/auth/google/callback",
clientID: process.env.CLIENT_ID,
clientSecret: process.env.CLIENT_SECRET,
},
async (accessToken:any, refreshToken:any, profile:any, done:any) => {
const userInfo:UserDetails={
email:profile.emails[0].value,
profile_picture:profile.photos[0].value,
username:profile.displayName
}
try{
const user= await User.findOne({ email: profile.emails[0].value })
if(!user){
const newUser= new User(userInfo)
await newUser.save()
return done(null,newUser)
}
return done(null,user)
}
catch(err){
return done(null,false,err)
}
}
)
);
passport.serializeUser((user:any,done)=>{
return done(null,user._id)
})
passport.deserializeUser((id:string,done)=>{
User.findById(id,(err:Error,doc:any)=>{
const userInfo={
username:doc.username,
email:doc.email,
profile_picture:doc.profile_picture,
}
return done(null,userInfo)
})
})
// This is the recomended way to connect to the mongodb database using mongoose. source: https://docs.cyclic.sh/how-to/using-mongo-db#connection-example-mongoclient
mongoose.connect(process.env.MONGO_URI!,{
useUnifiedTopology: true,
useNewUrlParser: true
} as ConnectOptions )
.then(()=>{console.log("Connected to mongodb successfully");
// We will start to listen for request once the DB is connected
app.listen(process.env.PORT || PORT,()=>{
console.log("server is running on port "+PORT)
})
})
.catch((err:Error)=>{console.log(err)});
//Google auth
app.get('/auth/google', passport.authenticate('google', {
scope: ['profile','email']
// scope:['openid','profile', 'email']
}));
app.get('/auth/google/callback',
passport.authenticate('google', { failureRedirect: "http://localhost:5173/login", failureMessage: true,successRedirect:"http://localhost:5173/auth/success"}));
app.get("/getuser",(req,res)=>{
if(req.user){
res.json(req.user) // here we get user in req.user because passports deserialize function attaches it automatically
}
else{
res.json(null)
}
})
app.post('/auth/logout', function(req, res, next) {
req.logout(function(err) {
if (err) { return next(err); }
res.send('done');
});
});
// Local auth
app.post("/signup",async(req:Request,res:Response)=>{
const {username,email,password}=req?.body
if(!username && !password && !email){
res.send("Please provide name, email and password")
return
}
try{
const user:UserDetails | null=await User.findOne({username})
if(!user){
const hashedPassword=await bcrypt.hash(password,10)
const newUser= new User({
username,
email,
password:hashedPassword
})
await newUser.save()
res.send("success")
}
else{
res.send("User already exist")
}
}
catch(error){
console.log(error)
}
})
app.post('/login',
passport.authenticate('local'),
(req, res) => {
if(req.user){
res.redirect("http://localhost:5173/auth/success")
}
else{
res.status(200).json({success:false});
}
}
);
I tried redirecting using successRedirect but that is also giving me same error
app.post('/login',
passport.authenticate('local',{successRedirect:"http://localhost:5173/auth/success"}));
**
When I authenticate using google auth it successfully redirects me but when I use local startegy for authentication it gives me cors error.**
Error message:
Access to XMLHttpRequest at 'http://localhost:5173/' (redirected from 'http://localhost:5000/login') from origin 'http://localhost:5173' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
The front-tend request looks like this-
for local strategy:
const handleLogin = async (e:FormEvent<HTMLFormElement>) => {
e.preventDefault()
const data = { email, password };
axios.post("http://localhost:5000/login",data,{withCredentials:true})
}
for google strategy:
const handleGoogleLogin = async () => {
window.open("http://localhost:5000/auth/google","_self")
}

react-aad-msal authProvider.getAccessToken() reloads the component indefinitely

I have added a axios interceptor within which authProvider.getAccessToken() is called to fetch token and add to header of each request.
Here is my axiosInterceptor.js
import axios from 'axios'
import { authProvider } from '../authProvider'
export const axiosApiIntance = axios.create()
export const axiosInterceptor = axiosApiIntance.interceptors.request.use(async request => {
try {
let token = await authProvider.getAccessToken()
request.headers['Authorization'] = `Bearer ${token.accessToken}`
return request
} catch (err) {
console.log(err)
}
}, error => {
return Promise.reject(error.message)
})
Here is my authProvider.js
import { LoginType, MsalAuthProvider } from 'react-aad-msal'
// The auth provider should be a singleton. Best practice is to only have it ever instantiated once.
// Avoid creating an instance inside the component it will be recreated on each render.
// If two providers are created on the same page it will cause authentication errors.
export const authProvider = new MsalAuthProvider(
{
auth: {
authority: process.env.REACT_APP_AUTHORITY,
clientId: process.env.REACT_APP_CLIENT_ID,
postLogoutRedirectUri: process.env.REACT_APP_URL,
redirectUri: process.env.REACT_APP_URL,
validateAuthority: true,
// After being redirected to the "redirectUri" page, should user
// be redirected back to the Url where their login originated from?
navigateToLoginRequestUrl: false
},
cache: {
cacheLocation: 'sessionStorage',
storeAuthStateInCookie: true
}
},
{
scopes: ['openid', 'profile', 'user.read']
},
{
loginType: LoginType.Redirect,
// When a token is refreshed it will be done by loading a page in an iframe.
// Rather than reloading the same page, we can point to an empty html file which will prevent
// site resources from being loaded twice.
tokenRefreshUri: window.location.origin + '/auth.html'
}
)
authProvider is used in App.js
<AzureAD provider={authProvider} reduxStore={configureStore}>
....
</AzureAD>
axiosInterceptor is also included in App.js.
Please provide suggestion on what could cause the component the reload indifinitely.
I have removed the authProvider.getAccessToken() and verified, it works fine. So the reload is caused due to that.
First, I suggest you to verify the Scope, authority and clientId of your AuthProvider.
I had a similar issue in one project ans I had to add the scope to the getAccessToken() function, even if I never did that in others projects..
See below:
var authenticationParameters = {
scopes: ['openid', 'profile', 'user.read'],
};
axios.interceptors.request.use(function (config): any {
return new Promise(async (resolve: any, reject: any) => {
await authProvider.getAccessToken(authenticationParameters).then((response: any) => {
config.headers["Authorization"] = "Bearer " + response.accessToken;
config.headers["Content-Type"] = "application/json";
config.headers.Accept = "application/json";
resolve(config);
})
.catch((error: any) => {
console.log(error.message);
});
});
});
Hope it help ;)
Regards

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

setting state not setting in React

I have the following code
this.setState({loading: true}, () => {
if (googleUser && googleUser.profileObj) {
app.auth().signOut();
// We need to register an Observer on Firebase Auth to make sure auth is initialized.
var unsubscribe = app.auth().onAuthStateChanged(firebaseUser => {
unsubscribe();
// Check if we are already signed-in Firebase with the correct user.
if (!this.isUserEqual(googleUser, firebaseUser)) {
// Build Firebase credential with the Google ID token.
var credential = firebase.auth.GoogleAuthProvider.credential(
googleUser.getAuthResponse().id_token);
// Sign in with credential from the Google user.
app.auth().signInWithCredential(credential).catch(error => {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
// The email of the user's account used.
var email = error.email;
// The firebase.auth.AuthCredential type that was used.
var credential = error.credential;
this.setState({error: errorMessage, loading: false})
}).then(() => {
app.auth().onAuthStateChanged((user) => {
if (user) {
user.getIdToken().then(idToken => {
// Session login endpoint is queried and the session cookie is set.
// CSRF protection should be taken into account.
const csrfToken = Utils.getCookie('csrfToken');
this.postIdTokenToSessionLogin('/auth/session-login', idToken, csrfToken, 'google', googleUser.profileObj.googleId);
});
} else {
this.setState({error: "There was an error", loading: false});
}
});
})
} else {
this.setState({error: "You are already signed in.", loading: false});
}
});
}
});
The code is not working, it's not setting loading to true and not re-rendering before invoking the callback function in setState.
Does anyone know what's going on?

Can't authenticate with adal.js without angular

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();
});

Resources