New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Working example with react/axios #481

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

Comments

Projects
None yet
@magnuf
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

This comment has been minimized.

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

This comment has been minimized.

Contributor

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

This comment has been minimized.

Contributor

tushargupta51 commented Feb 10, 2017

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

This comment has been minimized.

ausmurp commented Mar 28, 2017

@magnuf thanks dude this works great even with ADFS.

@CauseOfDev

This comment has been minimized.

CauseOfDev commented Jun 27, 2017

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

@codybushnell

This comment has been minimized.

codybushnell commented Jul 7, 2017

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

@bikashnk

This comment has been minimized.

bikashnk commented Sep 19, 2017

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

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

Contributor

salvoravida commented Apr 25, 2018

@levalencia

This comment has been minimized.

levalencia commented May 11, 2018

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?

@levalencia levalencia referenced this issue May 14, 2018

Closed

Question #26

@CauseOfDev

This comment has been minimized.

CauseOfDev commented Nov 26, 2018

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

@aramiz84 aramiz84 referenced this issue Dec 13, 2018

Open

Component - Login component #25

2 of 4 tasks complete
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment