Access to Calendar API return 401 Unauthorized - authorization

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.

Related

invalid_grant exchanging authorization code for access and refresh tokens

My application is using OAuth to access the Youtube Data API. My OAuth callback is written in node and uses the OAuth2Client class from the "googleapis" npm package to exchange the authorization code for the access and refresh tokens.
Everything was working fine up to last week until suddenly I started getting the "invalid_grant" response during the authorization code exchange. I have tried everything to resolve this and am running out of ideas. My callback executes as a cloud function so I don't think that it would be out of sync with NTP.
My OAuth consent screen is in "Testing" mode and my email address is included in the test users. The odd thing is that even though the authorization code exchange fails, my Google account's "Third-party apps with account access" section lists my application as if the handshake succeeded.
Is there a limit to how many refresh tokens can be minted for my application? I am testing my implementation of incremental authorization so I have been going through the OAuth flow often.
Edit
I've included my code for generating the auth URL and exchanging the authorization code below. The invalid_grant occurs during the call to "oauth2.getToken"
async startFlow(scopes: string[], state: string): Promise<AuthFlow> {
const codes = await oauth2.generateCodeVerifierAsync();
const href = oauth2.generateAuthUrl({
scope: scopes,
state,
access_type: 'offline',
include_granted_scopes: true,
prompt: 'consent',
code_challenge_method: CodeChallengeMethod.S256,
code_challenge: codes.codeChallenge
});
return { href, code_verifier: codes.codeVerifier };
}
async finishFlow(code: string, verifier: string): Promise<Tokens> {
const tokens = await oauth2.getToken({ code, codeVerifier: verifier })
return {
refresh_token: tokens.tokens.refresh_token!,
access_token: tokens.tokens.access_token!,
expires_in: tokens.tokens.expiry_date!,
token_type: 'Bearer',
scopes: tokens.tokens.scope!.split(' ')
};
}
"oauth2" is an instance of OAuth2Client from "google-auth-library". I initialize it here:
export const oauth2 = new google.auth.OAuth2({
clientId: YT_CLIENT_ID,
clientSecret: YT_CLIENT_SECRET,
redirectUri: `${APP_URI}/oauth`
});
Looking at the logs, the only out of the ordinary thing I notice is that the application/x-www-form-urlencoded body looks slightly different than the example https://developers.google.com/identity/protocols/oauth2/web-server#exchange-authorization-code
The POST request to "https://oauth2.googleapis.com/token" ends up looking like this:
code=4%2F0AX4XfWiKHVnsavUH7en0TywjPJVRyJ9aGN-JR8CAAcAG7dT-THxyWQNcxd769nzaHLUb8Q&client_id=XXXXXXXXXX-XXXXXXXXXXXXXXX.apps.googleusercontent.com&client_secret=XXXXXX-XXXXXXXXXXXXXXX-XX_XXX&redirect_uri=https%3A%2F%2Fapp.example.com%2Foauth&grant_type=authorization_code&code_verifier=KjOBmr4D9ISLPSE4claEBWr3UN-bKdPHZa8BBcQvcmajfr9RhWrgt7G429PLEpsP7oGzFGnBICu3HgWaHPsLhMkGBuQ2GmHHiB4OpY2F0rJ06wkpCjV2cCTDdpfRY~Ej
Notice that the "/" characters are not percent-encoded in the official example, but they are in my requests. Could this actually be the issue? I don't see how the official google auth library would have an issue this large.
The most common cause for the invalid_grant error is your refresh token expiring.
If you check oauth2#expiration you will see the following
A Google Cloud Platform project with an OAuth consent screen configured for an external user type and a publishing status of "Testing" is issued a refresh token expiring in 7 days.
Once you set your project to production your refresh tokens will stop expiring.
Is there a limit to how many refresh tokens can be minted for my application?
No but you have a limit of 100 test users.

Accessing secured FeatureLayer on ArcGIS online with JavaScript API

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.

How to fix "The OAuth client was not found" error from a Bing Ads script

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.

Perform a log-out using stormpath $http

I am trying to revoke oauth2 tokens using the stormpath API. Server-side authentication is performed using stormpath + express. Here is my request.
function revokeOauthTokens(params) {
// Revoke the oauth2 access. and refresh tokens
var oauthLogoutReq = {
method: 'POST',
url: params.apiBaseUrl + '/logout',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: 'grant_type=refresh_token&refresh_token='
+ params.oauth_tokens.refresh_token
}
return $http(oauthLogoutReq);
}
Where apiBaseUrl is my nodejs base url and ouath_tokens contains the response granted by a request to /oauth/token endpoint.
Looking at the documentation at the following links leaves me confused.
http://docs.stormpath.com/nodejs/express/latest/logout.html
http://docs.stormpath.com/guides/token-management/
Thanks.
This is a great question. As you’ve seen, express-stormpath is using secure, http-only cookies for token storage, and this doesn’t work for Cordova, Electron, etc, where cookie storage isn't implemented to spec. The alternative is local storage, or some other storage API that is provided to you (hopefully a secure one!).
The express-stormpath library does provide a /logout route, and it does revoke tokens, but it’s looking for the tokens in cookies. We need to add a new route, likely /oauth/revoke, to support explicit token revocation.
This is pretty easy to add right now as a custom route handler, and I’m including a link below. But please be aware that express-stormpath uses local token validation by default. This is done for speed (no roundtrip to our API) but the caveat is that your local server will NOT know that the tokens have been revoked, and can technically still be used for authentication if a malicious third party has stolen them from your client. If this is a concern you want to to address, you should opt-in to stormpath validation, which will always require a check against our token database. This is documented here:
http://docs.stormpath.com/nodejs/express/latest/authentication.html#token-validation-strategy
All that said, here is the route handler that you could wire up as /oauth/revoke, and have your Electron client use it to revoke the tokens when the user logs out:
'use strict';
var revokeToken = require('express-stormpath/lib/helpers/revoke-token');
function defaultResponder(res, err) {
if (err) {
console.error(err); // or your system logger
return res.status(err.status || 400).json({
message: err.developerMessage || err.message
});
}
res.end();
}
/**
* Implements the expected behavior of the /oauth/revoke endpoint, and requires
* that token_type be defined. This assumes that you are using the express-stormpath
* module, so that your Stormpath client and configuration context is available.
*
* #param {Object<ExpressRequest>} req Express JS Request
* #param {Object<ExpressResponse>} res Express JS Response
*/
function revokeTokens(req, res){
var client = req.app.get('stormpathClient');
var config = req.app.get('stormpathConfig');
var secret = config.client.apiKey.secret;
var token = req.body.token;
var token_type = req.body.token_type;
if (!token || ! token_type) {
defaultResponder(res, {
message: 'token and token_type fields are required'
});
}
if (token_type === 'access_token') {
revokeToken.revokeAccessToken(client, token, secret, defaultResponder.bind(null, res));
} else if (token_type === 'refresh_token') {
revokeToken.revokeRefreshToken(client, token, secret, defaultResponder.bind(null, res));
} else {
defaultResponder(res, {
message: 'invalid token_type'
});
}
}
module.exports = revokeTokens;
If you find that you don't want to use express-stormpath and would like to use something more direct, you can drop down to the Stormpath Node SDK and use it for token revocation:
https://docs.stormpath.com/nodejs/jsdoc/AccessToken.html
Or you can make DELETE requests directly against our API:
https://docs.stormpath.com/rest/product-guide/latest/auth_n.html#revoking-access-and-refresh-tokens
In both cases, you would be doing that from your server, not the Electron application.
I hope this helps!
-Robert

Not able to redirect to client side with token from server(express) side route

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.