question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Document how consuming apps can login when an unauthenticated request fails with an auth error

See original GitHub issue

The scenario that we want to support is to allow an unauthenticated user to make requests via any of the arcgis-rest-js fns (getItem(), getItemDetails(), etc) and only a the point when they request a secured resource, will they be prompted to log in.

This request is currently coming from @qlqllu but will likely be something that others who are familiar w/ the default behavior of IdentityManager will want as well.

Obviously there are challenges to accommodating this kind of behavior due to the stateless nature of our API and the fact that it needs to run in both the browser and node and therefore has to support different authorization schemes. However, I’ve done some experiments, and I have what I think is a reasonable solution for this.

I propose that we add a property like autoAuthentication to IRequestOptions that is a function that returns a promise that resolves with an instance of an IAuthenticationManager. So we’d add this to IRequestOptions:

  /**
   * Use this function to login and retry when an unauthenticated request fails with an authorization error
   */
  autoAuthentication?: (originalRequestOptions: IRequestOptions) => Promise<IAuthenticationManager>

Then, if an unauthenticated request fails with an ArcGISAuthError, and an autoAuthentication function was passed in, call that function and retry the request with the auth that was returned. It’s up to the consuming application to decide how to implement autoAuthentication(). Browser apps can provide their own sign in dialog using their framework of choice, or kick off the OAuth flow. Node apps can create a new UserSession or ApplicationSession using credentials they get from the environment or from prompting the user. Note that we pass the original request options to autoAuthentication in case the consuming application needs the URL or other info to decide how to get the credentials.

Here’s an example of what this might look like in a browser app:

  // send this w/ all requests, it will be undefined until the user requests a secure resource and is prompted to sign in
  let userSession;

  //  use this to auto sign in user when the first unauthenticated request fails
  signIn = (orginalRequestOptions) => {
    const url = orginalRequestOptions.url;
    if (url.indexOf('sharing/rest') > -1) {
      // this is going to the portal API (items, groups, etc), kick off OAuth sign in
      const oauth2Options = {
       // NOTE: you could base portal URL and/or clientId of the orignialRequestOptions
        portal: "https://www.arcgis.com/sharing/rest",
        clientId: 'getyourown',
        redirectUri: window.location.origin + location.pathname,
        popup: true
      };
      return UserSession.beginOAuth2(oauth2Options)
      .then(newUserSession => {
        // hold on to user session and use that for future requests
       userSession = newUserSession;
       return newUserSession;
      });
    } else {
     // this is going to a server (feature service, etc)
     // TODO: we could either throw an error, or have a system to store multiple credentials and
     // show a dialog to gather user credentials and send a request to generate token
    }
  }

  // request the data for an unsecured application
  getItemData('3aef...', {
   // neither of these will be used since the item is shared w/ everyone
    authentication: userSession,
    autoAuthentication: !!userSession ? undefined : signIn
  }).then(data => {
    // now request a secured webmap
    getItem('4beg...', {
      // userSession is still undefined
      authentication: userSession,
      // signIn will be called when this request fails
      autoAuthentication: !!userSession ? undefined : signIn
    }).then(webMapItem => {
       // now get the webmap's data
       getItemData('4beg...', {
        // userSession is now defined, so this will provide the token for this request
        authentication: userSession,
        // signIn will not be passed in since it is no longer needed
        autoAuthentication: !!userSession ? undefined : signIn
      }).then(webMapData => {
         // do something with the webmap item/data
      });
    });
  });

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Comments:10 (10 by maintainers)

github_iconTop GitHub Comments

2reactions
tomwaysoncommented, Jun 19, 2018

@patrickarlt++ for thinking ahead. I agree that IdentityManager’s uber generic solution is overkill for most apps outside of the context of a webmap that may be pulling layers from who knows where.

I’ve updated my auto-login demo to use arcgis-rest-js’s built-in retry() method as @patrickarlt suggested above, and it works well.

So, the only thing left to do in this issue is add better docs for the existing capabilities. I’m not sure we could fit a meaningful example inline w/ the existing retry() docs. This seems like it should be a guide, perhaps a sub-topic of Authentication in Browser-based Apps

