DocuSign Implicit Grant v1

This example implements the OAuth Implicit grant flow, and retrieves the user's information via the /userinfo endpoint. See my Medium article XXX for more information.

by Larry Kluger

HTML

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.0.2/css/bootstrap.min.css">
<div id="content">
   <h1>OAuth Implicit Grant</h1>
   <section id="spinner" class="hide">
       <div class="spinner">
          <div class="rect1"></div>
          <div class="rect2"></div>
          <div class="rect3"></div>
          <div class="rect4"></div>
          <div class="rect5"></div>
       </div>
   </section>
   <div id="login">
      <a id="btnOauth" class="btn btn-primary" 
         type="button" role="button">
         Login with DocuSign</a>
      <p class="mt-3">
      Use your DocuSign Developer Account. Don't have one? 
      <a href="https://go.docusign.com/o/sandbox/?utm_campaign=GBL_XX_DEV_NEW_2208_ExternalProgrammingTools&utm_medium=display&utm_source=jsfiddle" target="_blank">Free signup!</a>
      </p>
      <p><a href="https://javascript.plainenglish.io/oauth-implicit-grant-for-single-page-applications-jsfiddle-and-codepen-10c2c7946f05" target="_blank">Medium article</a></p>
   </div>
   <div id="msg">
   </div>
   <div id="fetch" class="hide">
      <a id="btnFetch" class="btn btn-primary" 
         type="button" role="button">
         Fetch data</a>
    </div>
   <div id="notSecure" class="hide">
        <p class="text-danger">Error: this page is using HTTP.</p>
        <p class="text-danger">Solution: reload this page using HTTPS.</p>
   </div>
   <div id="oauthResults" class="hide">
       <h3>OAuth Results</h3>
       <div class="panel-body"></div>
   </div>
   <div id="results" class="hide">
       <h3>Fetch Results</h3>
       <div class="panel-body"></div>
   </div>
</div>

CSS

pre {
    text-align: left;
    background-color: aliceblue;
    font-size: smaller;
    margin: 15px;
}

h1, h2, h3 {margin-top: 1.5em;}

#content {
    text-align: center;
}
#content p {
    padding: 0 25px 0 25px;
}
#demo {
    display: none;
}
#raw_data {
    text-align: left;
}
#demo img {
    height: 64px;
    width: 64px;
    border-radius: 50%;
}
.panel {
    margin: 50px 75px;
}

.hide {
    display: none;
}

        /* From http://tobiasahlin.com/spinkit/ */
        .spinner {
            margin: auto;
            width: 150px;
            height: 140px;
            text-align: center;
            font-size: 10px;
        }
        .spinner > div {
            background-color: blueviolet;
            height: 100%;
            width: 6px;
            display: inline-block;
            -webkit-animation: sk-stretchdelay 1.2s infinite ease-in-out;
            animation: sk-stretchdelay 1.2s infinite ease-in-out;
        }
        .spinner .rect2 {
            -webkit-animation-delay: -1.1s;
            animation-delay: -1.1s;
        }
        .spinner .rect3 {
            -webkit-animation-delay: -1.0s;
            animation-delay: -1.0s;
        }
        .spinner .rect4 {
            -webkit-animation-delay: -0.9s;
            animation-delay: -0.9s;
        }
        .spinner .rect5 {
            -webkit-animation-delay: -0.8s;
            animation-delay: -0.8s;
        }
        @-webkit-keyframes sk-stretchdelay {
            0%, 40%, 100% { -webkit-transform: scaleY(0.4) }
            20% { -webkit-transform: scaleY(1.0) }
        }
        @keyframes sk-stretchdelay {
            0%, 40%, 100% {
                transform: scaleY(0.4);
                -webkit-transform: scaleY(0.4);
            }  
            20% {
                transform: scaleY(1.0);
                -webkit-transform: scaleY(1.0);
            }
        }

JavaScript

// Copyright © 2022 DocuSign, Inc.
// License: MIT Open Source https://opensource.org/licenses/MIT

// Set basic variables
const oAuthServiceProvider = "https://account-d.docusign.com";
const implicitGrantPath = "/oauth/auth";
const userInfoPath = "/oauth/userinfo";
// Client IDs are NOT secrets. See
// https://www.rfc-editor.org/rfc/rfc6749.html#section-2.2
const oAuthClientID = "d70c3a89-97b9-48d1-8bf3-5401cbbd6f82";
const oAuthScopes = "signature";
const oAuthReturnUrl = "https://docusign.github.io/jsfiddleImplicitGrantReturn.html"
const eSignBase = '/restapi/';

debugger; // uncomment with debugger open to find the right JS file.

/*
 * ImplicitGrant handles the functionality of the implicit grant flow
 * 
 * It opens, then later closes, a new browser tab.
 * The client app must call handleMessage when the window receives a message event
 *
 * args -- an object containing attributes:
 *   oAuthServiceProvider
 *   implicitGrantPath
 *   oAuthClientID
 *   oAuthScopes
 *   oAuthReturnUrl
 *   workingUpdateF -- function called when working state changes
 * 
 * public values
 *   .errMsg -- null or contains the error information
 *   .working -- is the implicit grant flow in process?
 *   .accessToken -- the access_token or null
 *   .accessTokenExpires -- a Date object or null
 */
class ImplicitGrant {
  constructor(args) {
    this.oAuthServiceProvider = args.oAuthServiceProvider;
    this.implicitGrantPath = args.implicitGrantPath;
    this.oAuthClientID = args.oAuthClientID;
    this.oAuthScopes = args.oAuthScopes;
    this.oAuthReturnUrl = args.oAuthReturnUrl;
    this.workingUpdateF = args.workingUpdateF || null;

    // public variables
    this.working = false;
    this.accessToken = null;
    this.accessTokenExpires = null;
    this.errMsg = null;

    // internal
    this._loginWindow = null;
    this._nonce = null;
  }

  async login() {
    this.working = true;
    this.errMsg = null;
    if (this.workingUpdateF) {
     ...