C# GMAIL API Service Account Modify - unauthorized_client - c#

I am just starting out with the c# Google.Apis.Gmail.V1 classes and using a service account to read a mailbox.
My code works fine when I use the following snippet
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(ServiceAccountEmailAddress)
{
User = "abc#test.domain.com",
Scopes = new[] { "https://www.googleapis.com/auth/gmail.readonly" }
}.FromCertificate(certificate));
With that code I can call the following successfully
if (credential.RequestAccessTokenAsync(CancellationToken.None).Result)
{
}
But I need to modify the email messages so I changed the scope from
https://www.googleapis.com/auth/gmail.readonly
to
https://www.googleapis.com/auth/gmail.modify
I now get an exception when requesting the access token
{"Error:\"unauthorized_client\", Description:\"Unauthorized client or scope in request.\", Uri:\"\""}
I have checked the service account (*.iam.gserviceaccount.com) in the Google Developers Console and I have tried all options for permissions including OWNER which should give me Full access to all resources but no joy.
I think I am just missing a simple step but I am unsure of where to look next.

TL;DR
I would read through this, but here is the short version. I know this is an older post, but hopefully it finds you!
If you have not updated/white-listed the service account's privileges/scopes in the Google Admin Console you will need to do that, make sure the domain has API access enabled, make sure the service account is setup properly, when creating the "certificate" object be aware its parameters so that it is being instantiated correctly, check the permissions on the account being impersonated and finally make sure you've made an appropriate Google Apps service account key (could have easily made an inappropriate key type.)
White-listing Google APIs in the Admin Console
This gives the Google Apps service account the abilityto use whatever scopes you provide in your Google Apps domain.
Login to the Google Apps Admin Console by using the following link.
https://admin.google.com/
The Google Apps user account must have sufficient privileges to modify domain related settings. It does not have to be the account used to create the Google Apps project in the developer console. If the account does not have privilege you will be directed to a completely different screen with no options to click on varying domain controlling web apps like "Security", "Roles", "Support", "Groups" and etc. Instead you'll dumped onto a page that shows things like "Gmail", "Drive", "Docs" and etc. that is typical user apps. The current link it drops you at is https://apps.google.com/user/hub
Click “Security.”
Click “Show more” option at the bottom of the security options list.
Click “Advanced Settings” to get the more options.
Select the “Manage API client access” link.
Now certain API scopes must be white-listed for the desired service account. In the “Client Name” text box provide the service account’s client ID. The client ID is obtained in the developer console. In the “One or more API scopes” add the desired scopes; comma delimited.
Note, if there are existing scopes they will be removed so be sure to re-add any that will be needed.
Enable Domain Wide API Access
Login to the Google Apps Admin Console by using the following link.
https://admin.google.com/
Go to the “Security” page.
Under “API reference” section
Make sure that “Enable API access” is enabled.
Creating an Appropriate Google Apps Service Account Key (Probably this)
Go to the Google Developer Console. Login as the Google Apps user that created the Google Apps project/service account. https://console.developers.google.com/
Navigate to the particular project with which you created the service account.
Click the "Service Account" button on the left of the project's page to bring up a page with all of the project's service accounts.
Click the vertical ellipse widget all the way to the right of the desired service account's row. Select “Create Key.”
Select .p12 key as it looks like this is what you're trying to use. Click "Create." Be sure to protect this key.
I have found that if the key is not created this way then it leaves open the possiblity for making either an API key or an OAuth 2.0 client/user key. These are the wrong types of keys to use in this case you would need to have created a service account key. The way outlined above forces you to create a service account key.
Modifying the Existing Google Apps Service Account's Settings
I'm not going over how to setup the actual service account, one thing you may need in your case is to make sure that the service account has domain wide delegation enabled. This is toggled in the Google Developer Console. Should be pretty easy to find.
Code
You do not provide your entire code base for creating the token, so just want to add a few things you might be doing improperly.
Make sure when you create the certificate that the secret you provide is the default "notasecret" string. This secret is currently the default value provided by all keys distributed by Google and is immutable during key creation. I had a link to prove this, but have since lost it.
X509Certificate2 certificate = new X509Certificate2(certificateFilePath, "notasecret", X509KeyStorageFlags.Exportable);
Just trying to advocate proper coding. While I have found some bugs in the past with Google's constant values that required additional string manipulation (adding additional slashes.) You should really be using the string constants that they provide in place of literals. I only say to use these because it provides a layer of abstraction, who is to say Google will never change the literal; unlikely.
In your case the new scope is:
GmailService.Scope.GmailModify
While the old scope was:
GmailService.Scope.GmailReadonly
Otherwise, everything code wise looks good to me.
Another thing to try would be to make sure that the actual Google Apps user account being impersonated by the service account has sufficient privileges. I would suspect a different error if this were the case, would be getting a 403 in the response instead. Anyway, in your case this is the "abc#test.domain.com" account. Once again you would go to the Google Admin Console, check its roles make sure it has sufficient roles checked for whatever it is you're trying to do. I don't know what specifically you'll need in this case, best bet would be to give it the same permissions as the "Super Admin" role then remove permissions as you go to see what it might actually need. Otherwise, if possible just give it "Super Admin."
If I was a gambler I would put my money on an inappropriately created service account key. I just recently ran into this and it was the only thing that produced the same exact error you're receiving. Other things would get me the same "Description" value in the response token, but not the same "Error" value. I'm not really even sure how the culprit key was made, because I didn't make it. I just know the fix was to recreate a new key with the steps above and that fixed the issue.

