10.10 Example code listing

The following is the full listing of the final example in this chapter.

To use this on your own system, you must edit the following:

Copy
// Address of the MyID server
const server = "https://myserver";
// The URL for this page - this must match a value for RedirectUris 
// in the web.oauth2 appsettings file.
const redirect_uri = "http://127.0.0.1:5500/";
Copy
<html>
<head>
    <title>Single-page PKCE client authentication through Javascript</title>
</head>
<body>
    <div id="intro">
        <p>This single-page example uses client authentication to obtain an 
        access token and call the API.</p>
    </div>  
    <div id="login">
        <p>Click <b>Login</b> to authenticate to the MyID server using 
        your client credentials.</p>
    </div>
    <script>
        // Address of the MyID server
        const server = "https://myserver";
        // Name of the client ID you have set up in the web.oauth2 appsettings file
        const client_id = "myid.mywebsite";
        // Scope configured for the client
        const scope = "myid.rest.basic";
        // The URL for this page - this must match a value for RedirectUris 
        // in the web.oauth2 appsettings file.
        const redirect_uri = "http://127.0.0.1:5500/";
        // MyID oauth2 authorization URL
        const authorize_url = "/web.oauth2/connect/authorize";
        // MyID oauth2 token URL
        const token_url = "/web.oauth2/connect/token";
        
        // Set up the HTTP requests
        // The request object is used to obtain an access token
        var request = new XMLHttpRequest();
        // The call_api object is used to call the API
        var call_api = new XMLHttpRequest();
        // The display_person object is used to get info about a person
        var display_person = new XMLHttpRequest();

        // Get the authorization code from the URL parameters. 
        const queryString = window.location.search;
        const urlParams = new URLSearchParams(queryString);
        const code = urlParams.get('code');

        // If the code is available, use it to obtain an access token
        if (code) {
            getAccessToken();
            }
        else {
            // Create the form
            createForm();
        }

        function getAccessToken() {
            // Use the authorization code to obtain an access code
            // This retrieves the code verifier from the browser's session storage
            request.open("POST", server + token_url, true);
            request.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
            request.send('client_id=' + client_id + 
                // Obtain the code_verifier from the session storage
                '&code_verifier=' + window.sessionStorage.getItem("code_verifier") + 
                '&grant_type=authorization_code&redirect_uri=' + redirect_uri + 
                '&code=' + code);
            document.getElementById("login").innerHTML="Working...";
        }

        // When the access token is returned, obtain it from the response.
        request.onload = function() {
            let response = JSON.parse(request.responseText);
            let access_token = response.access_token;
            window.sessionStorage.setItem("access_token", access_token);
            getPeople();
        }

        function createForm() {
            // Create the form
            var form = document.createElement("form");
            form.setAttribute("method", "post");
            form.setAttribute("enctype", "application/x-www-form-urlencoded");
            form.setAttribute("action", server + authorize_url);

            // Create the hidden fields
            var client_id_element = createFormField("hidden", "client_id", client_id);
            var scope_element = createFormField("hidden", "scope", scope);
            var redirect_uri_element = createFormField("hidden", "redirect_uri", redirect_uri);
            var response_type_element = createFormField("hidden", "response_type", "code");
            var code_challenge_element = createChallengeField();
            var code_challenge_method_element = createFormField("hidden", "code_challenge_method", "S256");

            // Create a Login button
            var submit_button = document.createElement("input");
            submit_button.setAttribute("type", "submit");
            submit_button.setAttribute("value", "Login");

            // Append the fields to the form
            form.appendChild(client_id_element);
            form.appendChild(scope_element);
            form.appendChild(redirect_uri_element);
            form.appendChild(response_type_element);
            form.appendChild(code_challenge_element);
            form.appendChild(code_challenge_method_element);

            // Append the Login button to the form
            form.appendChild(submit_button); 
                        
            // Add the form to the login div
            document.getElementById("login").appendChild(form);
        }

        function createFormField(type, name, value) {
            var formFieldElement = document.createElement("input");
            formFieldElement.setAttribute("type", type);
            formFieldElement.setAttribute("name", name);
            formFieldElement.setAttribute("value", value);
            return formFieldElement;
        }

        // Create a function to generate a random code verifier string
        function generateRandomString(length) {
                var text = "";
                var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
                for (var i = 0; i < length; i++) {
                    text += possible.charAt(Math.floor(Math.random() * possible.length));
                }
                return text;
        }

        // Create an asynchronous function to generate a code challenge from the code verifier
        async function generateCodeChallenge(code_verifier) {
            var digest = await crypto.subtle.digest("SHA-256",
            new TextEncoder().encode(code_verifier));
            return btoa(String.fromCharCode(...new Uint8Array(digest)))
                .replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_')
        }

        function createChallengeField() {
            // Create the code challenge element for the form
            var code_challenge_element = document.createElement("input");
            code_challenge_element.setAttribute("type", "hidden");
            code_challenge_element.setAttribute("name", "code_challenge");

            // Create a random string to use as the code verifier
            var code_verifier = generateRandomString(128);
            // Store the code verifier to use later
            window.sessionStorage.setItem("code_verifier", code_verifier);

            // Generate the code challenge from the asyncronous function
            (async () => {
                const code_challenge = await generateCodeChallenge(code_verifier);
                code_challenge_element.setAttribute("value", code_challenge);
            })();

            // Return the code challenge form element
            return code_challenge_element;
        }

        function getPeople() {
            call_api.open("GET", server + "/rest.core/api/People?q=*", true);
            call_api.setRequestHeader('Authorization', "Bearer " + window.sessionStorage.getItem("access_token"));
            call_api.send();
        }

        call_api.onload = function() {
            let response = JSON.parse(call_api.responseText);
            message = "<p>List of people:</p><ul>";
            for (key in response.results) {
                message += "<li style=\"cursor:pointer;\" onclick=\"displayPerson(\'" + 
                    response.results[key].id + "\')\">" + 
                    response.results[key].firstName + " "
                    response.results[key].surname + "</li>";
            }
            message += "</ul>";
            message += "<p style=\"cursor:pointer;\" onclick=\'logout()\'\"><b>Logout</b></p>"
            document.getElementById("login").innerHTML = message;
        }

        function displayPerson(personID) {
            display_person.open("GET", server + "/rest.core/api/People/" + personID, true);
            display_person.setRequestHeader('Authorization', "Bearer " + window.sessionStorage.getItem("access_token"));
            display_person.send();
        }

        display_person.onload = function() {
            let response = JSON.parse(display_person.responseText);
            message = "<p><b>Name:</b> " + response.name.fullName + "</p>";
            message += "<p><b>Group:</b> " + response.group.name + "</p>";
            message += "<p><b>Logon Name:</b> " + response.logonName + "</p>";
            message += "<p><b>Roles:</b></p><ul>";
            for (key in response.roles) {
                message += "<li>" + response.roles[key].name + "</li>"
            }
            message += "</ul><p style=\"cursor:pointer;\" onclick=\'getPeople()\'\"><b> &lt; Back</b></p>"
            document.getElementById("login").innerHTML = message;
        }

        logout = function() {
            window.sessionStorage.setItem("access_token", "");
            window.sessionStorage.setItem("code_verifier", "");
            window.location.href="/";
        }
    </script>
</body>
</html>