Azure · CDS Single Page Application · Customization and Configuration · Dynamics CRM Javascript · Javascript

Dynamics CRM Create Single Page Application using ADAL.JS to connect to CDS

We can create a Single Page Apps (SPAs) which uses JavaScript to work with Common Data Service data. To provide this, Cross-Origin Resource Sharing (CORS) is enabled so that your SPAs can bypass browser restrictions that normally prevent requests that cross domain boundaries.

you can use the Azure Active Directory Authentication Library for JavaScript (adal.js) and it will take care of much of the CORS complexity for you. Since Common Data Service are authenticated using Azure Active Directory, ADAL.js is the supported way to authenticate SPA users.

Step – 1 Create new Empty Web Application in Visua Studio.

Step – 2 Add a new HTML page named SimpleSPA.html to the project and paste in the following code:

<!DOCTYPE html>
<html>
<head>
    <title>Simple SPA</title>
    <meta charset="utf-8" />
    <script src="https://secure.aadcdn.microsoftonline-p.com/lib/1.0.17/js/adal.min.js"></script>
    <script type="text/javascript">

        "use strict";

        //Set these variables to match your environment
        var organizationURI = "https://infylearninfy.crm.dynamics.com"; //The URL of your Common Data Service organization
        var tenant = "f629b38b-d95b-4d46-81d7-192aa10400ae"; //The name of the Azure AD organization you use
        var clientId = "2370bdb3-742b-40f4-998e-f0f9fc34a8c5"; //The ClientId you got when you registered the application
        var pageUrl = "https://localhost:44396/SimpleSPA.html"; //The URL of this page in your development environment when debugging.

        var user, authContext, message, errorMessage, loginButton, logoutButton, getAccountsButton, accountsTable, accountsTableBody;

        //Configuration data for AuthenticationContext
        var endpoints = {
            orgUri: organizationURI
        };

        window.config = {
            tenant: tenant,
            clientId: clientId,
            postLogoutRedirectUri: pageUrl,
            endpoints: endpoints,
            cacheLocation: 'localStorage',
        };

        document.onreadystatechange = function () {
            if (document.readyState == "complete") {

                //Set DOM elements referenced by scripts
                message = document.getElementById("message");
                errorMessage = document.getElementById("errorMessage");
                loginButton = document.getElementById("login");
                logoutButton = document.getElementById("logout");
                getAccountsButton = document.getElementById("getAccounts");
                accountsTable = document.getElementById("accountsTable");
                accountsTableBody = document.getElementById("accountsTableBody");

                //Event handlers on DOM elements
                loginButton.addEventListener("click", login);
                logoutButton.addEventListener("click", logout);
                getAccountsButton.addEventListener("click", getAccounts);

                //call authentication function
                authenticate();

                if (user) {
                    loginButton.style.display = "none";
                    logoutButton.style.display = "block";
                    getAccountsButton.style.display = "block";

                    var helloMessage = document.createElement("p");
                    helloMessage.textContent = "Hello " + user.profile.name;
                    message.appendChild(helloMessage)

                }
                else {
                    loginButton.style.display = "block";
                    logoutButton.style.display = "none";
                    getAccountsButton.style.display = "none";
                }

            }
        }

        // Function that manages authentication
        function authenticate() {
            //OAuth context
            authContext = new AuthenticationContext(config);

            // Check For & Handle Redirect From AAD After Login
            var isCallback = authContext.isCallback(window.location.hash);
            if (isCallback) {
                authContext.handleWindowCallback();
            }
            var loginError = authContext.getLoginError();

            if (isCallback && !loginError) {
                window.location = authContext._getItem(authContext.CONSTANTS.STORAGE.LOGIN_REQUEST);
            }
            else {
                errorMessage.textContent = loginError;
            }
            user = authContext.getCachedUser();

        }

        //function that logs in the user
        function login() {
            authContext.login();
        }
        //function that logs out the user
        function logout() {
            authContext.logOut();
            accountsTable.style.display = "none";
            accountsTableBody.innerHTML = "";
        }

        //function that initiates retrieval of accounts
        function getAccounts() {

            getAccountsButton.disabled = true;
            var retrievingAccountsMessage = document.createElement("p");
            retrievingAccountsMessage.textContent = "Retrieving 10 accounts from " + organizationURI + "/api/data/v9.1/accounts";
            message.appendChild(retrievingAccountsMessage)

            // Function to perform operation is passed as a parameter to the acquireToken method
            authContext.acquireToken(organizationURI, retrieveAccounts)

        }

        //Function that actually retrieves the accounts
        function retrieveAccounts(error, token) {
            // Handle ADAL Errors.
            if (error || !token) {
                errorMessage.textContent = 'ADAL error occurred: ' + error;
                return;
            }

            var req = new XMLHttpRequest()
            req.open("GET", encodeURI(organizationURI + "/api/data/v9.1/accounts?$select=name,address1_city&$top=10"), true);
            //Set Bearer token
            req.setRequestHeader("Authorization", "Bearer " + token);
            req.setRequestHeader("Accept", "application/json");
            req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
            req.setRequestHeader("OData-MaxVersion", "4.0");
            req.setRequestHeader("OData-Version", "4.0");
            req.onreadystatechange = function () {
                if (this.readyState == 4 /* complete */) {
                    req.onreadystatechange = null;
                    if (this.status == 200) {
                        var accounts = JSON.parse(this.response).value;
                        renderAccounts(accounts);
                    }
                    else {
                        var error = JSON.parse(this.response).error;
                        console.log(error.message);
                        errorMessage.textContent = error.message;
                    }
                }
            };
            req.send();
        }
        //Function that writes account data to the accountsTable
        function renderAccounts(accounts) {
            accounts.forEach(function (account) {
                var name = account.name;
                var city = account.address1_city;
                var nameCell = document.createElement("td");
                nameCell.textContent = name;
                var cityCell = document.createElement("td");
                cityCell.textContent = city;
                var row = document.createElement("tr");
                row.appendChild(nameCell);
                row.appendChild(cityCell);
                accountsTableBody.appendChild(row);
            });
            accountsTable.style.display = "block";
        }

    </script>
    <style>
        body {
            font-family: 'Segoe UI';
        }

        table {
            border-collapse: collapse;
        }

        td, th {
            border: 1px solid black;
        }

        #errorMessage {
            color: red;
        }

        #message {
            color: green;
        }
    </style>
