OAuth Implicit Flow in JS without ADAL.js

This post provides a lightweight implementation of the OAuth implicit flow grant for obtaining an access token. Implicit flow is appropriate when the current user is authenticated to a common identity provider (e.g. Azure Active Directory a.k.a AAD) and the client (the environment requesting the token) is not secure. A great example of this is making a call to the Microsoft Graph from a page in SharePoint Online using only JavaScript.

The ADAL.js library exists as a solution to all five of the OAuth grants specifically when working against AAD as the identity provider. Unfortunately, it is currently not well maintained and is over complicated. From a user experience perspective, the implementation discussed in this post avoids the need to redirect in order to authenticate. It happens seamlessly in the background via a hidden iframe.

Azure Active Directory
Azure Active Directory
  • A great article on the OAuth grants, agnostic of implementation, can be found here.
  • Thanks to my colleague Paul Lawrence for writing the first iteration of this code.
  • This code has a dependency on jQuery, mostly just for promises. I know, old school. I expect I’ll write an es6/2016 version of this soon enough but it shouldn’t be a challenge to convert this code yourself.
  • As I know I’ll get comments about it if I don’t mention it, this code doesn’t send and verify a state token as part of the grant flow. This is optional as far as the OAuth specification is concerned but it should be done as an additional security measure.
  • Although I’m Microsoft stack developer and have only tested this with AAD as the identity provider, I believe that it should work for any identify provider that adheres to the OAuth specification for authentication. You would need to play around with the authorisation server URL as login.microsoftonline.com is specifically for authenticating to AAD. I’d love feedback on this.
  • By definition, the OAuth implicit flow grant does not return a refresh token. Furthermore, the access token has a short lifetime, an hour I believe, and credentials must be re-entered before additional access tokens can be obtained via the implicit flow grant. The code provided in this post handles this by returning a URL which can be used to re-authenticate when a request fails. This URL can be used behind a link or redirection could be forced to occur automatically.

The following code snippet is an example of using this implicit flow library to call into the Microsoft Graph from within the context of a SharePoint Online page.
You will need to provide an appropriate AAD app ID for your AAD app. And don’t forget that you need to enable implicit flow via the app manifest and associate the correct delegate permissions.
This code should work not only with the Microsoft Graph but also to SharePoint Online endpoints, other AAD secured resources such as Azure services or your own AAD secured and CORS enabled web API.
[See note above about identity providers other than AAD]

