Search code examples
angularsingle-page-applicationadal

ADAL.js: How to force user to re-enter password after token expiry


I've got a single page application using adal-angular (https://github.com/AzureAD/azure-activedirectory-library-for-js) for secure access. Whenever the token expires (after 1 hour), I want to redirect the user to the AD login screen, to re-enter their password. I've modified adal-angular.js to call _adal.login() whenever the token has expired, however this appears to automatically re-authenticate the user and take them directly to the redirect URI with the renewed token. I'm not sure what it is that is cached, that allows the renewal automatically.

How can I force the user to re-enter their password whenever the token expires?


Solution

  • AFAIK, the adal.js acquire the token using the implicit flow in a iframe when the token is expired. It doesn't support to force the user sign-in again.

    To implement this feature, you can modify the source code. Here is an code sample modifying the acquireToken and login method in the adal.js for your reference:

    AuthenticationContext.prototype.acquireToken = function (resource, callback) {
        if (this._isEmpty(resource)) {
            this.warn('resource is required');
            callback('resource is required', null, 'resource is required');
            return;
        }
    
        var token = this.getCachedToken(resource);
        if (token) {
            this.info('Token is already in cache for resource:' + resource);
            callback(null, token, null);
            return;
        }
        //add by me-begin
        var expiry = this._getItem(this.CONSTANTS.STORAGE.EXPIRATION_KEY + resource);
    
        var isExpired = true;
        // If expiration is within offset, it will force renew
        var offset = this.config.expireOffsetSeconds || 300;
    
        if (expiry && (expiry > this._now() + offset)) {
            isExpired = false;
        }
    
        //add by me-end
        if (!this._user) {
            this.warn('User login is required');
            callback('User login is required', null, 'login required');
            return;
        }
        //add by me-begin
        else if (isExpired) {
            this.login(null,"prompt=login");
        }
        //add by me-end
        // refresh attept with iframe
        //Already renewing for this resource, callback when we get the token.
        if (this._activeRenewals[resource]) {
            //Active renewals contains the state for each renewal.
            this.registerCallback(this._activeRenewals[resource], resource, callback);
        }
        else {
            if (resource === this.config.clientId) {
                // App uses idtoken to send to api endpoints
                // Default resource is tracked as clientid to store this token
                this.verbose('renewing idtoken');
                this._renewIdToken(callback);
            } else {
                this._renewToken(resource, callback);
            }
        }
    };
    
    //add the extraParameters to force user sign-in
    AuthenticationContext.prototype.login = function (loginStartPage,extraParameters) {
        // Token is not present and user needs to login
        if (this._loginInProgress) {
            this.info("Login in progress");
            return;
        }
        var expectedState = this._guid();
        this.config.state = expectedState;
        this._idTokenNonce = this._guid();
        this.verbose('Expected state: ' + expectedState + ' startPage:' + window.location.href);
        this._saveItem(this.CONSTANTS.STORAGE.LOGIN_REQUEST, loginStartPage || window.location.href);
        this._saveItem(this.CONSTANTS.STORAGE.LOGIN_ERROR, '');
        this._saveItem(this.CONSTANTS.STORAGE.STATE_LOGIN, expectedState);
        this._saveItem(this.CONSTANTS.STORAGE.NONCE_IDTOKEN, this._idTokenNonce);
        this._saveItem(this.CONSTANTS.STORAGE.ERROR, '');
        this._saveItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION, '');
        var urlNavigate = this._getNavigateUrl('id_token', null) + '&nonce=' + encodeURIComponent(this._idTokenNonce);
        urlNavigate = urlNavigate + "&"+extraParameters;//add by me
        this._loginInProgress = true;
        if (this.config.displayCall) {
            // User defined way of handling the navigation
            this.config.displayCall(urlNavigate);
        }
        else if (this.popUp) {
            this._loginPopup(urlNavigate);
        }
       else {
            this.promptUser(urlNavigate);
        }
    };
    

    If you have any idea or feedback about this library you can rise a new issue from here.