How to using Gmail Smtp service with Oauth2 from a web.api - c#

I am try to send emails using GMAIL OAuth protocol but the GoogleWebAuthorizationBroker.AuthorizeAsync method hangs.
I try to run this from server (it is a web.api project that receive the send email information from the client through a htttp POST, then my service should connect to Google Gmail and send the email) Then the service act as a proxy between the Gmail and the client.
I got the client secret stuff from google console for Web Applications.
I use Visual Studio 2013 C# NET4.5. The service has access to the directory where I store the DataStore.
The code that i use is the following and hang over GoogleWebAuthorizationBroker.AuthorizeAsync
DbGoogleDataStore is the same implementation of DataStore as Google (I planning to create my data store in the future in the database, for that I separate it)
public void SendMailUsingOauthProtocol(SmtpConfigurationSettings settings, SmtpSendMessageInputModel filter)
{
var msg = new AE.Net.Mail.MailMessage() // MailMessage
{
Subject = filter.Subject,
Body = filter.Body64,
From = new MailAddress(settings.Username),
};
msg.To.Add(new MailAddress(filter.To));
msg.ReplyTo.Add(new MailAddress(filter.To));
var msgStr = new StringWriter();
msg.Save(msgStr);
// Get from settings the Client secret
MemoryStream stream = new MemoryStream();
StreamWriter writer = new StreamWriter(stream);
writer.Write(settings.AutClientSecret);
writer.Flush();
stream.Position = 0;
//ClientSecrets secret = GoogleClientSecrets.Load(stream).Secrets;
var storeFilePath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase);
storeFilePath = storeFilePath + #"\GmailOauth\";
Debug.WriteLine(storeFilePath);
IDataStore fileStore = DbGoogleDataStore.Factory(storeFilePath, true);
// Create the credential.. this step go authenticate with the user if it is necessary
UserCredential credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
stream,
Scopes,
"user",
CancellationToken.None,
fileStore
).Result;
//Create GMAIL API Service
var service = new GmailService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = ApplicationName
});
var result = service.Users.Messages.Send(new Message { Raw = Base64UrlEncode(msgStr.ToString()) }, "me").Execute();
}
Please, Someone has a idea about how to do this?

Related

GMail API auto authorization for read mail

I am using gmail api to read mail from mail account. But to access mail i have to authorize by selecting or login the mail. If it's only one mail and it's already logged in, it can auto authorize without have select the mail. But if multiple account logged in i have to select the mail to authorize. My goal is to authorize multiple gmail account without selecting the mail. Because this mail reading function will run in crone job. I use following code for authorization.
Thanks in advance.
UserCredential credential;
using (FileStream stream = new FileStream(userfilepath, FileMode.Open, FileAccess.Read))
{
String FolderPath = Convert.ToString(ConfigurationManager.AppSettings["CredentialsInfo"]);
String FilePath = Path.Combine(FolderPath, "APITokenCredentials");
var flow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = cid,
ClientSecret = csecret
},
Scopes = new[] { GmailService.Scope.MailGoogleCom },
DataStore = new FileDataStore(FilePath, true)
});
var token = new Google.Apis.Auth.OAuth2.Responses.TokenResponse()
{
AccessToken = accesstoken,
ExpiresInSeconds = 3600,
Issued = DateTime.Now
};
credential = new UserCredential(flow, usermail, token);
// Create Gmail API service.
var refreshResult = credential.RefreshTokenAsync(CancellationToken.None).Result;
GmailService service = new GmailService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = ApplicationName,
});
return service;
}
In your case the best option is using a Service Account, these types of accounts are intended for non-human access.
Examples:
Running workloads on virtual machines (VMs).
Running workloads on on-premises workstations or data centers that call Google APIs.
Running workloads which are not tied to the lifecycle of a human user.
Documentation
C# Google API Github
.NET Gmail API Documentation

Gmail API Create and Send Email .NET C#

