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.

Handling authentication

See original GitHub issue

Authentication is a problem that has been bugging me for awhile about this repo. I’m note entirely sure how to resolve it. As a reference here are some of our design goals for this:

  • Avoid use of a singleton like IdentityManager
  • Make it easy for a developer to configure authentication once and forget about it no matter what library they are using.
  • We want to properly federate requests (a la JS API) as much as possible with as minimal overhead as possible.

I feel like these goals are at odds with each other. No matter what I seem to always come up with designs where you pass the authentication in or query the authentication object for information.


Design 1

Below is a rehash of my inital design:

import { request } from 'arcgis-rest-core';
import { UserAuthentication, AppAuthentication } from 'arcgis-rest-auth';

const auth = new AppAuthentication({
  clientId: '123'; // required
  clientSecret: '123' // required
  token: '123' // optional, an access token if you have one
  expires: Date.now() // when the provided token expires
  portal: 'https://www.arcgis.com/sharing/rest' // which portal generated this token
});

auth.on('error', (error) => {
  // unable to authenticate a request so get a new token and retry.
  auth.refreshToken(/* ... */).then((token) => {
    error.request.retry(token);
  });
});

request({
  url: '...'
  authentication: auth,
  params: {
    //...
  }
}).then((response) => {
  // Success!
}).catch((error) => {
  // Error! Both HTTP AND server errors will end up here...
});

This approach will work fine, except that most of the interaction this this repo won’t be directly with request it will be with help methods that we will write like geocode. So this starts to break down when we do this:

import {request} from 'arcgis-core';

export function geocode (/* ... */) {
  // since request is internal it won't be authenticated. :(
  return request(/* ... */).then(/* ... */);
}

Design 2

import { request } from 'arcgis-rest-core';
import { UserAuthentication, AppAuthentication } from 'arcgis-rest-auth';

const session = new AppAuthentication({
  clientId: '123'; // required
  clientSecret: '123' // required
  token: '123' // optional, an access token if you have one
  expires: Date.now() // when the provided token expires
  portal: 'https://www.arcgis.com/sharing/rest' // which portal generated this token
});

session.on('error', (error) => {
  // unable to authenticate a request so get a new token and retry.
  session.refreshToken(/* ... */).then((token) => {
    error.request.retry(token);
  });
});

// The UserAuthentication and AppAuthentication methods will expose a `authenticate`
// method that accepts a Promise from `request()` it then returns that promise with
// additional error handling.
session.authenticate(request({
  url: '...'
  params: {
    //...
  }
})).then((response) => {
  // Success!
}).catch((error) => {
  // Error! Both HTTP AND server errors will end up here...
});

// this would also work for anything that returned a Promise from `request()` so...
session.authenticate(geocode({/* ... */})).then((response) => {
  // Success!
}).catch((error) => {
  // Error! Both HTTP AND server errors will end up here...
});

This method has 1 major drawback though. The inital request will ALWAYS be unauthenticated, since the token was never passed in the inital params. This sucks but we should be able to recover from the error but it will happen every time whereas the JS API is smart enough to not fail and retry with a token all the time.


Design 3

This design is like the inverse of the one above. We simply expose the authentication option on all methods use request under the hood and teach request how to use the passed authentication object to handle auth failures.

import { request } from 'arcgis-rest-core';
import { UserAuthentication, AppAuthentication } from 'arcgis-rest-auth';

const session = new AppAuthentication({
  clientId: '123'; // required
  clientSecret: '123' // required
  token: '123' // optional, an access token if you have one
  expires: Date.now() // when the provided token expires
  portal: 'https://www.arcgis.com/sharing/rest' // which portal generated this token
});

session.on('error', (error) => {
  // unable to authenticate a request so get a new token and retry.
  session.refreshToken(/* ... */).then((token) => {
    error.request.retry(token);
  });
});

// we would have to teach request how to use `AppAuthentication` to recover from
// auth failures. Hopefully via an interface so it doesn't bloat the core repo.
request({
  url: '...'
  authentication: session
  params: {
    //...
  }
})).then((response) => {
  // Success!
}).catch((error) => {
  // Error! Both HTTP AND server errors will end up here...
});

// we would have to pass the `authentication` object down throguh the 
geocode({
  authentication: session
})).then((response) => {
  // Success!
}).catch((error) => {
  // Error! Both HTTP AND server errors will end up here...
});

Design 4 - Singleton Authentication Manager

import { request } from 'arcgis-rest-core';
import { AuthenticationManager } from 'arcgis-rest-auth';

// register a client id and client secret
AuthenticationManager.registerOauthCredentials({
  clientId: '123'; // required
  clientSecret: '123' // required
  token: '123' // optional, an access token if you have one
  expires: Date.now() // when the provided token expires
  portal: 'https://www.arcgis.com/sharing/rest' // which portal generated this token
});

// register an existing token or token from user auth
AuthenticationManager.registerToken({
  clientId: '123'; // required
  token: '123' // optional, an access token if you have one
  expires: Date.now() // when the provided token expires
  portal: 'https://www.arcgis.com/sharing/rest' // which portal generated this token
});

AuthenticationManager.on('authentication-required', (error) => {
  // unable to authenticate a request user will need to prompt for auth to a service.
});

// request now talks to the `AuthenticationManager` for all auth decisions.
request({
  url: '...'
  params: {
    //...
  }
})).then((response) => {
  // Success!
}).catch((error) => {
  // Error! Both HTTP AND server errors will end up here...
});

// since geocode will impliment `request` it will also use the `AuthenticationManager` singleton.
geocode({
  authentication: session
})).then((response) => {
  // Success!
}).catch((error) => {
  // Error! Both HTTP AND server errors will end up here...
});

I’m not super liking any of these options here. @jgravois @dbouwman @ajturner @tomwayson.

Issue Analytics

  • State:closed
  • Created 6 years ago
  • Comments:11 (11 by maintainers)

github_iconTop GitHub Comments

1reaction
patrickarltcommented, Aug 4, 2017

@dbouwman I edited your comment to add highlighting.

@dbouwman @mjuniper If everyone else is feeling like a stateless request method that relies on getting passed a stateful authentication option is best I’ll work on implementing it.

I’m going to open a new issue to work on the design of this based on Design 3.

0reactions
patrickarltcommented, Aug 5, 2017

@dbouwman @mjuniper I’ve opened up #10 with a design based on Design 3 but with a few improvements.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Account authentication and password management best ...
13 best practices for user account, authentication, and password management ... secure handling and authentication of user accounts (in this ...
Read more >
Different ways to Authenticate a Web Application - Medium
Authentication is common way to handle security for all applications. This is only way to answer the question “who you are?
Read more >
Handling authentication for Power Query connectors
The sample below shows the Authentication record for a connector that supports OAuth, Key, Windows, Basic (Username and Password), and anonymous ...
Read more >
Handling Authentication - IBM
Handling Authentication. The Emptoris® Contract Management XML protocol offers two types of credentials for authentication: password and shared secret.
Read more >
Handling Authentication in Express.js - Stack Abuse
Introduction. In this article, we are going to make a simple app to demonstrate how you can handle authentication in Express.js.
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