Can I use ADAL.js in website with MVC architecture

时间:2015-12-10 01:40:20

标签: adal

Currently I am using ADAL.NET in our website which follows MVC architecture. I use session cache for storing the user tokens.

Can I used ADAL.JS instead of ADAL.NET and store the user token in the browser/client to avoid using the session cache?

1 个答案:

答案 0 :(得分:2)

Yes you can. We are currently doing this using the adal js library. We're not using angularjs, so we are using the underlying AuthenticationContext as provided, but have an adapted angularjs client layer for our MVC app

https://github.com/AzureAD/azure-activedirectory-library-for-js/blob/master/README.md

Here is some sample code:

/// <reference path="../lib/adal/index.js" />
'use strict';

import AuthenticationContext  from '../lib/adal/lib/adal';
import unauthorized from './unauthorized';

// endpoints is a map of base urls to the app URI in the AAD Application configuration - e.g. {'https://host/services/api/': 'https://appuri.contoso.com' }
class AuthenticationService {
    constructor(jQuery, webClientId, endpoints, domainHint) {
        var userInfo = { isAuthenticated: false, userName: '', loginError: '', profile: '' };

        var updateDataFromCache = function (adal, resource) {
            // only cache lookup here to not interrupt with events
            var token = adal.getCachedToken(resource);
            userInfo.isAuthenticated = token != null && token.length > 0;
            var user = adal.getCachedUser() || { userName: '' };
            userInfo.userName = user.userName;
            userInfo.profile = user.profile;
            userInfo.loginError = adal.getLoginError();
        };

        var locationChangeHandler = function (adal) {
            var hash = window.location.hash;

            if (adal.isCallback(hash)) {
                // callback can come from login or iframe request

                var requestInfo = adal.getRequestInfo(hash);
                adal.saveTokenFromHash(requestInfo);

                window.location.hash = '';

                if (requestInfo.requestType !== adal.REQUEST_TYPE.LOGIN) {
                    adal.callback = window.parent.AuthenticationContext().callback;
                    if (requestInfo.requestType === adal.REQUEST_TYPE.RENEW_TOKEN) {
                        adal.callback = window.parent.callBackMappedToRenewStates[requestInfo.stateResponse];
                    }
                }

                // Return to callback if it is send from iframe
                if (requestInfo.stateMatch) {
                    if (typeof adal.callback === 'function') {
                        // Call within the same context without full page redirect keeps the callback
                        if (requestInfo.requestType === adal.REQUEST_TYPE.RENEW_TOKEN) {
                            // Idtoken or Accestoken can be renewed
                            if (requestInfo.parameters['access_token']) {
                                adal.callback(adal._getItem(adal.CONSTANTS.STORAGE.ERROR_DESCRIPTION), requestInfo.parameters['access_token']);
                            } else if (requestInfo.parameters['id_token']) {
                                adal.callback(adal._getItem(adal.CONSTANTS.STORAGE.ERROR_DESCRIPTION), requestInfo.parameters['id_token']);
                            }else {
                                adal.callback(adal._getItem(adal.CONSTANTS.STORAGE.ERROR_DESCRIPTION), requestInfo.parameters.error);
                            }
                        }
                    } else {
                        // normal full login redirect happened on the page
                        updateDataFromCache(adal, adal.config.loginResource);
                        if (userInfo.userName) {
                            //IDtoken is added as token for the app
                            adal._logstatus('adal:loginSuccess');
                        } else {
                            adal._logstatus('adal:loginFailure', adal._getItem(adal.CONSTANTS.STORAGE.ERROR_DESCRIPTION));
                        }
                    }
                }
            } else {
                // No callback. App resumes after closing or moving to new page.
                // Check token and username             
                updateDataFromCache(adal, adal.config.loginResource);
                if (!adal._renewActive && !userInfo.isAuthenticated && userInfo.userName) {
                    if (!adal._getItem(adal.CONSTANTS.STORAGE.FAILED_RENEW)) {
                        // Idtoken is expired or not present
                        adal.acquireToken(adal.config.loginResource, function (error, tokenOut) {
                            if (error) {
                                adal._logstatus('adal:loginFailure', 'auto renew failure');
                            } else {
                                if (tokenOut) {
                                    userInfo.isAuthenticated = true;
                                }
                            }
                        });
                    }
                }
            }

            updateDataFromCache(adal, adal.config.loginResource);
            return userInfo.isAuthenticated;
        };

        var loginHandler = function (adal) {
            adal._logstatus('Login event for:' + window.location);
            if (adal.config && adal.config.localLoginUrl) {
                window.location.pathname = adal.config.localLoginUrl;
            } else {
                // directly start login flow
                adal._saveItem(adal.CONSTANTS.STORAGE.START_PAGE, window.location.pathname);
                adal._logstatus('Start login at:' + window.location.href);                
                adal.login();
            }
        };

        var createAdal = function() {
            // make sure adal is singleton - get from window/store on window
            if (window.parent.AuthenticationContext) return window.parent.AuthenticationContext();

            var currentUrl = window.location.origin;

            var adal = AuthenticationContext.inject({
                clientId: clientId,
                cacheLocation: 'localStorage',
                endpoints: endpoints,
                // make sure to exclude any hash (#) since MS validates on a redirectUri match and will fail even if there is an empty trailing #1

                redirectUri: window.location.protocol
                    + "//" + window.location.hostname
                    + (window.location.port === 80 || window.location.port === 443 ? '' : ':' + (window.location.port))
                    + window.location.pathname
            });

            var promptUser = adal.promptUser;
            adal.promptUser = function() {
                if (arguments[0].indexOf('domain_hint') == -1) arguments[0] += '&domain_hint=' + domainHint;
                if (adal.useFormsAuth) arguments[0] += '&prompt=login';
                return promptUser.apply(adal, arguments);
            }

            // hack to simulate "global scope" of ADAL when not imported as module - this is necessary because of how ADAL deals with iframes
            window.parent.AuthenticationContext = adal.__proto__.constructor;

            return adal;
        }

        var acquireToken = function(adal, resource) {
            return new Promise(function(resolve, reject) {
                // automated token request call
                adal.acquireToken(resource, function(error, tokenOut) {
                    if (error) {
                        adal._logstatus('err :' + error);                       
                        adal.clearCacheForResource(resource);
                    }

                    if (typeof error === 'string' && error.indexOf('AADSTS65001') == 0) { // interaction required
                        var urlNavigate = adal._getNavigateUrl('token', resource) + '&login_hint=' + encodeURIComponent(adal._user.userName);
                        window.location.href = urlNavigate;
                        return;
                    } else if (error) {
                        reject(error);
                    } else {
                        resolve(tokenOut);
                    }
                });
            });
        };

        var configureJQuery = function(adal) {
            if (window.parent.hasConfiguredJQueryAuth) {
                return;
            }

            window.parent.hasConfiguredJQueryAuth = true;

            jQuery(window).on('hashchange', function() { locationChangeHandler(adal); });

            // intercept ajax method as there is no way of deferring a call from beforeSend
            var ajax = jQuery.ajax;

            adal._logstatus('Performing jQuery configuration for auth');

            jQuery.ajax = function() {
                var deferred = jQuery.Deferred();

                function invoke(call) {
                    call
                        .fail(function() { deferred.reject.apply(deferred, arguments); })
                        .done(function() { deferred.resolve.apply(deferred, arguments); });
                }

                var args = arguments;

                var options = args[0];

                if (!options) {
                    return ajax.apply(jQuery, args);
                }

                if (!options.headers) options.headers = {};

                // get resource
                var resource = adal.getResourceForEndpoint(options.url);
                if (resource === adal.config.loginResource) {
                    // try again for absolute url
                    resource = adal.getResourceForEndpoint(document.location.origin + options.url);
                }

                if (resource === adal.config.loginResource) {
                    // no auth required
                    invoke(ajax.apply(jQuery, args));
                    return deferred.promise();
                }

                var tokenStored = adal.getCachedToken(resource);
                if (tokenStored) {
                    // got from cache
                    options.headers['Authorization'] = 'Bearer ' + tokenStored;
                    invoke(ajax.apply(jQuery, args));
                    return deferred.promise();
                }

                // new token required
                acquireToken(adal, resource).then(function fulfilled(tokenOut) {
                    options.headers['Authorization'] = 'Bearer ' + tokenOut;
                    invoke(ajax.apply(jQuery, args));
                }, function rejected(error) {
                    deferred.reject(error);
                });

                return deferred.promise();                
            };

            jQuery(document).ajaxError(function(event, jqxhr, options) {
                if (jqxhr.status === 401) {
                    var resource = adal.getResourceForEndpoint(options.url);
                    if (resource === adal.config.loginResource) {
                        resource = adal.getResourceForEndpoint(document.location.origin + options.url);
                    }

                    adal.clearCacheForResource(resource);

                    unauthorized.show();
                }
            });
        };

        // set up adal and jquery
        var adal = createAdal();
        configureJQuery(adal);

        this.userInfo = userInfo;

        this.config = adal.config;

        this.login = function() {
            adal.login();
        };

        this.loginInProgress = function() {
            return adal.loginInProgress();
        };

        this.getCachedToken = function(resource) {
            return adal.getCachedToken(resource);
        };

        this.acquireToken = function(resource) { return acquireToken(adal, resource); };

        this.getUser = function() {
            return new Promise((resolve, reject) => {
                adal.getUser(function(error, user) {
                    if (error) {
                        adal._logstatus('err :' + error);
                        reject(error);
                    } else {
                        resolve(user);
                    }
                });
            });   
        };

        this.getResourceForEndpoint = function(endpoint) {
            return adal.getResourceForEndpoint(endpoint);
        };

        this.clearCache = function() {
            adal.clearCache();
        };

        this.clearCacheForResource = function(resource) {
            adal.clearCacheForResource(resource);
        };

        this.loginAsADifferentUser = function() {
            adal.useFormsAuth = true;
            this.clearCache();
            this.login();
        }

        this.init = function() {
            locationChangeHandler(adal);
            if (userInfo.isAuthenticated) {
                return Promise.resolve();
            }

            loginHandler(adal);            
            return new Promise(function() {}); // wait for redirect
        };

    }
}
export default AuthenticationService;

Import AuthenticationService and call on load or when you want to authenticate

   import AuthenticationService from './auth'
    new AuthenticationService($)init.then(() => {
            if (!authService.userInfo.isAuthenticated) {
                if (!authService.loginInProgress()) {
                    unauthorized.show();
                }
            } else {
                // succcess and userInfo available at authService.userInfo
            });

From entry point handle callbacks in iframes differently:

$(document).ready(() => {
    if (window === window.parent) {
        // do regular init stuff
    } else {
        // callback from iframe for auth - just init auth so callbacks are processed
        authService.init();
    }
});