Problem
I am trying to implement the Gmail API into an API application. I created a service account and saved the p12 key and the json credentials. I am getting an exception talking about a failed precondition. I think it might have something to do with the message I'm trying to send.
Code
String serviceAccountEmail = "SERVICE-ACC-EMAIL";
X509Certificate2 certificate = new X509Certificate2("./key.p12", "notasecret", X509KeyStorageFlags.Exportable);
// FileStream stream = new FileStream("./credentials.json", FileMode.Open, FileAccess.Read); // ! Not Used
ServiceAccountCredential credential = new ServiceAccountCredential(new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
User = serviceAccountEmail,
Scopes = new[] { GmailService.Scope.MailGoogleCom }
}.FromCertificate(certificate));
GmailService service = new GmailService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Testing Application",
});
var result = service.Users.Messages.Send(CreateEmail.CreateEmailMessage(), "me").Execute();
Exception
An unhandled exception of type 'Google.GoogleApiException' occurred in System.Private.CoreLib.dll: 'Google.Apis.Requests.RequestError
Precondition check failed. [400]
Errors [
Message[Precondition check failed.] Location[ - ] Reason[failedPrecondition] Domain[global]
]'
Building Mail Message (does not work)
In the CreateEmail.CreateEmailMessage method I build up a new instance of Google.Apis.Gmail.v1.Data.Message. Setting the payload and headers. Take this as reference. I am not sure if this is the way to do it but I can't seem to find a way to create a new message. All I can find is things written in Java or Python which i tried translating over to C#, failing spectacularly
var msg2 = new Message()
{
Payload = new MessagePart()
{
Body = new MessagePartBody()
{
Data = Convert.ToBase64String(Encoding.UTF8.GetBytes("Hello world"))
},
Headers = new List<MessagePartHeader>() {
new MessagePartHeader() { Name = "To", Value = "My email"},
...
Precondition check failed. [400]
with the Gmail api and service accounts normally means that you have not properly setup domain wide delegation to the service account.
Implementing Server-Side Authorization
In your case it may be because you are delegating to a user that is not on your domain.
User = serviceAccountEmail,
Is not the service accounts email address it is the user on your Google Workspace which you want the service account to be impresontating.
string ApplicationName = "Gmail API .NET Quickstart";
const string serviceAccount = "clawskeyboard-smtp#clawskeyboard-api.iam.gserviceaccount.com";
var certificate = new X509Certificate2(#"D:\api-ed4859a67674.p12", "notasecret", X509KeyStorageFlags.Exportable);
var gsuiteUser = "xxx#YourWorkGroupDomain.com";
var serviceAccountCredentialInitializer = new ServiceAccountCredential.Initializer(serviceAccount)
{
User = gsuiteUser,
Scopes = new[] { GmailService.Scope.GmailSend, GmailService.Scope.GmailLabels }
}.FromCertificate(certificate);

Error when login to Gmail with Imap client using Gmail API C#

im working on a project that need to access Gmail using Gmail API .
but i keep getting this error when trying to login Imap:
{"status":"400","schemes":"Bearer","scope":"https://mail.google.com/"}
i sew here OAuth 2 SASL string error ,that someone asked about this issue and said that it solved by puting the right token scope
in my case it didnt work , the error is still popping .
static string[] Scopes = { GmailService.Scope.GmailReadonly };
static string ApplicationName = "Gmail API .NET Quickstart";
static void Main(string[] args)
{
UserCredential credential;
using (var stream =
new FileStream("client_secret.json", FileMode.Open,FileAccess.Read))
{
string credPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
credPath = Path.Combine(credPath, ".credentials/gmail-dotnet-quickstart.json");
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(GoogleClientSecrets.Load(stream).Secrets, Scopes, "Sapir", CancellationToken.None, new FileDataStore(credPath, true)).Result;
Console.WriteLine("Credential file saved to: " + credPath);
}
// Create Gmail API service.
var service = new GmailService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = ApplicationName,
});
// Define parameters of request.
UsersResource.LabelsResource.ListRequest request = service.Users.Labels.List("me");
credential.Token.Scope = "https://mail.google.com/";
ImapClient ic = new ImapClient("imap.gmail.com","astechnetivot#gmail.com", credential.Token.AccessToken,AuthMethods.SaslOAuth, 993, true);
ic.SelectMailbox("INBOX");
Console.WriteLine(ic.GetMessageCount());
MailMessage[] mm = ic.GetMessages(0, 1);
foreach (MailMessage m in mm)
{
Console.WriteLine(m.Subject + " " + m.Date.ToString());
}
ic.Dispose();
Console.Read();
}
Error message seems to happen when you use an expired access token, check your access token and make sure that it is not expired. Here's a related issue having status 400: Gmail XOAUTH 2.0, IMAP AUTHENTICATE returns "status":"400"

Google Oauth error: At least one client secrets (Installed or Web) should be set

I'm using Google's Oauth 2.0 to upload videos to Youtube via our server.
My client ID is a "service account". I downloaded the json key and added it to my solution.
Here is the relevant code:
private async Task Run(string filePath)
{
UserCredential credential;
var keyUrl = System.Web.HttpContext.Current.Server.MapPath("~/content/oauth_key.json");
using (var stream = new FileStream(keyUrl, FileMode.Open, FileAccess.Read))
{
credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
// This OAuth 2.0 access scope allows an application to upload files to the
// authenticated user's YouTube channel, but doesn't allow other types of access.
new[] { YouTubeService.Scope.YoutubeUpload },
"user",
CancellationToken.None
);
}
var youtubeService = new YouTubeService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = Assembly.GetExecutingAssembly().GetName().Name
});
When I run it, I get this error: At least one client secrets (Installed or Web) should be set.
However, in my json there is no "client secret":
{
"private_key_id": "9d98c06b3e730070806dcf8227578efd0ba9989b",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIICdQIBADANBgkqhk etc,
"client_email": "546239405652-8igo05a5m8cutggehk3rk3hspjfm3t04#developer.gserviceaccount.com",
"client_id": "546239405652-8igo05a5m8cutggehk3rk3hspjfm3t04.apps.googleusercontent.com",
"type": "service_account"
}
so I assume I overlooked something.
Maybe I can't use the "service account" ? don't know...
The solution that uses json file is quite similar.
Here is sample that create VisionService using GoogleCredential object created from json file with ServiceAccountCredential.
GoogleCredential credential;
using (var stream = new FileStream(fileName, FileMode.Open, FileAccess.Read))
{
credential = GoogleCredential.FromStream(stream)
.CreateScoped(VisionService.Scope.CloudPlatform);
}
var service = new VisionService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "my-app-name",
});
this sample require two NuGet packages:
Google.Apis.Vision.v1
Google.Apis.Oauth2.v2
I managed to get a service account to work with a P12 file, But would like to know how to use with the JSON file, Or just value from the JSON file to create the certificate.
To get the token
private static String GetOAuthCredentialViaP12Key()
{
const string serviceAccountEmail = SERVICE_ACCOUNT_EMAIL;
var certificate = new X509Certificate2(SERVICE_ACCOUNT_PKCS12_FILE_PATH, "notasecret", X509KeyStorageFlags.Exportable);
var scope = DriveService.Scope.Drive + " https://spreadsheets.google.com/feeds";
var credential = new ServiceAccountCredential( new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = new[] { scope }
}.FromCertificate(certificate) );
if (credential.RequestAccessTokenAsync(CancellationToken.None).Result == false)
{
return null;
}
return credential.Token.AccessToken;
}
And this is how I used the token I got
// Initialize the variables needed to make the request
OAuth2Parameters parameters = new OAuth2Parameters {AccessToken = token};
GOAuth2RequestFactory requestFactory = new GOAuth2RequestFactory(null, "MySpreadsheetIntegration-v1", parameters);
SpreadsheetsService service = new SpreadsheetsService("MySpreadsheetIntegration-v1");
service.RequestFactory = requestFactory;
Not an expert on C# but it looks like you were trying to use the service account to do the OAuth2 web server flow, which shouldn't work.
You probably want to use ServiceAccountCredential instead.
For more information about different Google OAuth2 flows, please refer to the doc for web server, service account, etc.

