Gmail Api problems - gmail-api

We are currently trying to create a tool in Google Apps Script using OAuth2 for Authentication. The script is supposed to use the GMail api to carry out some tasks such as moving labels from one account to another.
Documentation we have used says we need to create a service account with domain wide authority and create necessary components to allow it to work (i.e client_id and so on.. )
We are getting the error below is anyone able to help?
Code:
function getGmailService() {
return OAuth2.createService('GmailMigration:' + user_email)
// Set the endpoint URL.
.setTokenUrl('https://oauth2.googleapis.com/token')
.setAuthorizationBaseUrl(CREDENTIALS.auth_uri)
// Set the private key and issuer.
.setPrivateKey(CREDENTIALS.private_key)
.setIssuer(CREDENTIALS.client_email)
// Set the name of the user to impersonate. This will only work for
// Google Apps for Work/EDU accounts whose admin has setup domain-wide
// delegation:
// https://developers.google.com/identity/protocols/OAuth2ServiceAccount#delegatingauthority
.setSubject(CLIENT_EMAIL)
// Set the property store where authorized tokens should be persisted.
.setPropertyStore(PropertiesService.getScriptProperties())
// Set the scope. This must match one of the scopes configured during the
// setup of domain-wide delegation.
.setScope('https://mail.google.com https://www.googleapis.com/auth/gmail.modify
https://www.googleapis.com/auth/gmail.labels https://www.googleapis.com/auth/gmail.insert
https://www.googleapis.com/auth/gmail.addons.current.message.action https://www.googleapis.com/auth/gmail.readonly https://www.googleapis.com/auth/gmail.addons.current.message.metadata https://www.googleapis.com/auth/gmail.metadata');
}
function reset() {
getGmailService().reset();
}
function run() {
var service = getGmailService();
if (service.hasAccess()) {
console.log({Service: service})
var url = 'https://gmail.googleapis.com/gmail/v1/users/{user_id}/labels/{message/label_id}?key='+CREDENTIALS.api_key;
var response = UrlFetchApp.fetch(url, {
headers: {
Authorization: 'Bearer ' + service.getAccessToken()
},
muteHttpExceptions: true
});
var result = JSON.parse(response.getContentText());
Logger.log(JSON.stringify(result, null, 2));} else {
Logger.log(service.getLastError());
}
}
Error:
{
"error": {
"code": 400,
"message": "Precondition check failed.",
"errors": [
{
"message": "Precondition check failed.",
"domain": "global",
"reason": "failedPrecondition"
}
],
"status": "FAILED_PRECONDITION"
}
}
Thanks

Related

Configure OpenAPI/Swagger to get access_token from Azure AD with client credentials flow

We are trying to configure swagger in our .NET 6 API project so that it automatically retrieves the access_token from Azure token endpoint with "client credentials flow". Here is the configuration part in startup.cs
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "register_api", Version = "v1" });
c.SchemaFilter<EnumSchemaFilter>();
var jwtSecurityScheme = new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OAuth2,
Scheme = "bearer",
BearerFormat = "JWT",
Flows = new OpenApiOAuthFlows
{
ClientCredentials = new OpenApiOAuthFlow
{
TokenUrl = new Uri(#"https://login.microsoftonline.com/512024a4-8685-4f03-8086-14a61730e818/oauth2/v2.0/token"),
Scopes = new Dictionary<string, string>() { { #"api://e92b626c-f5e7-422b-a8b2-fd073b68b4a1/.default", ".default" } }
}
}
};
c.AddSecurityDefinition(JwtBearerDefaults.AuthenticationScheme, jwtSecurityScheme);
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{ jwtSecurityScheme, new string[] { #"api://e92b626c-f5e7-422b-a8b2-fd073b68b4a1/.default" } }
});
}
It looks as follows when the user clicks the "Authorize" button the first time. But then, after entering the client_id and client_secret and clicking Authorize button, it shows up the message "Auth Error TypeError: Failed to fetch"
There is something weird with the request that is sent to the token endpoint. The payload includes just the grant_type and the scope. But the client_id and client_secret are base64 encoded and sent in Authorization header:
Is it the reason that the Azure token endpoint refuses to generate the access_token? I have used the same token endpoint and succeeded to get token with postman, but I included all the parameters in the payload.
If that is the case, is it possible to change the configuration of Swagger so that client_id and client_secret are sent in the payload instead (together with the grant_type and the scope) ?

Ignore multi-factor authentication in azure graph API request

I want to validate user credential from Azure AD. It works for users who haven't enable MFA.but MFA enabled users getting below error.
Due to a configuration change made by your administrator, or because
you moved to a new location, you must use multi-factor authentication
to access
So it need a way to ignore MFA ,when we accessing though the graph API
this is my code.
var values = new Dictionary<string, string>
{
{ "grant_type", "password" },
{ "client_secret", appKey },
{ "client_id", clientId },
{ "username", userName },
{ "password", password },
{ "scope", "User.Read openid profile offline_access" },
};
HttpClient client = new HttpClient();
string requestUrl = $"https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token";
var content = new FormUrlEncodedContent(values);
var response = client.PostAsync(requestUrl, content).Result;
if (response.IsSuccessStatusCode)
{
return true;
}
else
{
return false;
}
The correct way to validate user credentials is have the user authenticate interactively through a browser.
This will allow them to go through MFA, login through federation with ADFS etc.
And most importantly, the users do not have to give their password to your app.
The flow you are trying to use only exists in the spec as an upgrade path for legacy applications.
Its usage becomes essentially impossible once MFA is enabled.

IdentityServer4 - RequestedClaimTypes is empty

From the IdentityServer 4 documentation :
If the scopes requested are an identity resources, then the claims in the RequestedClaimTypes will be populated based on the user claim types defined in the IdentityResource
This is my identity resource:
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResources.Phone(),
new IdentityResources.Email(),
new IdentityResource(ScopeConstants.Roles, new List<string> { JwtClaimTypes.Role })
};
and this is my client
AllowedScopes = {
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Phone,
IdentityServerConstants.StandardScopes.Email,
ScopeConstants.Roles
},
ProfileService - GetProfileDataAsync method:
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var sub = context.Subject.GetSubjectId();
var user = await _userManager.FindByIdAsync(sub);
var principal = await _claimsFactory.CreateAsync(user);
var claims = principal.Claims.ToList();
claims = claims.Where(claim => context.RequestedClaimTypes.Contains(claim.Type)).ToList();
if (user.Configuration != null)
claims.Add(new Claim(PropertyConstants.Configuration, user.Configuration));
context.IssuedClaims = claims;
}
principal.claims.ToList() has all the claims listed but context.RequestedClaimTypes is empty, hence the filter by context.RequestedClaimTypes.Contains(claim.Type)) returns no claims.
Client configuration:
let header = new HttpHeaders({ 'Content-Type': 'application/x-www-form-urlencoded' });
let params = new HttpParams()
.append('username', userName)
.append('password', password)
.append('grant_type', 'password')
.append('scope', 'email offline_access openid phone profile roles api_resource')
.append('resource', window.location.origin)
.append('client_id', 'test_spa');
let requestBody = params.toString();
return this.http.post<T>(this.loginUrl, requestBody, { headers: header });
Response type :
export interface LoginResponse {
access_token: string;
token_type: string;
refresh_token: string;
expires_in: number;
}
Someone indicated adding AlwaysIncludeUserClaimsInIdToken = true resolves the issue - I tried and it did not.
What am i missing here? Please help.
You're using resource owner password flow. This is an OAuth2 flow.
OAuth2 is not really suited for "identity data". Therefore a typical setup is to acquire an access token first (like you do), but then you'd use this token to call /userinfo endpoint, which would send back to you that user identity data.
Because of that, on the first request (getting an access token) in your ProfileService, RequestedClaimTypes will not have any claims related to identity resources (e.g. profile, email).
However, on the second call (/userinfo endpoint), your ProfileService would be called again (Caller=UserInfoEndpoint). and RequestedClaimTypes should now contain the identity claims you miss.
If you want to get identity data in a single call, then you should use an OpenID flow (e.g Implicit with response_type=id_token). You would then get an id token with this data right away (given AlwaysIncludeUserClaimsInIdToken was set to true). When your ProfileService is called (Client=ClaimsProviderIdentityToken), RequestedClaimTypes will contain identity claims.
Reference: https://github.com/IdentityServer/IdentityServer4/blob/main/src/IdentityServer4/src/Services/Default/DefaultClaimsService.cs#L62

