I am building a web app in a low code platform (Mendix). I am connecting the web app with ArcGIS online resources via the ArcGIS JavaScript API v4.19, which all goes pretty smoothely.
The challenge arises when I want to load specific secured ArcGIS online content via the ArcGIS JavaScript API, specifically from some FeatureLayers which are secured. I looked into the documentation and it seems the best way forward would be a so-called 'application login'. For this I want to setup an OAuth application login based on CLient ID and Client Secret. With these two I can get a valid token via AOuth and use that token to access the content by feeding the token to the IdentityManager via the JavaScript API.
This is were it goes wrong currently, I can't seem to figure out where to make it explicit on the ArcGIS online side that this specific secured FeatureLayer can be accessed via this application login, hence currently I am getting errors that the valid token and app id don't have access to the resource, being the end-point of the secured FeatureLayer.
Does anybody know how to associate a secured FeatureLayer in ArcGIS online to a application login?
EDIT 10-6-2021: Added code sample
After succesfully retrieving a valid token on the server side based on client id and client secret I use the client ID (=AppID) and token in the ArcGIS JavaScript API like below:
const token = {
server: "http://www.arcgis.com",
userId: <AppID>,
token:
<valid token retrieved via OAuth generateToken request,
ssl: true,
expires: 7200
};
IdentityManager.registerToken(token);
Only implementing this gives me an error whilst trying to access the secured feature layer:
identity-manager:not-authorized. "You are currently signed in as:
AppID. You do not have access to this resource:
https://server/someid/arcgis/rest/services/somefeatureserver/FeatureServer/0
I also read that sometimes below could be needed so added as well:
const idString = JSON.stringify(IdentityManager.toJSON());
console.debug("idString: " + idString);
IdentityManager.initialize(idString);
This resolves the error but makes a login popup appear again.
The layer is afterwards declared like below:
const layer = new FeatureLayer({
// URL to the service
url: layerObj.layerURLStatic
definitionExpression: queryDefinition,
featureReduction: clusterConfig && { type: "cluster" },
popupTemplate: {
title: "{" + inAttributeTitle + "}",
content: [
{
type: "fields", // FieldsContentElement
fieldInfos
}
],
actions: [
{
title: props.intButtonLabel,
id: btnId,
className: props.intButtonClass + intButtonIconClass,
type: "button"
}
]
},
outFields: ["*"]
});
webMap.add(layer);
Here is a snippet to generate the token and then register it with IdentityManager:
IdentityManager = require('esri/identity/IdentityManager')
function login(user, password){
var serverInfo = {
"server": "https://www.arcgis.com",
"tokenServiceUrl" : "https://www.arcgis.com/sharing/generateToken"
};
var userInfo = {
username : user,
password : password
}
IdentityManager.generateToken(serverInfo, userInfo).then(function (response){
response.server = serverInfo.server;
response.userId = user;
IdentityManager.registerToken(response);
});
}
I'm not sure how you are going to fit this in you app, but the sample should work if you paste it in your developer tools console when the app is running.
Also, it seems to me that userId property is for arcgis online username, not for appId.
As pointed out by Shaked, if you append '?token=[token_value]' int the layer URL you probably don't even need to register the token to query the layer.
Related
I am trying to make API (Lambda and API gateway) for sign in and verify auth using OTP for password-less authentication. The target is to make front end using angular and mobile application using Flutter but there is no support of AWS Amplify for flutter. So going through to create those API to serve my purpose. The frontend code(Auth.signIn and Auth.sendCustomChallengeAnswer) works great but using same code the verify auth API is not working. Sharing my code.
Sign In API:
await Auth.signIn(phone);
Verify Auth API: (Returned c['Session'] from DynamoDB which is stored in during signIn)
let otp = body['otp'];
const poolData = {
UserPoolId: '------ pool id -------',
ClientId: '------ client id -------'
};
const userPool = new CognitoUserPool(poolData);
const userData = {
Username: '+12014222656',
Pool: userPool
};
this.cognitoUser1 = new CognitoUser(userData);
this.cognitoUser1['Session'] = c['Session'];
await Auth.sendCustomChallengeAnswer(this.cognitoUser1, otp);
const tokenDetails = await Auth.currentSession()
response = {
'statusCode': 201,
'body': JSON.stringify({
message: 'Verification successful',
body:tokenDetails
})
}
After debugging frontend Auth.signIn response and Lambda API Auth.signIn response i investigated that an extra "storage" object returned when signing in from frontend and appended on this.cognitoUser1 before sending through Auth.sendCustomChallengeAnswer . See the attached screenshot below:
Is this the reason for successful verifying OTP from frontend? If so, what about making API (using Lambda and API gateway) and where it stores this storage object. Stuck here. Any suggestion and help will be appreciated.
You can set your own storage: https://docs.amplify.aws/lib/auth/manageusers/q/platform/js#managing-security-tokens
I am using node.js in my application, with shopify-api-node (v3.2.0), to authenticate customer login along with other features if shopify. As per shopify documentation (https://shopify.dev/docs/storefront-api/reference/mutation/customeraccesstokencreate) I am using GraphQL to access shopify API.
My code looks something like this below :-
const Shopify = require('shopify-api-node');
const shopify = new Shopify({
shopName: process.env.SHOPIFY_DOMAIN_NAME,
apiKey: process.env.SHOPIFY_API_KEY,
password: process.env.SHOPIFY_API_KEY_PASSWORD
});
const query = `mutation {
customerAccessTokenCreate (input: {
email: "user#mail.com",
password: "password123"
}
)
{
customerAccessToken {
accessToken
expiresAt
}
customerUserErrors {
code
field
message
}
}
}`;
shopify
.graphql(query)
.then((output) => {
console.log(output);
})
.catch((err) => {
console.error(err)
});
After this I am getting below error :-
Error: Field 'customerAccessTokenCreate' doesn't exist on type 'Mutation'
at got.then (/Users/admin/Documents/Code/shopify-node-app/node_modules/shopify-api-node/index.js:239:19)
at process._tickCallback (internal/process/next_tick.js:68:7)
locations: [ { line: 2, column: 5 } ],
path: [ 'mutation', 'customerAccessTokenCreate' ],
extensions:
{ code: 'undefinedField',
typeName: 'Mutation',
fieldName: 'customerAccessTokenCreate' }
Even I am getting the same thing from postman itself.
Any help would be appreciated.
There are two types of GraphQL:
the storefront GraphQL - https://shopify.dev/docs/storefront-api/reference
the admin GraphQL - https://shopify.dev/docs/admin-api/graphql/reference
While they seems similar the strorefront is much more limited but can be used on the front-end, while the admin one is more rich in method and functionality but can't be used safely on the font-end.
The documentation and the method you are trying to make is referring to the Storefront API, but the package you are using is for the Admin GraphQL API.
You can create a storefront access token via the storefrontAccessToken method if you want to make storefront request but the Admin API GraphQL allows for more customization.
So you need to make sure you are using the proper API.
If you plan to use the storefront API, you shouldn't use NodeJS and just create a private app ( from Admin -> APP -> Private App) which will provide you a Store Front Access Token (if you enable it at the bottom and select the proper scopes) that can be used directly on the front-end.
If you plan to use the Admin API, you will need to create a public app and host it, then you can use NodeJS and pass the information via a Proxy in Shopify.
Summary
You are making a request to the Storefront API, while using a library for the Admin API.
We've got scripts on Bing to automatically adjust ad bids based on ad performance and client goals, which are stored in a Google spreadsheet.
We had a contractor set this up initially, and it worked. But I guess that the contractor was using a temp Google account and when it went away the bidders stopped working. Because it did work before, it's likely a configuration error on my part that's breaking it now, but the contractor pointed us to the steps I was already following to no avail (https://learn.microsoft.com/en-us/advertising/scripts/examples/authenticating-with-google-services#option2).
Stuff already tried
double checked for errant whitespace around the client ID and client secret
created new client secrets
created new client IDs
made sure that the project name, application name, and OAuth client id name were all the same
created whole new projects from scratch (configured to match the article cited above) to see if that would kick something loose
tried a different token URL (https://oauth2.googleapis.com/token) that appears in the client_secret JSON downloaded from Google
function main() {
const credentials = {
accessToken: '',
client_id: 'REDACTED.apps.googleusercontent.com', // from Google developer console
client_secret: 'REDACTED', // from Google developer console
refresh_token: 'REDACTED' // created at https://developers.google.com/oauthplayground
};
var access_token = '';
if (credentials.accessToken) {
access_token = credentials.accessToken;
}
var tokenResponse = UrlFetchApp.fetch('https://www.googleapis.com/oauth2/v4/token', { method: 'post', contentType: 'application/x-www-form-urlencoded', muteHttpExceptions: true, payload: { client_id: credentials.clientId, client_secret: credentials.clientSecret, refresh_token: credentials.refreshToken, grant_type: 'refresh_token' } });
var responseCode = tokenResponse.getResponseCode();
var responseText = tokenResponse.getContentText();
if (responseCode >= 200 && responseCode <= 299) {
access_token = JSON.parse(responseText)['access_token'];
}
throw responseText;
// use the access token to get client targets from the spreadsheet
A JSON encoded access token is the expected response, but instead, we get HTTP 400 with the message "The OAuth client was not found."
Manually creating an access token on the OAuth playground (https://developers.google.com/oauthplayground) works as a stopgap, but this should work. This has worked. :P
The fix in this case switching the Application Type on console.developers.google.com > Credentials > OAuth consent screen to Internal instead of Public.
That wasn't in the steps provided by Microsoft, and I'm not sure if that will have implications down the road, but at least we're off the manual process for now.
I am using 'googleapis' npm package to do token based google authentication.
I am redirected to '/api/auth/success/google' route inside express after google provided authentication and redirects us to the uri stated in google app credentials.
The problem I am facing is that ,I have retrieved the tokens on server side,but I am unable to send those tokens to client side for them to be saved in cookies.
The problem I am facing is because,'/api/auth/success/google' is redirected from google side and not an ajax call from client side.So if I send the tokens back in res,where will it redirect.Also please suggest a way to redirect from server side to client side,along with access_token.
server side code.
//Route reached after google successful login/authentication
app.get('/api/auth/success/google',function(req,res){
console.log("inside redirect");
var code = req.query.code;
oauth2Client.getToken(code, function(err, tokens) {
// Now tokens contains an access_token and an optional refresh_token. Save them.
if(!err) {
oauth2Client.setCredentials(tokens);
}
res.sendFile('./index.html');
});
})
Client side call
//Google login requested from this function
googleLogin(){
event.preventDefault();
$.ajax({
type : 'POST',
url : baseURL + 'api/authenticate/google',
success: (function(data) {
if (data.redirect) {
document.location.href = data.redirect;
}
}).bind(this)
});
}
//Route handling request of google access
app.post('/api/authenticate/google',function(req,res){
// generate a url that asks permissions for Google+ and Google Calendar scopes
var scopes = [
googlecredentials.SCOPE[0],
googlecredentials.SCOPE[1]
];
var url = oauth2Client.generateAuthUrl({
access_type: 'offline', // 'online' (default) or 'offline' (gets refresh_token)
scope: scopes // If you only need one scope you can pass it as string
});
res.send({ redirect: url });
})
//Google App Credentials
var OAuth2 = google.auth.OAuth2;
var oauth2Client = new OAuth2(googlecredentials.CLIENT_ID, googlecredentials.CLIENT_SECRET, googlecredentials.REDIRECT_URL);
googlecredentials.CLIENT_ID - 858093863410-j9ma1i7lgapupip1ckegc61plrlledtq.apps.googleusercontent.com
REDIRECT_URL - http://localhost:3000/api/auth/success/google where localhost:3000 runs server side
If you send the redirect URL back in the res, the client-side should be able to check for the presence of the redirect URL in the response, and if it exists, push your user to that URL.
I'm new to office 365 and having problem with accessing rest api.
I'm trying to test the rest api of Calendar and Mail API, so I decided to use Postman. However, to test those APIs, I need an access token in Authorization header. To figure out how to get a token, I decided to get the sample project here , configure, run and sign in on this local site to get the token cached in local storage and use that token for further requests in Postman. However, all requests I tested returned '401 unauthorized request'.
What I did:
Register a new app on Azure ADD associated with O365 account
Add full app permissions and delegated permissions.
Update 'oauth2AllowImplicitFlow' to true in manifest file.
Clone sample project
In app.js, I change the alter the content of config function as following
function config($routeProvider, $httpProvider, adalAuthenticationServiceProvider) {
$routeProvider
.when('/', {
templateUrl: 'views/home.html',
controller: 'HomeController',
controllerAs: 'home',
requireADLogin: true
})
.otherwise({
redirectTo: '/'
});
// The endpoints here are resources for ADAL to get tokens for.
var endpoints = {
'https://outlook.office365.com': 'https://outlook.office365.com'
};
// Initialize the ADAL provider with your tenant name and clientID (found in the Azure Management Portal).
adalAuthenticationServiceProvider.init(
{
tenant: 'mytenantname.onmicrosoft.com',
clientId: '<my cliend Id>',
endpoints: endpoints,
cacheLocation: 'localStorage'
},
$httpProvider
);
};
Then I ran the app, it sign me in just fine and I can also get the token, but that token is also unauthorized to request.
I decoded the token and saw the value of 'aud', it didn't return "https://outlook.office365.com/". In this url, the author said that "This should be "https://outlook.office365.com/" for the Mail, Calendar, or Contacts APIs"
So what did I miss ?
How you call the Office 365 API in AngularJS?
When signing the user in, you will only get the id_token to authenticate the user.
The aud of id_token is the tenant id (GUID).
To call the Office 365 API, you need to use AugularJS http request.
Here is a sample of sending email using Microsoft Graph API in AngularJS:
// Build the HTTP request to send an email.
var request = {
method: 'POST',
url: 'https://graph.microsoft.com/v1.0/me/microsoft.graph.sendmail',
data: email
};
// Execute the HTTP request.
$http(request)
.then(function (response) {
$log.debug('HTTP request to Microsoft Graph API returned successfully.', response);
response.status === 202 ? vm.requestSuccess = true : vm.requestSuccess = false;
vm.requestFinished = true;
}, function (error) {
$log.error('HTTP request to Microsoft Graph API failed.');
vm.requestSuccess= false;
vm.requestFinished = true;
});
Before calling the API, ADAL.js will acquire another token - access token which you can used to send the email.
UPDATE#1
I also downloaded the sample you mentioned. To run this sample, please ensure you have the Exchange Online > Read and writer user mail Permission assigned in your application.