Hello,
I was trying to explore user contents by following the documents https://developers.arcgis.com/rest/users-groups-and-items/user-content/, and I used the API https://machine.domain.com/webadaptor/sharing/rest/content/users/jsmith?sf=json&token=mytoken.
Option 1: When the token is generated from https://machine.domain.com/webadaptor/sharing/rest/generateToken, the content API is working perfectly from Postman, but from the widget, it is getting the error "Invalid token.".
Option 2: If the token is generated from Python, then the API is working perfectly, both from the widget and Postman
gis = GIS(portal_url, username, password)
print(gis._con.token)
I am seeking help to get a prompt solution to work with Option A because the token is generated from a .NET application.
Thank you
Sakil
I have a few questions.
first: You said the 'token is generated from a .Net application'? Its my understanding that the rest endpoint for generateToken is an ArcGIS endpoint. I don't know that it is specifically .Net but that should not matter.
second: You said a custom widget was having issues, could you show us the bit of code that makes the call to that endpoint? Specifically, I'm wondering if you've not set it up to make the call asynchronously and wait for a callback.
So I was expecting some kind of code which might look like this (note you'd have to have the correct url for the rest api endpoint, and its not wise to hard code username/pwd into a script like this for use in production, but you could use it to test out whether the endpoint is returning a token correctly)
async function generateToken(): Promise<string | null> {
const tokenUrl = "https://machine.domain.com/webadaptor/sharing/rest/generateToken";
const params = new URLSearchParams();
params.append("username", "your-username");
params.append("password", "your-password");
params.append("client", "requestip"); // or 'referer' with referer param
params.append("f", "json");
params.append("expiration", "60"); // token lifetime in minutes
try {
const response = await fetch(tokenUrl, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: params.toString()
});
const json = await response.json();
if (json.token) {
console.log("Token generated:", json.token);
return json.token;
} else {
console.error("Token generation failed:", json);
return null;
}
} catch (error) {
console.error("Error generating token:", error);
return null;
}
}
Does this help?
Hi TimWestern,
Thank you for your response. I am sharing my code here:
From .NET:
Controller:
[HttpPost("generate-arcgis-token")]
public async Task<IActionResult> GetToken([FromBody] UserCreds payload)
{
try
{
string pass = CryptoHelper.DecryptStringFromCryptoJS(payload.pa_encp);
var tokenClient = new ArcGISAccessFacade();
var token = await tokenClient.GetArcGISTokenAsync(payload.portal_url, payload.username, pass);
return Ok(token);
}
catch (Exception ex)
{
return BadRequest(new { error = ex.Message });
}
}
public async Task<TokenResponse> GetArcGISTokenAsync(string portal_url, string username, string password)
{
var url = portal_url + "/sharing/rest/generateToken";
int expirationMinutes = 60 * 24 * 7; // 7 days
var formData = new Dictionary<string, string>
{
{ "username", username },
{ "password", password },
{ "client", "referer" },
{ "referer", portal_url },
{ "expiration", expirationMinutes.ToString()},
{ "f", "json" }
};
var content = new FormUrlEncodedContent(formData);
var response = await httpClient.PostAsync(url, content);
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync();
var result = System.Text.Json.JsonSerializer.Deserialize<TokenResponse>(json);
return result;
}
From ExbWidget:
So I'm not sure I quite get the architecture here.
You have a custom widget in Experience Builder, (this is a separate web application built on top of ReactJS as a platform for multiple applications, which can then host multiple widgets custom and built-in)
You have some .Net application that you are trying to connect?
I'm wondering why the server would not see this other .Net Application as a different location. The EXB app/Widget takes advantage of the user agent of the browser. A .Net app's http call is likely going to look different. (I'm not sure what your .net application does once it has a token, but if all you need is to access the REST API to do some things in .Net I am struggling to understand why you need Experience Builder at all.)
Perhaps put Fiddler between the .Net application and the server and see the header differences might be a clue?
Looking at this bit of code:
public async Task<TokenResponse> GetArcGISTokenAsync(string portal_url, string username, string password)
{
var url = portal_url + "/sharing/rest/generateToken";
int expirationMinutes = 60 * 24 * 7; // 7 days
var formData = new Dictionary<string, string>
{
{ "username", username },
{ "password", password },
{ "client", "referer" },
{ "referer", portal_url },
{ "expiration", expirationMinutes.ToString()},
{ "f", "json" }
};
It occurss to me that you are using a 'referer' based token (using the portal_url)
Exb has some safeguards in there around that built around CORS, the Referer headers, as wella s portal trust level. If you try to do the same with the same token from .NET you may find that won't work because the referer is going to look different. (It might take a comparison between the request in the network tab that the widget makes vs the headers in the .net request).
The python one runs differently, so may not have some of the same checks I believe.
You might try using a different type instead of referrer
{ "client", "requestip" }
Especially if the ip seen by the .net app would be the same as that seen by EXB app?
The other option might be to register an OAuth App, setup a client_id and client_secret to generate a trusted token, and then use something server side (rather than in the code in the front end) to keep it secure when used.