Related

Need to add Offline and Touch ID to Microsoft Authentication Layer

I have some Xamarin C# code that authenticates users against the company directory, it's basically the code found in the Microsoft tutorial, and right now we're iOS-only:
App.xaml.cs
PublicClientApplicationOptions options = new PublicClientApplicationOptions()
{
ClientId = MyAppClientId,
TenantId = MyAppTenantId
};
var builder = PublicClientApplicationBuilder.CreateWithApplicationOptions(options);
if (!string.IsNullOrEmpty(iOSKeychainSecurityGroup))
{
builder = builder.WithIosKeychainSecurityGroup(iOSKeychainSecurityGroup);
}
PCA = builder.Build();
ViewModel.cs
string Scopes = "User.Read";
var scopes = Scopes.Split(' '); // Yeah, overkill
// First, attempt silent sign in
// If the user's information is already in the app's cache,
// they won't have to sign in again.
string accessToken = string.Empty;
try
{
var accounts = await App.PCA.GetAccountsAsync();
// PCA.GetAccountsAsync() returned [List<IAccount> #=0]
if (accounts.Count() > 0)
{
var silentAuthResult = await App.PCA
.AcquireTokenSilent(scopes, accounts.FirstOrDefault())
.ExecuteAsync();
accessToken = silentAuthResult.AccessToken;
}
}
catch (MsalUiRequiredException)
{
// This exception is thrown when an interactive sign-in is required.
// Don't need to do anything, we will notice the empty access token later
}
if (string.IsNullOrEmpty(accessToken))
{
// Prompt the user to sign-in
var interactiveRequest = App.PCA.AcquireTokenInteractive(scopes);
// PCA.AcquireTokenInteractive(scopes) returned Microsoft.Identity.Client.AcquireTokenInteractiveParameterBuilder
if (authUiParent != null)
{
interactiveRequest = interactiveRequest
.WithParentActivityOrWindow(authUiParent);
}
try
{
var authResult = await interactiveRequest.ExecuteAsync();
}
catch (MsalClientException clientException)
{
// When I entered the wrong password, and then hit cancel:
// Or, when I got the "you need admin permissions" error and then hit cancel:
/*
interactiveRequest.ExecuteAsync() threw MsalClientException [error code "authentication_canceled"]
Exception MsalClientException: User canceled authentication.
*/
}
}
That's working great, now I need it to be slightly different (of course).
1) First of all, we need an "offline" mode. If the user wants to access the app in a place that has no Internet, we want them to enter their username and password which will be compared against the last known good value for that user. Right now we are using an internal encrypted database to store last known good values for comparison. Yes, there is a security hole here, if we've disabled a user's account on the server they can continue to log in as long as they disable the Internet on their mobile device - we have other ways to minimize this problem that I won't go into here.
2) Secondly, we want to allow touch ID. But even if the fingerprint verifies the identify of the user, we still want to check if that user has been disabled at the server - so we need to send the "last known good values" for that user to the server for verification. Of course, if there is no Internet, the fingerprint will be enough. I'm assuming this means we need a screen before calling AcquireTokenInteractive() where we give the user a chance to use Touch ID, with a button that says "nope, I want to type in my username and password please"
3) Finally, even when we have Internet and the user has chosen to not use Touch ID, we want them to enter their password every time. We want to remember the username of the most-recently-logged-in user and fill that in for them to speed things up, but for security reasons we need a password every time.
It seems security of your application is your biggest concern. For this reason I'll go in to some detail on security and identity to help your decision making.
Issue 1: Offline Mode
I'm afraid the approach you've taken for offline access isn't advisable. In fact, in MSAL we actively mitigate it by using the System Browser.
Security Issues with Embedded Browsers and Forms Based Authentication in Mobile Applications
When a user signs in to a mobile application using either an embedded webview or a form based authentication, that application can access the plain text username and password entered in to the application. If a user downloads an application that is acting like a legitimate application from your company, those credentials can be stolen without the user being aware. This is a security vulnerability, and one that is severe enough that Google has taken the step of blocking all apps that use embedded webview or forms based authentication.
System Browsers and Authentication
To prevent this storage of credentials by applications Google, Microsoft, and others have switched to the System Browser to collect credentials. We use the new ability of the operating system on mobile devices to display a web sign-in experience on top of your application. This appears native to the user but is actually the browser of the operating system. Because neither the application nor Microsoft has access to the browser of the operating system, the credentials entered by your users are safe. You'll see most modern applications using this pattern.
This will also prevent you from storing username and password of your users and is by design. Please don't store your user's credentials anywhere.
How To Allow Offline Access for Applications
To achieve your scenario correctly there are two options we've seen apps use:
The most popular pattern we've seen is for applications to ask the user to set a PIN to access the application when they first sign-in or as an option in Settings. Often times the user is asked to set a PIN as well as Touch ID/Face ID if Touch ID/Face ID fails or is reset. The user then uses the PIN to access the app on each launch, and this also works when internet isn't available. This PIN is stored securely and encrypted on the device. As soon as the internet is available the application should call acquireTokenSilently() to ensure the user still has access to the resource. If they don't, you should prompt the user to sign in again. Many banks and other highly regulated industries use this pattern as the best compromise between user experience and security.
The next option is to use the resiliency we've built in to the library for companies and application developers that want the user to maintain access to resources in case of an outage. We allow companies and application developers to configure a token lifetime that is longer than our default token lifetime for access tokens and refresh tokens. When a user isn't able to access the internet to get a new access token, the extended lifetime can allow the user to continue to use the application.
This has the added benefit of working if your APIs are local to your environment but the identity provider is cloud based or if Azure AD is suffering an outage but the rest of the internet is working. Your internal APIs will accept the tokens so your users can continue to work even if the internet is inaccessible. Both the API and your app will need to use MSAL in order for your API to honor the extended lifetime specified in the token. Keep in mind we have maximum value for the access token of one day. If your internet outage is longer than that, I recommend using option 1 above.
Issue 2: Check User With Identity Service When Using Touch ID
This answer is fairly easy. Just use acquireTokenSilently() after the Touch ID is used. This will acquire a new token for the resource you requested the token for. For default token values, the access token will be refreshed every 1 hour. I do not recommend forcing authenticating every Touch ID as that will introduce significant slowdown and cellular usage. If you just want to check the user is present, I recommend you use a locally stored PIN as discussed above.
Issue 3: Force Authentication if Touch ID is not enabled
You can force authentication at any time by using acquireToken()with the appropriate values. See the code sample here for the method acquireTokenInteractively().
However, a small amount of warning here on usability:
The Problem With Using Password Frequently To Test User Access
The identity service is used to check if a user has access to the resources, not to check if the user has access to the device or if the user is still the user from a previous session. For that you should use the PIN method I discuss above, along with Touch ID/Face ID if available. Why?
For true security, a password should be combined with 2FA. Research indicates that 99% of illegal access can be mitigated by 2FA. However, when a user has to use 2FA for a sign-in it increases the friction they have to do endure to sign-in. In fact, relying on passwords at all is problematic.
Your use case seems to indicate you want the maximum amount of security for your application. That necessitates using 2FA if possible. However, moving to the best security posture for your application would also make using a password at every app launch very difficult for your users.
The recommended pattern would again be PIN or Touch ID/Face ID along with a password prompt after either a change to the user's access to a resource or after a long period of time.

