We have a small internal web application with a map component displaying specified zip codes in a feature layer. The application is used by users that has no ArcGIS account (and should not have) .
We have an organisational ArcGIS portal where the zip code layers we are using are shared on an organizational level (meaning that they are considered as private content).
I have looked at the documentation and figured out that I can’t use Application Credentials (as it can’t access private content at all) or an API key (as this key only can access my private ArcGIS Developer content), according to documentation and https://community.esri.com/t5/arcgis-api-for-javascript/access-restricted-content-from-application/td-p/1044287
So I need an ArcGIS identity to be able to access the organizational shared feature layers on our portal.
I have found a solution where the app calls a backend service component/api for a token in line with the ‘Direct username password authentication’ documentation: https://developers.arcgis.com/documentation/mapping-apis-and-services/security/arcgis-identity/direct-username-password/
The backend api uses my ArcGIS credentials (securely stored and protected) and the generateToken REST API with client=requestip, to generate and return a valid token back to the app.
The token is then passed on to the identityManager.registerToken method. Then the app can access and display the needed feature layers on our portal.
Users can not do anything with the feature layers in the app, only display filtered out zip codes.
Could anyone confirm that this is is a valid setup (inline with 'Terms of usage') for displaying protected organizational content for users without access to ArcGIS and the best (only?) way to do it?
From my perspective it would be useful to use some kind of 'Service Account' instead, which can be used instead of my own personal ArcGIS account credentials. Or even better, to be able to use the API Key to access private content.
Thanks in advance...
The code for initializing the map component where token is fetched and used:
export const initialize = async (container) => {
const { userToken, error } = await fetchMapToken();
const tokenObj = {
server: 'https://www.arcgis.com/sharing/rest',
token: `${userToken.token}`,
ssl: true,
expires: userToken.expires,
};
return new Promise((resolve, reject) => {
if (error) {
reject(new Error(`Failed to login to ArcGIS.' Error: ${error.message}`));
}
map = new Map({
basemap: 'arcgis-navigation',
});
view = new MapView({
container,
map,
});
identityManager.registerToken(tokenObj);
identityManager
.checkSignInStatus(portalUrl)
.then(async () => {
await view.when(
() => {
view.extent = countryLevel;
},
(err) => reject(err)
);
resolve(() => {
view.container = null;
});
})
.catch((err) => {
displayMessage('error', `Failed to sign in to ArcGIS. Error: ${err.message}`);
reject(err);
});
});
};
Backend API code:
router.get(‘/userToken', authenticate, async (req, res) => {
getUserCredentials().then(async (credentials) => {
const { arcGisAccountUserId, arcGisAccountUserPassword } = credentials;
const urlencoded = new URLSearchParams();
urlencoded.append('password', arcGisAccountUserPassword);
urlencoded.append('username', arcGisAccountUserId);
urlencoded.append('f', 'json');
const requestOptions = {
method: 'POST',
body: urlencoded,
headers: {
'content-type': 'application/x-www-form-urlencoded',
},
};
const endpoint = 'https://www.arcgis.com/sharing/rest/generateToken?client=requestip';
try {
const response = await fetch(endpoint, requestOptions);
const json = await response.json();
if (response.ok && json && !json.error) {
res.json(json);
} else {
return res.sendStatus(500);
}
} catch (err) {
return res.sendStatus(400);
}
});
});