1reaction
patrickarltcommented, Jun 19, 2018

@tomwayson UserSession already handles a lot of IdentityManager like trust scenarios like what you are talking about in https://github.com/Esri/arcgis-rest-js/issues/224#issuecomment-398153456.

Each UserSession keeps its own cache of trustedServers https://github.com/Esri/arcgis-rest-js/blob/master/packages/arcgis-rest-auth/src/UserSession.ts#L241-L246 which it knows are federated with the original portal.

const sessionForPortal = UserSession.beginOAuth2({
  portal: "https://gis.somewhere.org/sharing/rest",
  clientId: "getyourown",
  redirectUri: window.location.origin + location.pathname
}).then(newUserSession => {
  // do stuff
});

So using the above UserSession would work for any server federated with https://gis.somewhere.org/sharing/rest. The UserSession.getTokenForServer() method is is used to fetch tokens for federated servers we do not trust yet and establish trust by having the portal generate a token for the server.

UserSession also implicitly trusts other arcgis.com domains if you have https://www.arcgis.com/sharing/rest set as your portal to make less requests.

If we cannot establish trust like in the following case we will get an ArcGISAuthError where ArcGISAuthError.code === 'NOT_FEDERATED' then the developer can use ArcGISAuthError.url and ArcGISAuthError.retry to get the user signed into the correct portal.

UserSession.beginOAuth2({
  clientId: "getyourown",
  redirectUri: window.location.origin + location.pathname
}).then(agolUserSession => {
  /**
   * Attempt to use our ArcGIS Online session with a service that is not
   * federated with ArcGIS Online
   */
  return geocode({
    endpoint: "https://geocode.gis.somewhere.org/rest/services/OrganizationGeocodeService"  
    address: "1600 Pennsylvania Ave",
    postal: 20500,
    countryCode: "USA",
    authentication: agolUserSession
  })
}).then((result) => {
  /**
   * this will fail since the 3rd party geocode service isn't federated with
   * Online so agolUserSession can't generate a token for it.
   */
}).catch((error) => {
  /**
   * since we cant generate a token we will get an instance of `ArcGISAuthError`
   * with `NOT_FEDERATED` as its code.
   */
  if(error.code === "NOT_FEDERATED") {
    /**
     * error message will be https://geocode.gis.somewhere.org/rest/services/OrganizationGeocodeService is not federated with https://www.arcgis.com/sharing/rest.`
     */
    console.log(error.message);
    error.retry(() => {
      /**
       * Now we can figure out the portal that owns `error.url` and prompt the
       * user to sign into that portal and retry the request.
       */
    })
  }
});

I did of this with the interest of NOT doing any sort of IdentityManager instead I focused on building the tools so developers could manage however many instances of UserSession they wanted. I have issues with how IdentityManager gets authentication details from you when it needs to get data from another portal or unfederated service because it doesn’t use OAuth for those authentications it just asks for your username and password. There is no way to trust it which I think makes this a huge security hole but there really isn’t any way to handle it.

I always have the impression that the design and implementation of IdentityManager is great for ArcGIS Online since it seamlessly handles an infinite amount of federation, service and portals but is total overkill for most developers who are only going to be working with 1-2 portals.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Azure AD authentication & authorization error codes
Client authentication failed. The client credentials aren't valid. To fix, the application administrator updates the credentials. The  ...
Read more >
401 Unauthorized Error: What It Is and How to Fix It
The 401 Unauthorized Error is an HTTP response status code indicating that the client could not authenticate a request.
Read more >
Making Unauthenticated Requests - Learn — Liferay
Sign in using the default credentials: · Go to Global Menu → Control Panel → Security → Service Access Policy. · Click Add...
Read more >
The Complete Guide to React User Authentication with Auth0
You can now secure your React applications using security best ... up an authentication service that your React application can consume.
Read more >
Authentication and Authorization in the Google Data Protocol
Each OAuth token is specific to the user who authorized access. Your application must be able to associate a token with the correct...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found