asp C# Application Default Credentials are not available

I am running Google Translate API in C#.
Running locally on my computer the next code works, but online on a server it throws the following error:
using Google.Cloud.Translation.V2;
TranslationClient client = TranslationClient.Create();
var response = client.TranslateText(sentence, targetLanguage, sourceLanguage: sourceLanguage);
"The Application Default Credentials are not available. They are available if running in Google Compute Engine. Otherwise, the environment variable GOOGLE_APPLICATION_CREDENTIALS must be defined pointing to a file defining the credentials. See https://developers.google.com/accounts/docs/application-default-credentials for more information."
Locally this runs just by installing Cloud SDK Installer which does all the settings, there is no need for authentication in code.
On the server, should I use instead OAuth 2.0 or Service account keys ?
Can someone assist me on how to solve this?
EDIT: Can someone confirm to me if it is necessary to have access to the local server to run commands in command line like here https://cloud.google.com/storage/docs/authentication ? This would be pretty ridiculous, instead of just writing code. For example Youtube API does not require local access.
Follow directions to get json file:
https://cloud.google.com/translate/docs/reference/libraries
Then run this code first:
System.Environment.SetEnvironmentVariable("GOOGLE_APPLICATION_CREDENTIALS", "c:\mypath\myfile.json");
To generate a private key in JSON or PKCS12 format:
Open the list of credentials in the Google Cloud Platform Console.
OPEN THE LIST OF CREDENTIALS
Click Create credentials.
Select Service account key. A Create service account key window
opens.
Click the drop-down box below Service account, then click New service account.
Enter a name for the service account in Name.
Use the default Service account ID or generate a different one.
Select the Key type: JSON or P12.
Click Create. A Service account created
window is displayed and the private key for the Key type you
selected is downloaded automatically. If you selected a P12 key, the
private key's password ("notasecret") is displayed.
Click Close.
You can find more details here
https://cloud.google.com/storage/docs/authentication
Its all in the errormessage. You have two options
Run the Google Compute Engine on the machine you have your program running on and input your credentials there.
Use a service account and set the "GOOGLE_APPLICATION_CREDENTIALS" environment variable to reference your credentials file (which is a .json file that you can download from the google developer console.)
PS: Do not store your credentials file anywhere on the server where it may be accessed by someone else!
You must download API key from
https://console.developers.google.com/iam-admin/serviceaccounts
After that download .P12 file file to use it in your code
var certificate = new X509Certificate2(#"key3.p12", "notasecret", X509KeyStorageFlags.Exportable);
notasecret is default password
The easiest answer to my question , to avoid local settings on the server, is the third option of using the Translation API described below: using API keys.
This means just a simple POST to an endpoint that has the API key in the link.
https://cloud.google.com/docs/authentication/#getting_credentials_for_server-centric_flow
https://cloud.google.com/docs/authentication/api-keys

How can I register a new user with a user-defined unique identifier when leveraging OAuth code flow?

I'm building a sign-up / login flow for a web site. I plan to use Facebook as my identity provider instead of rolling my own.
I have a good feel for the server-side login flow with Facebook:
Call FB login API to get a code
Exchange the code for a user access token
Inspect the user access token
Validate the user access token details
After these steps, I'd like to check if the authenticated user is already registered in my system. If yes, simply return a newly generated bearer token, so the user can make subsequent requests to resource servers to access protected data.
If the user is not registered in the system, however, then I'd like to register them by creating a database entry. Before creating this entry though, I'd like to collect one piece of information from the user. Namely, I'd like for them to tell me their desired 'username'. I will use this unique username as my database primary key.
I'm not 100% sure on how to securely ask the user for their desired username before creating the database entry. This is my question. :)
One thought I had was to create a "redemption code". This code would be encrypted and contain the user initialization details, a secret only the server would know, and a timestamp. Something like this:
code: {
mySecret: "super-secret-value",
expirationDate: "date-value",
user: { ... },
}
After seeing the user is not in my system, I'd respond with the code + redirect the client to a page where they'd be able to specify their username. Upon submitting their username + code back up to the server, I could decrypt the code, and validate mySecret to determine the code is not tampered. If all is good, create the user in the database with the user information from the redeemed code. Lastly, I'd generate a new bearer token for the user and send it to the client.
Questions
Is my proposed redemption code strategy a secure way of requesting a username before creating the backend DB entry?
If not, what would be?
If yes, what is a secure encryption/decryption routine to use for this purpose in C#?
Flow Sequence
Steps 1-4 from above correspond to "Login" through "Validate" arrows.
My proposed redemption code strategy corresponds to the purple arrows.
Red text corresponds to Facebook specific nomenclature.
Note, Stack Overflow does something very similar to what I want to do. Before creating your account on SO, it will ask you for your desired Display Name (this happens after authenticating via Facebook, Google, etc.). After submitting your display name, your account is registered.
Use open source IdentityServer3.
Whatever flow you choose its already standardized in their server. Including (if you want or need) OpenID, OAuth2 etc.

Adding Roles to MobileServices in Azure (ACS)

I am trying to learn about how to do Roles in ACS. I have a Windows Store App (not a website :-), and I need Administrator and a Member roles - but finding even very basic documentation or tutorials for something like this is proving very difficult for me.
All I have found are a bunch of references to ASP.NET or Azure Websites stuff - which I can't use, since a Store app is not a Website - and doesn't use ASP.NET.
I have Azure Mobile Services for my Windows Store app. Is there any information that you know of that could be of help?
I'm surprised that Roles don't seem to be covered in the samples/reference/tutorials section anywhere on the Azure website (unless I've missed it - several times).
Take, for example, this scenario. Here, in the Script tab of the Management Portal, I insert some text into the table associated with the currently logged in user (via the insert function):
var SendGrid = require('sendgrid').SendGrid;
function insert(item, user, request) {
item.userId = user.userId;
request.execute(
{
success: function() {
request.respond();
// Send email in the background.
sendEmail(item);
}
}
);
}
The above code inserts some text into the table and then sends an email to that user. But, how could I adapt this code, so that it would (pseudo code):
if item.text == "administrator"
then insert userid into admin column
else
then insert userid into member column
Any help at all will be highly appreciated.
It is not really an answer, but it is also too long for a comment, and I think it is really important.
First of all, when using authentication from the Windows Azure Mobile Services, you are not using Windows Azure Access Control Service. So, there is no Active Directory, there are no (additional) claims when one uses Mobile Services. This must be very clear, because I see continuous misunderstanding and confusion. #Joey still asks for, and refers ACS as federation provider for his application, which is not the case if he relies on the Mobile Services authentication mechanism.
So, the roles assignment must totally be handled by your application logic with additional table as Jim describes.
However roles assignment in general is interesting topic, and really is not part of any tutorial or documentation. And it is not part of such, because Role Assignment is part of business logic of the application itself, not part of the Authentication service. There is very similar question here, which however again does not answer in detail how to assign roles. The foundation and main issue to solve is: How to assign administrator role. Everything else, can be solved by adding appropriate UI for the application administrators. Still, this question is not subject to a short SO answer.

MVC3 + How to get the current logged on user's user name

I am new to MVC and actually new to web development all together. I have about 7 years of development experience but in services, database, object models, etc.. basically middle-tier and back-end development. I am trying to learn ASP.NET and decided to build a site using MVC3 for a personal site for myself. I will be hosting this from an account at dotnet-hosts.com. Here is my question... I don't have a domain and I will be using the built in membership provider. I noticed in the auto generated code that was created when I added the project template that in the AccountController in the method ChangePassword (ChangePasswordModel model) there is this line of code...
MembershipUser currentUser = Membership.GetUser(User.Identity.Name, true /* userIsOnline */);
My question is specifically around User.Identity.Name, this looks like it would be returning the Windows user name just like Environment.UserName would. The Visual Studio template I used is the (Mobile Ready HTML5 MVC.NET) as I want to be able to support clients from any device...Windows PC, Apple, Windows Phone, iPhone, etc... If the call to User.Identity.Name is correct then I would like to ask how does this work on devices that are not Windows like an iPhone? If my assumption is correct that this will only work for Windows computers with a domain then how can I achieve this? would I need to perhaps use some caching? If so could I maybe grab the user name and their IP address to be used as the cache key from the Authentication page?
My high level question is... How do I get the current logged in user's userName regardless of the device/platform? I know this question is probably not written well and may be hard to understand... I apologize for that. I am new to web development and trying to get my feet wet and would like to start to the latest technology.
The call is correct. The User.Identity.Name is filled out by whatever authentication provider is in use - Windows authentication, Forms authentication, some custom authentication provider, or whatever. It isn't bound to a specific user "type". The authentication provider has the responsibility of making sure the Identity object corresponds to the current user on every request. Usually that part is taken care of using a combination of cookies and database.
The MVC template (although I haven't had a look at the template since MVC 2) uses ASP.NET's Membership class, which in turn uses a membership provider - for example SqlMembershipProvider or ActiveDirectoryMembershipProvider - the former stores your users' credentials (username and password etc.) in an SQL Server database, the latter uses Active Directory (i.e. primarily Windows logons). SqlMembershipProvider is the default, and MVC is set up to use a local SQLExpress database file as its user store.
The authentication provider that's implemented in the template project uses FormsAuthentication, which does the login procedure through a simple HTML form (the one in the LogOn view) and keeps the user signed in by way of an encrypted cookie. Works on any platform.
The setup for both FormsAuthentication and SqlMembershipProvider can be found in web.config (the one in the root of the site). There you can find the connection strings for the SQLExpress database (and e.g. change them to use a "real" SQL Server if needed), the timeout for logins etc.
(Note that you can do a lot of that configuration easily in a GUI through the "ASP.NET Configuration" button in the toolbar of Solution Explorer in Visual Studio - it also provides an easy way to set up the first users).
In short, it's all ready to go - and doesn't lock out non-Windows users.
Like you said User.Identity.Name is indeed correct. for returning the logged in users name. But the membership section like you said, provides only windows accounts. You can use similar without the user of windows accounts, to work in every scenario, and can still verify against windows if present. If you call it without membership, and follow the default MVC3 template it should work fine.
String Username = User.Identity.Name;
When you log on, using the template MVC3, it creates an authcookie. See account controller code. Here, two parameters are passed into it. The username, and to persist (when browser is closed - login is still cached).
The username is a string field, which is what is called by User.Identity.Name and infact, anything can be put into it, and is not in anyway linked to Windows login.
You could test the login via method you desire, and if yes, set a cookie using the authcookie method. (its encripted). And set the username to what ever you want. And if your verification of the user fails, dont create one, and redrect back to page.
See the example code. This is all from memory, as I dont have code infront of me for reference. But its all in the account controller, Login Action.
When the cookie is set, The users login state is cached for the session. You will need to ensure the user is logged in when visiting a webpage. Otherwise loggin in will be pointless. This is a simple attribute on the controller/action.
Note: dont do this to the Account/logon controller, as you wont be able to visit the logon page, as you are not logged in.
[Authorize]
public ActionResult DoSomething()
{
// ...
}
Hope I have helped.

Categories

Resources