</head>
<body>
    <button id="login">Login</button>
    <button id="logout" style="display:none;">Logout</button>
    <button id="getAccounts" style="display:none;">Get Accounts</button>
    <div id="errorMessage"></div>
    <div id="message"></div>
    <table id="accountsTable" style="display:none;">
        <thead><tr><th>Name</th><th>City</th></tr></thead>
        <tbody id="accountsTableBody"></tbody>
    </table>
</body>
</html>

Step – 3 Register Application in Azure AD. Follow steps defined here.

Redirect URL
This is the URL which the user should be redirected to after they sign in. Select Web from the drop down list. For debugging purposes in Visual Studio it should be https://localhost:####/SimpleSPA.html where #### represents the port number

Step – 4 Replace Placeholder value with your Application and CRM instance details

//Set these variables to match your environment
var organizationURI = “https://infylearninfy.crm.dynamics.com&#8221;; //The URL of your Common Data Service organization
var tenant = “f629b38b-d95b-4d46-81d7-192aa10400ae”; //The name of the Azure AD organization you use
var clientId = “2370bdb3-742b-40f4-998e-f0f9fc34a8c5”; //The ClientId you got when you registered the application
var pageUrl = “https://localhost:44396/SimpleSPA.html&#8221;; //The URL of this page in your development environment when debugging.

Step – 5 Enable ID tokens in Azure AD App Registration Authentication

Step – 6 Run Your application and once you login, You will see Top 10 Accounts

Hope this helps!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s