var aadAppClientId = "8BE5AA0E-F900-4BDF-A7CF-71B3CC53B78E";
var resource = "https://graph.microsoft.com"
var query = "/v1.0/me/events";
var tokenFactory = new CC.CORE.Adal.AppTokenFactory(aadAppClientId, resource);
.done(function (response) {
	// Success!
.fail(function (response) {
	// NOTE: Provide a link to renew an expired or yet to be approved session:
	// "Sorry, your session has expired or requires your approval. 
	// <div><a href='" + response.authorizeUrl + "'>Click here to sign in</a></div>";

Here is the implicit flow library code itself.

var CC = CC || {};
CC.CORE = CC.CORE || {};

CC.CORE.Log = function (errMsg) {
    // console.log is undefined in IE10 and earlier unless in debug mode, so must check for it
    if (typeof window.console === "object" && typeof console.log === "function") {

CC.CORE.Adal = (function () {
    "use strict";

    var appTokenFactory = function (aadAppClientId, resource) {
        // redirectUrl is the URL which the iframe will redirect to once auth occurs.
        // we use blank.gif as it is a very low payload
        var redirectUrl = _spPageContextInfo.webAbsoluteUrl + "/_layouts/images/blank.gif";

        // NOTE on security: include the userId in the cache key to prevent the case where a user logs out but
        // leaves the tab open and a new user logs in on the same tab. The first user's calender
        // would be returned if we didn't associate the cache key with the current user.
        var cacheKey = "candc_cache_adal_" + _spPageContextInfo.userId + "_" + aadAppClientId + "_" + resource;

        this.params = {
            clientId: aadAppClientId,
            redirectUrl: redirectUrl,
            resource: resource,
            cacheKey: cacheKey

        var getAuthorizeUri = function (params, redirectUrl) {
            var authUri = "https://login.microsoftonline.com/common/oauth2/authorize" +
                            "?client_id=" + params.clientId +
                            "&response_type=token" +
                            "&redirect_uri=" + encodeURIComponent(redirectUrl) +
                            "&resource=" + encodeURIComponent(params.resource);
            return authUri;

		var getQueryStringParameterByName = function (name, url) {
			name = name.replace(/[\[\]]/g, "\\$&");
			var regex = new RegExp("[?&#]" + name + "(=([^&#]*)|&|#|$)");
			var results = regex.exec(url);
			if (!results) return null;
			if (!results[2]) return '';
			return decodeURIComponent(results[2].replace(/\+/g, " "));
        // create iframe, set its href, set listener for when loaded
        // to parse the query string. Deferred returns upon parse of query string in iframe.
        var acquirePassiveToken = function (params) {
            var deferred = jQuery.Deferred();

            // create iframe and inject into dom
            var iframe = jQuery("<iframe />").attr({
                width: 1,
                height: 1,
                src: getAuthorizeUri(params, params.redirectUrl)

            // bind event handler to iframe for parse query string on load
            iframe.on("load", function (iframeData) {
                parseAccessTokenFromIframe(iframeData, deferred);

            return deferred.promise();

        // handle iframe once it has loaded
        var parseAccessTokenFromIframe = function (iframeData, deferred) {
            // read the iframe href
            var frameHref = "";
            try {
                // this will throw a cross-domain error for any issue other than success
                // as the iframe will diplay the error on the login.microsoft domain
                frameHref = iframeData.currentTarget.contentWindow.location.href;
            catch (error) {

            // parse iframe query string parameters
            var accessToken = getQueryStringParameterByName("access_token", frameHref);
            var expiresInSeconds = getQueryStringParameterByName("expires_in", frameHref);

            // delete the iframe, and event handler.
            var iframe = jQuery(iframeData.currentTarget);

            // resolve promise
                accessToken: accessToken,
                expiresInSeconds: expiresInSeconds

        // get the most recent token from the cache, or if not available,
        // fetch a new token via iframe
        var getToken = function (params) {
            var deferred = jQuery.Deferred();
            // check for cached token
            var tokenFromCache = CC.CORE.Cache.Get(params.cacheKey);
            if (!tokenFromCache) {
                // fetch token via iframe
                .done(function (tokenFromIframe) {
                    CC.CORE.Log("ADAL: Fetched token from iframe.");
                    // expire cache a minute before token expires to be safe
                    var cacheTimeout = (tokenFromIframe.expiresInSeconds - 60) * 1000;
                    CC.CORE.Cache.Set(params.cacheKey, tokenFromIframe, cacheTimeout);
                    // resolve the promise
                .fail(function (error) {
                    // Logs when rejection is caught
            else {
                CC.CORE.Log("ADAL: Fetched token from cache.");
                // resolve the promise
            return deferred.promise();

        this.ExecuteQuery = function (query, additionalHeaders) {
            var deferred = jQuery.Deferred();
            var params = this.params;
            // get token from cache or via iframe
            .done(function (token) {
                // submit request with token in header
                var ajaxHeaders = {
                    'Authorization': 'Bearer ' + token.accessToken
                if (typeof additionalHeaders === "object") {
                    jQuery.extend(ajaxHeaders, additionalHeaders);
                    type: "GET",
                    url: params.resource + query,
                    headers: ajaxHeaders
                }).done(function (response) {
                }).fail(function (error) {
                        error: error
            .fail(function (error) {
                CC.CORE.Log('ADAL error occurred: ' + error);
                    error: error,
                    authorizeUrl: getAuthorizeUri(params, window.location.href)
            return deferred.promise();

    return {
        AppTokenFactory: appTokenFactory


And here is the definition of the cache functions used above. Nothing special here, this could be swapped out with any cache implementation or removed altogether if caching is truly unnecessary or a security concern.

var CC = CC || {};
CC.CORE = CC.CORE || {};

CC.CORE.Cache = (function () {
    var defaultCacheExpiry = 15 * 60 * 1000; // default is 15 minutes
	var aMinuteInMs = (1000 * 60);
	var anHourInMs = aMinuteInMs * 60;
    var getCacheObject = function () {
        // Using session storage rather than local storage as caching benefit
        // is minimal so would rather have an easy way to reset it.
        return window.sessionStorage;

    var isSupportStorage = function () {
        var cacheObj = getCacheObject();
        var supportsStorage = cacheObj && JSON && typeof JSON.parse === "function" && typeof JSON.stringify === "function";
        if (supportsStorage) {
            // Check for dodgy behaviour from iOS Safari in private browsing mode
            try {
                var testKey = "candc-cache-isSupportStorage-testKey";
                cacheObj[testKey] = "1";
                return true;
            catch (ex) {
                // Private browsing mode in iOS Safari, or possible full cache
        CC.CORE.Log("Browser does not support caching");
        return false;

    var getExpiryKey = function (key) {
        return key + "_expiry";

    var isCacheExpired = function (key) {
        var cacheExpiryString = getCacheObject()[getExpiryKey(key)];
        if (typeof cacheExpiryString === "string" && cacheExpiryString.length > 0) {
            var cacheExpiryInt = parseInt(cacheExpiryString);
            if (cacheExpiryInt > (new Date()).getTime()) {
                return false;
        return true;

    var get = function (key) {
        if (isSupportStorage()) {
            if (!isCacheExpired(key)) {
                var valueString = getCacheObject()[key];
                if (typeof valueString === "string") {
                    CC.CORE.Log("Got from cache at key: " + key);
                    if (valueString.indexOf("{") === 0 || valueString.indexOf("[") === 0) {
                        var valueObj = JSON.parse(valueString);
                        return valueObj;
                    else {
                        return valueString;
            else {
                // remove expired entries?
                // not required as we will almost always be refreshing the cache
                // at this time
        return null;

    var set = function (key, valueObj, validityPeriodMs) {
        var didSetInCache = false;
        if (isSupportStorage()) {
            // Get value as a string
            var cacheValue = undefined;
            if (valueObj === null || valueObj === undefined) {
                cacheValue = null;
            else if (typeof valueObj === "object") {
                cacheValue = JSON.stringify(valueObj);
            else if (typeof valueObj.toString === "function") {
                cacheValue = valueObj.toString();
            else {
                alert("Cannot cache type: " + typeof valueObj);

            // Cache value if it is valid
            if (cacheValue !== undefined) {
                // Cache value
                getCacheObject()[key] = cacheValue;
                // Ensure valid expiry period
                if (typeof validityPeriodMs !== "number" || validityPeriodMs < 1) {
                    validityPeriodMs = defaultCacheExpiry;
                // Cache expiry
                getCacheObject()[getExpiryKey(key)] = ((new Date()).getTime() + validityPeriodMs).toString();
                CC.CORE.Log("Set in cache at key: " + key);
                didSetInCache = true;
        return didSetInCache;

    var clear = function (key) {
        var cache = getCacheObject();

    return {
        Get: get,
        Set: set,
        Clear: clear,
        IsSupportStorage: isSupportStorage,
        Timeout: {
            VeryShort: (aMinuteInMs * 1),
            Default: (anHourInMs * 2),
            VeryLong: (anHourInMs * 72),

I welcome your comments, especially from anyone who gives this a go outside of Office 365 and the Microsoft stack.


One thought on “OAuth Implicit Flow in JS without ADAL.js”

  1. Hi,

    First of all let me congratulate you for this excellent work.
    From my tests, it all works great, but a comment should be concerning the value referred in “redirectUrl”:

    var redirectUrl = _spPageContextInfo.webAbsoluteUrl + “/_layouts/images/blank.gif”;

    This works, but one also needs to register this very same URL in the AAD app as “Reply URL”, otherwise it will give an exception stating that at least one Reply URL must be configured. At least to me this aspect wasn’t clear. All of the rest seems to work just fine.

    Thank you!


Leave a Reply

Your email address will not be published.