SMTP and OAuth 2

Does .NET support SMTP authentication via OAuth protocol? Basically, I would like to be able to send emails on users' behalves using OAuth access tokens. However, I couldn't find a support for this in the .NET framework.
Google provides some samples for this in other environments but not .NET.
System.Net.Mail does not support OAuth or OAuth2. However, you can use MailKit's (note: only supports OAuth2) SmtpClient to send messages as long as you have the user's OAuth access token (MailKit does not have code that will fetch the OAuth token, but it can use it if you have it).
The first thing you need to do is follow Google's instructions for obtaining OAuth 2.0 credentials for your application.
Once you've done that, the easiest way to obtain an access token is to use Google's Google.Apis.Auth library:
var certificate = new X509Certificate2 (#"C:\path\to\certificate.p12", "password", X509KeyStorageFlags.Exportable);
var credential = new ServiceAccountCredential (new ServiceAccountCredential
.Initializer ("your-developer-id#developer.gserviceaccount.com") {
// Note: other scopes can be found here: https://developers.google.com/gmail/api/auth/scopes
Scopes = new[] { "https://mail.google.com/" },
User = "username#gmail.com"
}.FromCertificate (certificate));
bool result = await credential.RequestAccessTokenAsync (CancellationToken.None);
// Note: result will be true if the access token was received successfully
Now that you have an access token (credential.Token.AccessToken), you can use it with MailKit as if it were the password:
using (var client = new SmtpClient ()) {
client.Connect ("smtp.gmail.com", 587, SecureSocketOptions.StartTls);
// use the access token
var oauth2 = new SaslMechanismOAuth2 ("username#gmail.com", credential.Token.AccessToken);
client.Authenticate (oauth2);
client.Send (message);
client.Disconnect (true);
}
I got it working by using Microsoft.Identity.Client and MailKit.Net.Smtp.SmtpClient like this using Office 365 / Exchange Online. App registration requires API permissions SMTP.Send.
var options = new PublicClientApplicationOptions
{
ClientId = "00000000-0000-0000-0000-000000000000",
TenantId = " 00000000-0000-0000-0000-000000000000",
RedirectUri = "http://localhost"
};
var publicClientApplication = PublicClientApplicationBuilder
.CreateWithApplicationOptions(options)
.Build();
var scopes = new string[] {
"email",
"offline_access",
"https://outlook.office.com/SMTP.Send" // Only needed for SMTP
};
var authToken = await publicClientApplication.AcquireTokenInteractive(scopes).ExecuteAsync();
//Test refresh token
var newAuthToken = await publicClientApplication.AcquireTokenSilent(scopes, authToken.Account).ExecuteAsync(cancellationToken);
var oauth2 = new SaslMechanismOAuth2(authToken.Account.Username, authToken.AccessToken);
using (var client = new SmtpClient())
{
await client.ConnectAsync("smtp.office365.com", 587, SecureSocketOptions.StartTls);
await client.AuthenticateAsync(oauth2);
var message = new MimeMessage();
message.From.Add(MailboxAddress.Parse(authToken.Account.Username));
message.To.Add(MailboxAddress.Parse("toEmail"));
message.Subject = "Test";
message.Body = new TextPart("plain") { Text = #"Oscar Testar" };
await client.SendAsync(message, cancellationToken);
await client.DisconnectAsync(true);
}
Based on this example:
https://github.com/jstedfast/MailKit/blob/master/ExchangeOAuth2.md
Just adding to the above answer. I also spend lot of time to find out things for sending email using gmail oAuth2 with mailkit in .net. As I am using this to send email to my App users. Thanks to mailkit developers.
Now we need:
Authorization code
Client ID
Client Secret
Refresh Token
Access Token
You can directly get the Client Id and Client Secret from google console by creating your project.
Next you can enable gmail app from the Google Developers OAuth Playground by using your own OAuth credentials in left top setting button.
After that Select and Authorize the API https://mail.google.com/.
Now you can directly refresh token by this http POST request https://developers.google.com/oauthplayground/refreshAccessToken. you will find the parameter in there.
Now you can directly use this code in your C# code using MailKit:
using (var client = new SmtpClient())
{
client.Connect("smtp.gmail.com", 587, SecureSocketOptions.StartTls);
var oauth2 = new SaslMechanismOAuth2(GMailAccount, token.AccessToken);
client.Authenticate(oauth2);
await client.SendAsync(mailMessage);
client.Disconnect(true);
}
Now you will be able to send email through your gmail account from server side.
Using MailKit as referenced in the other answers, I was hitting an authentication issue requiring more scopes to be requested from Gmail. For anyone experiencing "Authentication Failed error" with either of the other answers, this answer uses the Gmail API instead in order to avoid requesting more scopes.
Using some pieces from this answer: https://stackoverflow.com/a/35795756/7242722
Here's a complete example which worked for me:
var fromAddress = new MailboxAddress(fromName, fromEmail);
var toAddress = new MailboxAddress(toName, toEmail);
List<MailboxAddress> ccMailAddresses = new List<MailboxAddress>();
if (ccEmails != null)
foreach (string ccEmail in ccEmails)
ccMailAddresses.Add(new MailboxAddress(string.Empty, ccEmail));
var message = new MimeMessage();
message.To.Add(toAddress);
message.From.Add(fromAddress);
message.Subject = subject;
var bodyBuilder = new BodyBuilder();
bodyBuilder.HtmlBody = body;
bodyBuilder.TextBody = HtmlUtilities.ConvertToPlainText(body);
message.Body = bodyBuilder.ToMessageBody();
foreach (MailboxAddress ccMailAddress in ccMailAddresses)
message.Cc.Add(ccMailAddress);
GoogleAuthorizationCodeFlow authorizationCodeFlow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets()
{
ClientId = <ClientId>,
ClientSecret = <ClientSecret>
},
});
TokenResponse tokenResponse = await authorizationCodeFlow.RefreshTokenAsync("id", <RefreshToken>, CancellationToken.None);
UserCredential credential = new UserCredential(authorizationCodeFlow, "id", tokenResponse);
var gmailService = new GmailService(new BaseClientService.Initializer()
{
ApplicationName = <AppName>,
HttpClientInitializer = credential,
});
Google.Apis.Gmail.v1.Data.Message gmailMessage = new Google.Apis.Gmail.v1.Data.Message();
gmailMessage.Raw = Utilities.Base64UrlEncode(message.ToString());
var result = gmailService.Users.Messages.Send(gmailMessage, "me").Execute();

Categories

Resources