Programmatically Log In to ArcGIS Online via Javascript Application

11864
13
01-07-2014 10:42 AM
BrianBeck
New Contributor III
I have a Javascript Web Map Application using the Esri JS API that is accessing data in ArcGIS Online.  I don't want my users to have to log in to ArcGIS Online in order to access the content so I'd like the app to log in instead.

So far I was able to accomplish this using a proxy page that attaches a token to any requests for secured services.  This method works, but the proxy page creates a bottleneck and the performance decrease is very noticeable when using the app even when panning and zooming.

I've been looking at the documentation for the IdentityManager, IdentityManagerBase, and Credential classes in the JS API to see if there is a way to recreate the process used when the user is prompted for credentials by adding credentials to the Identity Manager programmatically.  So far I cannot figure out how to save a token in the IdentityManager for use by future requests.

Is there a way that I could use my proxy page to retrieve a token when the app is loaded and then add the token to the IdentityManager so it would automatically be added to any necessary requests and the user would not be prompted for credentials?
0 Kudos
13 Replies
JianHuang
Occasional Contributor III
For your case, you should disable identity manager by setting esri.id=null;.
The next step is to attach the token to the url when you create a layer. For example, var fl = new FeatureLayer(url+"?token="+token);. There are several things worth pointing out.
1. If you need to use editor and ownership-based-access-control is enable on server side, you need to add credential to the featureLayer by fl.credential = {}; fl.credential.userId = userId; fl.credential.token = token;
2. When generating the token, select HTTP referer or IP referer to make sure the token is only valid through your website.
3. when token is about to expire, you need to generate the token again and replace the token by: fl.url = url+"?token="+newToken; fl._url.query.token = newToken;

In most cases, attaching the token to the url when creating the layer is sufficient enough.
0 Kudos
BrianBeck
New Contributor III
In my case, I am not creating individual FeatureLayers because I am using arcgisUtils.createMap() to load data from ArcGIS Online.  Is there a way I could do the same thing with the webmap?
0 Kudos
JianHuang
Occasional Contributor III
After createMap() completes, you can have the reference to map and layers. Then loop through those layers and attach token to the url by:
layer.url = layer.url + "?token=" + token;
layer._url.query.token = token;

Please let me know if this helps.
0 Kudos
BrianBeck
New Contributor III
I think this will still prompt the user for credentials during the createMap call because it has to access the secured resources before returning the layers.
0 Kudos
JianHuang
Occasional Contributor III
When you share the map in arcgis.com viewer, did you select share to public?
0 Kudos
BrianBeck
New Contributor III
Currently everything is shared with Everyone (public) and everything works without requiring the user to login.  What I'm trying to do now is make it so certain services are only accessible via the app.  That way the only people who can work with the data are those who have access to the application.
0 Kudos
JianHuang
Occasional Contributor III
I think you should make the map (meaning the web map json) public, and secure the data (feature service, map service and so on.).
0 Kudos
BrianBeck
New Contributor III
Even if I make the webmap public and leave the layers secure, the createMap call tries to access the secured services and will prompt the user for credentials.

I think I may have found a solution.  I created a web service that simply calls the generateToken REST endpoint and returns the token to the client.  Then I have a Javascript function that creates new Credential objects for each secured resource with the returned token.  I can then add each credential to esriKernel.id.credentials.  By using the web service, my username and password are still protected in server side code so users have not access to it, but the app can still retrieve a token to save in the IdentityManger.
0 Kudos
JianHuang
Occasional Contributor III
That should work. That's the same logic as I suggested.
Here is a simple code running on nodejs to generate token.
1. you don't have to generate a new token every time when a user opens the web app. You can share the common one as long as it's still not expired.
2. On client side, you need to call setInterval(function(){'gettokenfunction'}, 2 hours);
var ex = require("express");
var app = ex();
var http = require("http"),
  qs = require("querystring");

var userToken, userTokenExpire;

var getToken = function(userInfo, res){
  var currentTime = new Date().getTime();
  if (userToken && (userTokenExpire && userTokenExpire - 7200000 > currentTime) ) {
    //-7200000 (it's 2 hours) ensures the token is valid for at least next 2 hours.
    //any new request within the 2 hours will create a new token.
    res.send({token: userToken, expires: userTokenExpire});
  }
  else {
  var options = {
    method: "POST",
    host: /*ip address here*/,
    path: "http://arcgisserverdomain/arcgis/admin/generateToken",
    headers: {"Connection": "keep-alive", "host": /*ip address*/, "Content-Type": "application/x-www-form-urlencoded"}
  };
  var postData = qs.stringify({
    username: userInfo.username,
    password: userInfo.password,
    client: "referer",
    referer: "http://yourwebsitedomain/",
    f: "json", 
    expiration:1440 //it can be up to 24 hours, which is 24*60 minutes
  });
  var request = http.request(options, function(response){
    response.setEncoding('utf8');
    response.on('data', function (chunk) {
      userToken = JSON.parse(chunk).token;
      console.log(userToken);
      userTokenExpire = JSON.parse(chunk).expires;
      console.log(userTokenExpire);
      res.send(chunk);
    });
  });
  request.write(postData);
  request.end();
  }
};

app.get("/gettoken", function(req, res){
  var userInfo = {};
  //userInfo.username = req.query.username;
  //userInfo.password = req.query.password;
  //if (!userInfo.username && !userInfo.password) {
  //since you don't need user to provide username and password, hard-code it.
  userInfo.password = username;
  userInfo.username = password;
  //}
  getToken(userInfo, res);  
});

app.listen(3000);
0 Kudos