Skip to content
This repository has been archived by the owner on Jun 26, 2021. It is now read-only.

Working example with react/axios #481

Closed
magnuf opened this issue Feb 8, 2017 · 12 comments
Closed

Working example with react/axios #481

magnuf opened this issue Feb 8, 2017 · 12 comments

Comments

@magnuf
Copy link
Contributor

magnuf commented Feb 8, 2017

Hi!

I have finally managed to get adal.js up and running (I think), with reauthenticating after token timeout as well. With a .net backend hosted in azure. This is done in a react environment, using axios for running requests.
As I have spent way too much time on this, I am making this issue so that maybe I can help someone who is struggling as well. I can't find any better place to put it. Also, if you see some errors, it would be greatly appreciated if you pointed them out :)

index.js

import React from "react";
import ReactDOM from "react-dom";
import {Provider} from "react-redux";
import {Router, Route, IndexRoute, browserHistory} from "react-router";
import Layout from "./views/Layout";
import Dashboard from "./views/Dashboard/Dashboard";
import Auth from "./views/Auth/Auth";
import store from "./store/index"
import AuthContext from 'adal-angular';
import axios from "axios";

//This is needed because adal.js does a window.parent.AuthenticationContext() in getRequestInfo
window.AuthenticationContext = AuthContext;

//ClientID is the AzureAD app registration Application ID. This is the one that lists approved reply URLs and keys.
//It has no connection to the backend app, except that the backend app lists the Application ID as the intended audience for the JWT (token) we send.
//Tenant is the AzureAD instance.
//You will need to enable Implicit Grant Flow for the AzureAd Application (The one where you get the ClientId)
//You will also need to add return URLs there, remember to add the auth path

window.authContext = new AuthContext({
  tenant: '<tentantId>',
  clientId: '<clientId>',
  redirectUri: "http://localhost:1337/auth",
  cacheLocation: 'localStorage'
});

//Turn this on for logging
// window.authContext.log = console.log;

axios.defaults.baseURL = 'https://APIURL.azurewebsites.net/api/';

axios.interceptors.response.use(undefined, err => {
    if(err.response.status === 401 && err.response.config && !err.response.config.__isRetryRequest) {
        err.response.config.__isRetryRequest = true;
        return new Promise( (resolve, reject) => {
            window.authContext.acquireToken("<ClientId>", (message, token, msg) => {
                axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
                err.config.headers.Authorization = `Bearer ${token}`;

                axios(err.config).then(resolve, reject);
            });
        });
    }
    throw err;
});

//This can safely be called whenever, as it doesn't crash if it isn't a callback.
//I am not sure why I need this here, but without it, nothing works.
window.authContext.handleWindowCallback();

if(!window.authContext.isCallback(window.location.hash)) {
    //Having both of these checks is to prevent having a token in localstorage, but no user. Relates to issue #471
    if(!window.authContext.getCachedToken('<ClientId>') || !window.authContext.getCachedUser()) {
        window.authContext.login();
    } else {
        window.authContext.acquireToken('<ClientId>', (error, token) => {
            axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;

            ReactDOM.render(
                <Provider store={store}>
                    <Router history={browserHistory}>
                        <Route path="/" component={Layout}>
                            <IndexRoute component={Dashboard}></IndexRoute>
                            <Route path="dashboard" component={Dashboard}></Route>
                            <Route path="auth" component={Auth}></Route>
                        </Route>
                    </Router>
                </Provider>,
                app
            );
        });
    }
}

Auth.js. This is set as the redirectUri in the adal config.

import React from "react";
import AuthContext from 'adal-angular';

export default class Auth extends React.Component {
     componentDidMount() {
        window.authContext.handleWindowCallback();
    }
    render() {
        return ( <div></div> );
    }
}
@candril
Copy link

candril commented Feb 8, 2017

Hint: You should be fetching a token before each HTTP request. Otherwise you could be working with expired tokens

@magnuf
Copy link
Contributor Author

magnuf commented Feb 9, 2017

The interceptor is there to catch any expired tokens. I guess it's perhaps a matter of taste if you want to fetch the token before each request, or wait until you get a 401 to fetch a new one. I first tried with fetching it before each call, but I figured that would lead to a lot of unnecessary fetchings, since it would only need to fetch it once for each time it expires instead.

Working with an expired token isn't a problem in itself, as long as you catch it, and retry with a renewed token, is the way I see it at least :)

Thanks for the feedback!

@tushargupta51
Copy link
Contributor

Thanks @magnuf for sharing the information. This might help people trying to use adal with react.

I will close issue since this is more of an informational post.

@ausmurp
Copy link

ausmurp commented Mar 28, 2017

@magnuf thanks dude this works great even with ADFS.

@FDMatthias
Copy link

thanks for this example! I had to adjust it a little bit to use with apiSauce but it helped a lot :)

@codybushnell
Copy link

@magnuf Thanks so much for this! After 2 weeks of stumbling in the dark this fixed it!

@bikashnk
Copy link

Hi magnuf,

I have to integrate ESO authentication in my application. My application's front-end is in react and back-end is in node.
I was trying understand the use of adal-angular and adal.js for this. Can you please help with the steps that I need to follow the achieve my requirement.

Requirement : when user hits the UI url, it should redirect to ESO url to check the authentication for that user. If the user is authenticated then home page should load in the UI.

Thanks in advance.

@aTable
Copy link

aTable commented Dec 19, 2017

@magnuf thank you, this script has been very helpful in getting AD SSO up and running smoothly. I do have a recommendation:

If you've been blessed to support IE11 / Edge then you'll find that you get a consistent "Invalid Calling Object" error when trying to log in using the above code. After spending all afternoon tearing my hair out I've found the problem to be right here

    //Turn this on for logging
   window.authContext.log = console.log;

Replace it with

   window.authContext.log = function(level, message, error) {
      console.log(level, message, error);
    };

I am not 100% sure why the Microsoft browsers have trouble with setting the function directly as opposed to wrapping it (maybe it's their implementation of console.log and can only take so many params?).

@senser1
Copy link

senser1 commented Jan 31, 2018

Where does renewal of the access token for the React SPA take place with the above setup? If I'm correct, the Axios intercepter renews access tokens for the API backend resource right? I'm having issues with renewing access tokens for the React frontend.

I saw the code to put in index.js but since its a SPA, index gets loaded only once.

Or am I missing something completely here? I'm using a React SPA with OAuth2 Implicit Flow (oauth2AllowImplicitFlow specified in manisfest in AAD) with a REST API backend (clientID of SPA added to knownClientApplications) protected with BearerStrategy.

I'm using two access tokens, one for the React SPA and one for the backend API, is that the way to go?

@salvoravida
Copy link
Contributor

@levalencia
Copy link

This is exactly what I am looking for, my setup is as follows, I bought a react template and I already integrated it with Azure AD Authentication using the above package provided by @salvoravida .

After logging in, however I will need to call REST APIs that will be secured with Azure AD.

I do have a question.

  1. What is redirectUri: "http://localhost:1337/auth", please note my client app will be already authenticated before calling the API, because the client app itself is already registered with AAD

Is there another way to contact you if I have issues, or everything should be done through this issue?

@FDMatthias
Copy link

A bit late, but I've written a blogpost about this here and put a sample of a React SPA with Adal on github here

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests