OAuth 2 in Web AppBuilder app

4533
14
11-08-2017 11:33 PM
sitsit
by
New Contributor III

Hi everyone,

I am trying to integrate the OAuth functionality in a simple app developed with the Web AppBuilder embedded with Portal and then deployed.

I have obtained the AppID and I have looked into the OAuth 2 basic sample, but I don't understand how to integrate it in my app.

I suppose that I have to modify the index.html, but I don't know how.

Can anyone point me in the right direction?

Thanks,

Marco

Tags (2)
0 Kudos
14 Replies
rgomes
by
New Contributor III

I have one example making app login with AGOL and this proxy-oauth. I just change the proxy.ashx and the Index.html for the application to request the TOKEn before load the application (inside the <script> tag for index.html).

Add the variables to web.config for the proxy configuration with username, password, clientid and clientsecret and make this changes like descrived:

<add key="user" value="loginname"/>
<add key="pass" value="password"/>
<add key="clientid" value="1111111111"/>
<add key="clientsecret" value="2222222222222222222"/>

Change the function ProcessNormalRequest (inside proxy.ashx) to add one validation just like below (before make the URL request ~line 232):

if (uri.Contains("oauth2/token"))
{
// Get the AppSettings section.
NameValueCollection appSettings = ConfigurationManager.AppSettings;
string appSecret = "";
string user = "";
string pass = "";
string clientid = "";
string clientsecret = "";
for (int i = 0; i < appSettings.Count; i++)
{
System.Diagnostics.Debug.WriteLine(appSettings.GetKey(i) + "," + appSettings);
//if (appID == appSettings.GetKey(i))
//{
// appSecret = appSettings;
//break;
//}
if ("user" == appSettings.GetKey(i))
{
user = appSettings;
}
if ("pass" == appSettings.GetKey(i))
{
pass = appSettings;
}
if ("clientid" == appSettings.GetKey(i))
{
clientid = appSettings;
}
if ("clientsecret" == appSettings.GetKey(i))
{
clientsecret = appSettings;
}
}
uri += "&username="+ user + "&password="+ pass + "&client_id=" + clientid + "&client_secret=" + clientsecret + "&grant_type=client_credentials";
}


and in the index.html of the main application, insert one script just like below:

<script>
/*******************************
* This is the function you can modify to customize the loading page
* This function will be invoked when one resource is loaded.
********************************/
// var progress;
// function loadingCallback(url, i, count) {
// var loading = document.getElementById('main-loading-bar');
// loading.setAttribute('title', url);
// if(!progress){
// progress = document.createElement('div');
// progress.setAttribute('class', 'loading-progress');
// loading.appendChild(progress);
// }
// progress.style.width = (((i - 1)/count) * 100) + '%';
// }
var xhr = new XMLHttpRequest();
xhr.open('POST', 'https://yourserver/proxyoauth/proxy.ashx?https://www.arcgis.com/sharing/rest/oauth2/token?expiration...', true);
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.onreadystatechange = function () {
// do something to response
console.log(this.responseText);
testeTopken = this.responseText;
//expiraUTCformat = Date.now() + (testeTopken.substr(testeTopken.indexOf(',"expires_in":')+14, 5)*1000);
expiraUTCformat = Date.now() + 86400000);
testeEsriJSAPI1 = testeTopken.replace('{"access_token":','{"/":{"https://yourportalserver":{"expires":'+expiraUTCformat+',"ssl":true,"token":');
testeEsriJSAPI1 = testeEsriJSAPI1.substr(0, testeEsriJSAPI1.indexOf(',"expires_in":'));
testeEsriJSAPI1 = testeEsriJSAPI1 + ',"userId":"username"}}}';
localStorage.setItem('esriJSAPIOAuth', testeEsriJSAPI1);
};
xhr.send();
</script>

Hope this help you. 

0 Kudos
sitsit
by
New Contributor III

Nothing to do: it keeps redirecting me to the same page. Probably I am missing something somewhere...

But thank you anyway, Rafael: you have been really kind!

0 Kudos
KenBuja
MVP Esteemed Contributor

I have a widget using OAuth2 functionality. In the widget's postcreate function, I call this function

  _logon() {
    var theAppId, thePopupCallbackUrl;

    var location = this._appInfo.find(x => x.location === window.location.hostname);
    if (location !== undefined) {
      theAppId = location.appID;
      thePopupCallbackUrl = location.popupCallbackUrl;
    } else {
      new Message({
        titleLabel: 'Error',
        message: 'This site will not allow ArcGIS.com login'
      });
      this._logoff();
      return;
    }

    var info = new ArcGISOAuthInfo({
      appId: theAppId,
      popupCallbackUrl: thePopupCallbackUrl,
      popup: true,
      portalUrl: this.config.agol.portalUrl
    });
    esriId.registerOAuthInfos([info]);
    esriId.getCredential(info.portalUrl, {
      oAuthPopupConfirmation: true
    }).then(lang.hitch(this, function () {
      new arcgisPortal.Portal(this.config.agol.portalUrl).signIn().then(lang.hitch(this, function (portalUser) {
        var portal = portalUser.portal;
        var queryParams = {
          num: 100,
          q: 'owner: ' + this._ownerName + ' AND type: Feature Service'
        };
        portal.queryItems(queryParams).then(lang.hitch(this, function (result) {
          if (result.total > 0) {
            if (array.some(result.results, lang.hitch(this, function (item) {
              this._SPGridUrl = item.url;
              return this._SPGridUrl.indexOf(this._baseGridLayerName) > -1;  //
            }))) {
              this._prepareEditor();
            } else {
              this._SPGridUrl = null;
              new Message({
                titleLabel: 'Logging out',
                message: 'The grid was not found in your account!'
              });
            }
          } else {
            this._SPGridUrl = null;
            new Message({
              titleLabel: 'Logging out',
              message: 'The grid is not available to edit!'
            });
          }
        }));
      }
      )).otherwise(
        function (error) {
          new Message({
            titleLabel: 'Error occurred while signing in',
            message: error
          });
        }
        );
    }))
      .otherwise(lang.hitch(this, this._promiseRejected));
  },

When the user starts the widget, after they successfully log in, the code searches for a feature layer (which begins with the string indicated in the variable this._baseGridLayerName) in their AGOL contents and added to their map (in the _prepareEditor function). The variable this._appInfo contains the AppID and the popup callback URL. In my case, I have several of these defined, since I have versions running on my laptop, a development server, and the production server.

    this._appInfo = [
      {
        "location": "productionURL",
        "appID": "xxxxxxxxxxxxxxxxx",
        "popupCallbackUrl": "productionURL/oauth-callback.html"
      },
      {
        "location": "developmentURL",
        "appID": "xxxxxxxxxxxxxxxxx",
        "popupCallbackUrl": "developmentURL/oauth-callback.html"
      },
      {
        "location": "laptopURL",
        "appID": "xxxxxxxxxxxxxxxxx",
        "popupCallbackUrl": "./oauth-callback.html"
      }
    ]
sitsit
by
New Contributor III

Thank you very much, Ken. I will look into your code and let you know if I can manage to replicate it.

I would have preferred a solution without the use of a widget, but I suppose that it will work just fine.

0 Kudos
TheKenerson
Occasional Contributor

Sit Sit,

        Please let me know if you were able to figure this out. I also have a WAB App that accesses secure services and I would like to use the OAuth Named User Login Popup instead of the AGOL Username and Password.

Thanks,

Scott

0 Kudos