Reputation: 137
I am trying to implement microsoft oauth button in react that redirects/pop up allows the user to sign into their microsoft account then use the info to get the access token to then send to graph api to get user info to login. I'm using this package https://www.npmjs.com/package/msal and tried registering a url for the app to redirect to.
Problem is the react app is using hash router to lazy load pages, and in order to register the redirect url in Azure AAD it can't be a fragment. So I tried switching to Browser router and upon testing it in local host atleast got results from a successful redirect.
Then trying it in a production build could'nt get it to redirect successfully. Every time it redirected, refreshed or even wrote a different path in the url it could not find the page. I read about this issue from here React-router urls don't work when refreshing or writing manually. But now not sure how to go about fixing this. I'm fairly new to coding so any kind of help of suggestion would be appreciated.
import React, { Component } from 'react';
import { BrowserRouter,browserHistory,Router, Route, Switch } from 'react-router-dom';
import { ProtectedRoute } from './auth/ProtectedRoute'
const loading = () => <div className="animated fadeIn pt-3 text-center">Loading...</div>;
// Containers
const DefaultLayout = React.lazy(() => import('./containers/DefaultLayout'));
// Pages
const Login = React.lazy(() => import('./views/Pages/Login'));
const Register = React.lazy(() => import('./views/Pages/Register'));
const Page404 = React.lazy(() => import('./views/Pages/Page404'));
const Page500 = React.lazy(() => import('./views/Pages/Page500'));
//const EditInfo = React.lazy(() => import('./views/Pages/EditInfo'));
export class App extends Component {
render() {
return (
<BrowserRouter>
<React.Suspense fallback={loading()}>
<Switch>
<Route exact path="/login" name="Login Page" render={props => <Login {...props}/>} />
<Route exact path="/register" name="Register Page" render={props => <Register {...props}/>} />
<Route exact path="/404" name="Page 404" render={props => <Page404 {...props}/>} />
<Route exact path="/500" name="Page 500" render={props => <Page500 {...props}/>} />
<ProtectedRoute path="/" name="Home" component={DefaultLayout} />
<ProtectedRoute path="/dashboard" name="Home" component={DefaultLayout} />
</Switch>
</React.Suspense>
</BrowserRouter>
);
}
}
My function to handle the configs, redirect, requests. I'm looking to redirect to the login page. However in the production build using browser router redirects to a page that won't be found on the server.
import * as Msal from 'msal';
import axios from 'axios'
export function loginOauth () {
var msalConfig = {
auth: {
clientId: 'my client id',
redirectUri: 'http://mysite.io/login'
},
cache: {
cacheLocation: "sessionStorage",
storeAuthStateInCookie: true
}
};
let loginRequest = {
scopes: ["user.read"],
prompt: 'select_account'
}
let accessTokenRequest = {
scopes: ["user.read","profile"]
}
var graphConfig = {
graphMeEndpoint: "https://graph.microsoft.com/v1.0/me"
};
var msalInstance = new Msal.UserAgentApplication(msalConfig);
let authRedirectCallBack = (errorDesc, token, error, tokenType) => {
if (token) {
console.log(token);
}
else {
console.log(error + ":" + errorDesc);
}
};
msalInstance.handleRedirectCallback(authRedirectCallBack);
let signIn = () => {
msalInstance.loginRedirect(loginRequest).then(async function (loginResponse) {
return msalInstance.acquireTokenSilent(accessTokenRequest);
}).then(function (accessTokenResponse) {
const token = accessTokenResponse.accessToken;
console.log(token);
}).catch(function (error) {
//handle error
});
}
let acquireTokenRedirectAndCallMSGraph = () => {
//Always start with acquireTokenSilent to obtain a token in the signed in user from cache
msalInstance.acquireTokenSilent(accessTokenRequest).then(function (tokenResponse) {
callMSGraph(graphConfig.graphMeEndpoint, tokenResponse.accessToken, graphAPICallback);
}).catch(function (error) {
console.log(error);
// Upon acquireTokenSilent failure (due to consent or interaction or login required ONLY)
// Call acquireTokenRedirect
if (requiresInteraction(error.errorCode)) {
msalInstance.acquireTokenRedirect(accessTokenRequest);
}
});
}
let callMSGraph = (theUrl, accessToken, callback) => {
console.log(accessToken);
var xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function () {
if (this.readyState === 4 && this.status === 200)
callback(JSON.parse(this.responseText));
}
xmlHttp.open("GET", theUrl, true); // true for asynchronous
xmlHttp.setRequestHeader('Authorization', 'Bearer ' + accessToken);
xmlHttp.send();
}
let graphAPICallback = (data) => {
console.log(data);
}
let requiresInteraction = (errorCode)=> {
if (!errorCode || !errorCode.length) {
return false;
}
return errorCode === "consent_required" ||
errorCode === "interaction_required" ||
errorCode === "login_required";
}
signIn();
}
On redirect and onloading of the page I am using the componentwillmount to get the data from the sessions.
export default class Login extends Component{
constructor(props) {
super(props);
this.state = {
modal: false,
};
this.toggle = this.toggle.bind(this);
}
toggle() {
this.setState(prevState => ({
modal: !prevState.modal,
}));
}
handleLogout(){
auth.logout(() => {console.log("logged out")})
this.toggle()
}
componentWillMount() {
var msalConfig = {
auth: {
clientId: 'my_clientid',
redirectUri: 'http://mysite.io/login'
},
cache: {
cacheLocation: "sessionStorage",
storeAuthStateInCookie: true
}
};
var msalInstance = new Msal.UserAgentApplication(msalConfig);
if (msalInstance.getAccount()) {
var tokenRequest = {
scopes: ["user.read"]
};
msalInstance.acquireTokenSilent(tokenRequest)
.then(response => {
callMSGraph(response.accessToken, (data)=>console.log(data))
// get access token from response
// response.accessToken
})
.catch(err => {
// could also check if err instance of InteractionRequiredAuthError if you can import the class.
if (err.name === "InteractionRequiredAuthError") {
return msalInstance.acquireTokenPopup(tokenRequest)
.then(response => {
// get access token from response
// response.accessToken
})
.catch(err => {
// handle error
});
}
});
} else {
// user is not logged in, you will need to log them in to acquire a token
}
let token = sessionStorage.getItem('msal.idtoken');
if(token !== null) {
var decoded = jwt_decode(token);
console.log(decoded);
} else {
console.log("NO token yet");
}
}
render() {
let open;
if(auth.isAuthenticated() === "true"){
open = true
}else{ open = false}
return (<div><button onClick={ () => {loginOauth()}} >Login with Microsoft</button> </div>);
}
Upvotes: 1
Views: 3448
Reputation: 33124
You should only register the base URL you want the user redirected to. To allow your state data (i.e. your fragment) to traverse the service boundaries, you use the state
parameter.
Any information you pass into the OAuth via the state
parameter will be returned with your token. You then parse the state
when the user is returned.
Upvotes: 2