JWT google-auth-library - bad request (400, failedPrecondition)

I try to get emails from gmail by Google API/Jwt authorizaton (by google-auth-library). It's my code:
var google = require('googleapis');
var gmail = google.gmail('v1');
var key = require('../jwt.keys.json');
var jwtClient = new google.auth.JWT(
key.client_email,
null,
key.private_key,
['https://mail.google.com/',
'https://www.googleapis.com/auth/gmail.readonly',
'https://www.googleapis.com/auth/gmail.modify',
'https://www.googleapis.com/auth/gmail.metadata']
);
jwtClient.authorize(function(err, tokens) {
if (err) {
console.log(err);
return;
}
gmail.users.messages.list({
auth: jwtClient,
maxResults: 5,
q: "",
labelIds: ["INBOX"],
userId: 'me',
}, function(err, response) {
if (err)
return d.reject('The API returned an error: ' + err);
//...
});
});
I get the next error for the gmail.users.messages.list request:
code: 400,
errors: [
{ domain: 'global',
reason: 'failedPrecondition',
message: 'Bad Request'
}]
P.S.
Gmail API is enabled.
Thank you!
You should not be using a JWT for a single user application like Gmail, unless you have a G Suite(Google Apps for Work domain), and that email account is with in it.
Service accounts are their own account and they're not Gmail accounts. They work well for APIs that don't need a user (e.g. maps, search) or when you are using a Google Apps for Work domain and want delegation enabled for all users in the domain (by domain admin, so you don't need individual user authorization).
More detailed answer here: https://stackoverflow.com/a/29778137/6890794
You need to apply Oauth2 to your workflow, not JWT.
https://developers.google.com/gmail/api/auth/web-server

Problems with Azure application manifest trying to authenticate with office-js-helpers in an Outlook web add-in

I'm using office-js-helpers in order to get an OAuth token in my Outlook web add-in so I can use it for OAuthCredentials with the EWS Managed API (code for that is in an Azure App Service using the ASP.NET Web API).
I have configured my app's application registration in my test Office 365 tenant (e.g. mytenant.onmicrosoft.com, which is NOT the same Azure subscription hosting the web app - if that matters) as a Native app with oauth2AllowImplicitFlow set to true. I used a Native app type instead of a Web/API app to bypass an unexpected error indicating my app requires admin consent - even though no application permissions were requested - but that's another story (perhaps I must use Native anyway - not 100% sure).
I made sure that the Redirect URI (aka reply URL) in the app registration points to the same page as the Outlook add-in (e.g. https://mywebapp.azurewebsites.net/MessageRead.html).
Here is my app manifest:
{
"appId": "a11aaa11-1a5c-484a-b1d6-86c298e8f250",
"appRoles": [],
"availableToOtherTenants": true,
"displayName": "My App",
"errorUrl": null,
"groupMembershipClaims": null,
"optionalClaims": null,
"acceptMappedClaims": null,
"homepage": "https://myapp.azurewebsites.net/MessageRead.html",
"identifierUris": [],
"keyCredentials": [],
"knownClientApplications": [],
"logoutUrl": null,
"oauth2AllowImplicitFlow": true,
"oauth2AllowUrlPathMatching": false,
"oauth2Permissions": [],
"oauth2RequiredPostResponse": false,
"objectId": "a11aaa11-99a1-4044-a950-937b484deb8e",
"passwordCredentials": [],
"publicClient": true,
"supportsConvergence": null,
"replyUrls": [
"https://myapp.azurewebsites.net/MessageRead.html"
],
"requiredResourceAccess": [
{
"resourceAppId": "00000003-0000-0000-c000-000000000000",
"resourceAccess": [
{
"id": "e1fe6dd8-ba31-4d61-89e7-88639da4683d",
"type": "Scope"
}
]
},
{
"resourceAppId": "00000002-0000-0000-c000-000000000000",
"resourceAccess": [
{
"id": "311a71cc-e848-46a1-bdf8-97ff7156d8e6",
"type": "Scope"
},
{
"id": "a42657d6-7f20-40e3-b6f0-cee03008a62a",
"type": "Scope"
}
]
},
{
"resourceAppId": "00000002-0000-0ff1-ce00-000000000000",
"resourceAccess": [
{
"id": "2e83d72d-8895-4b66-9eea-abb43449ab8b",
"type": "Scope"
},
{
"id": "ab4f2b77-0b06-4fc1-a9de-02113fc2ab7c",
"type": "Scope"
},
{
"id": "5eb43c10-865a-4259-960a-83946678f8dd",
"type": "Scope"
},
{
"id": "3b5f3d61-589b-4a3c-a359-5dd4b5ee5bd5",
"type": "Scope"
}
]
}
],
"samlMetadataUrl": null
}
I also made sure to add the authority URLs to my add-in's manifest:
<AppDomains>
<AppDomain>https://login.windows.net</AppDomain>
<AppDomain>https://login.microsoftonline.com</AppDomain>
</AppDomains>
This is the code I'm using in the add-in for the authentication with office-js-helpers:
// The Office initialize function must be run each time a new page is loaded.
Office.initialize = function(reason) {
$(document).ready(function () {
// Determine if we are running inside of an authentication dialog
// If so then just terminate the running function
if (OfficeHelpers.Authenticator.isAuthDialog()) {
// Adding code here isn't guaranteed to run as we need to close the dialog
// Currently we have no realistic way of determining when the dialog is completely
// closed.
return;
}
// Create a new instance of Authenticator
var authenticator = new OfficeHelpers.Authenticator();
authenticator.endpoints.registerAzureADAuth('a11aaa11-1a5c-484a-b1d6-86c298e8f250', 'mytenant.onmicrosoft.com');
// Add event handler to the button
$('#login').click(function () {
$('#token', window.parent.document).text('Authenticating...');
authenticator.authenticate('AzureAD', true)
.then(function (token) {
// Consume and store the acess token
$('#token', window.parent.document).text(prettify(token));
authToken = token.access_token;
})
.catch(function (error) {
// Handle the error
$('#token', window.parent.document).text(prettify(error));
});
});
});
};
Now the code in the add-in can properly sign in the user and ask for the required permissions, but after clicking the Accept button on the application authorization step the following error is returned:
AADSTS50011: The reply address 'https://mywebapp.azurewebsites.net' does not match the reply addresses configured for the application: 'a11aaa11-1a5c-484a-b1d6-86c298e8f250'. More details: not specified
The error now returns every time I click the Login button (the user is no longer prompted to sign in). It never did retrieve the token. The full auth URL is:
https://login.windows.net/mydomain.onmicrosoft.com/oauth2/authorize?response_type=token&client_id=a11aaa11-484a-b1d6-86c298e8f250&redirect_uri=https%3A%2F%2Fmywebapp.azurewebsites.net&resource=https%3A%2F%2Fgraph.microsoft.com&state=982599964&nonce=3994725115
What am I doing wrong? Could the issue actually be because the host name of the web app (the redirect URI) does not match the domain of the Azure AD tenant hosting the app registration? If so, how can I grant permissions to Exchange Online from my Azure subscription hosting the web app which does not have Office 365 or Exchange Online? Would I have to add an Azure subscription to my test Office 365 tenant so that it can also host a web application??
From your app manifest, I found that you used https://myapp.azurewebsites.net/MessageRead.html as one of the replyUrls.
And below is the url that you are using to get consent from user.
https://login.windows.net/mydomain.onmicrosoft.com/oauth2/authorize?response_type=token&client_id=a11aaa11-484a-b1d6-86c298e8f250&redirect_uri=https%3A%2F%2Fmywebapp.azurewebsites.net&resource=https%3A%2F%2Fgraph.microsoft.com&state=982599964&nonce=3994725115.
If you observe above url, you mentioned redirect_uri as https://myapp.azurewebsites.net. But redirect_uri should match with at least one of the replyUrls you mentioned in the app manifest.
Try to replace https://myapp.azurewebsites.net with https://myapp.azurewebsites.net/MessageRead.html in authorization url.
I have updated them in below url, if you want you can directly try below url.
https://login.windows.net/mydomain.onmicrosoft.com/oauth2/authorize?response_type=token&client_id=a11aaa11-484a-b1d6-86c298e8f250&redirect_uri=https%3A%2F%2Fmywebapp.azurewebsites.net%2FMessageRead.html&resource=https%3A%2F%2Fgraph.microsoft.com&state=982599964&nonce=3994725115

Resources