Yesterday I created a ClientID & Client Secret (using this guide) to authenticate a desktop application (C#/.NET) to send emails from a Gmail account.
My method, which authenticates and sends an email, looks as follows:
public static async System.Threading.Tasks.Task<int> SendEmailOAuth2Async(string sFromMailAddress, string sClientID, string sClientSecret)
{
var credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
new ClientSecrets
{
ClientId = sClientID,
ClientSecret = sClientSecret
},
new[] { "email", "profile", "https://mail.google.com/" },
"user",
CancellationToken.None
) ;
var jwtPayload = GoogleJsonWebSignature.ValidateAsync(credential.Token.IdToken).Result;
var username = jwtPayload.Email;
var mailMessage = new MimeMessage();
mailMessage.From.Add(new MailboxAddress("from name", sFromMailAddress));
mailMessage.To.Add(new MailboxAddress("to name", "someone#outlook.com"));
mailMessage.Subject = "Automated Mail with OAuth";
mailMessage.Body = new TextPart("plain")
{
Text = "Hello"
};
using (var client = new SmtpClient())
{
client.Connect("smtp.gmail.com", 587, SecureSocketOptions.StartTls);
// use the access token
var oauth2 = new SaslMechanismOAuth2(sFromMailAddress, credential.Token.AccessToken);
client.Authenticate(oauth2);
client.Send(mailMessage);
client.Disconnect(true);
}
return 0;
}
Yesterday, sending an email with this method worked. Today, I get a
System.AggregateException: 'One or more errors occured. (JWT has
expired.)'
I am new to OAuth and tokens. What can I do to get this to work again?
After generating the credentials object using GoogleWebAuthorizationBroker.AuthorizeAsync, I then attempted to run
GoogleJsonWebSignature.ValidateAsync(credential.Token.IdToken).Result
but was met with "Expired JWT." Using this method call-
await credential.RefreshTokenAsync(CancellationToken.None);
the credential.Token object was updated and I then was able to call GoogleJsonWebSignature.ValidateAsync method (using the same credential object) successfully.
Thank you Garth J Lancaster on different website. Not sure if I'm allowed to link from here.
Related
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
Because Microsoft ends the support for Basic Authentication access for IMAP in Office 365 I try to update our application to use OAuth 2.0. We use MailKit in a MVC .Net web-application to access an IMAP mailbox, but I get an error saying Authentication failed. However, as a test, I can get it to work in a c# console-application.
The strange thing is:
If I copy the access-token I acquired using the console-application and use it in my web-application I can successfully authenticate and read emails. So that part works.
The authentication itself seems to be successful in the web-application. Our webapp redirects to the Microsoft login-page, MFA works, I see successful audits in Azure A/D and I do get a token in the callback. However, this token gives the Authentication failed error by Mailkit.
In Azure A/D I see some of these errors between the successful audits, but I'm not sure whether they are related or not: Error AADSTS16000 SelectUserAccount - This is an interrupt thrown by Azure AD, which results in UI that allows the user to select from among multiple valid SSO sessions. This error is fairly common and may be returned to the application if prompt=none is specified.
I already verified that the scope for which I acquire a token is the same for both console and web.
The main difference is that I use pca.AcquireTokenInteractive(scopes) in the console application to acquire the token, but I use a webclient call with a call-back in the MVC-controller.
Here is my code (MVC):
public ActionResult Index()
{
string clientID = "[client-id here]";
string clientSecret = "[client-secret here]";
string redirectUri = "[redirectUri here]";
AuthorizationServerDescription server = new AuthorizationServerDescription
{
AuthorizationEndpoint = new Uri("https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize"),
TokenEndpoint = new Uri("https://login.microsoftonline.com/organizations/oauth2/v2.0/token"),
ProtocolVersion = ProtocolVersion.V20,
};
List<string> scopes = new List<string>
{
"email",
"offline_access",
"https://outlook.office365.com/IMAP.AccessAsUser.All"
};
WebServerClient consumer = new WebServerClient(server, clientID, clientSecret);
OutgoingWebResponse response = consumer.PrepareRequestUserAuthorization(
scopes, new Uri(redirectUri));
return response.AsActionResultMvc5();
}
public async Task<ActionResult> Authorized(string code, string state, string session_state)
{
List<string> scopes = new List<string>
{
"IMAP.AccessAsUser.All",
"User.Read",
"offline_access"
};
HttpClient httpClient = new HttpClient();
var values = new Dictionary<string, string>
{
{ "Host", "https://login.microsoftonline.com" },
{ "Content-Type", "application/x-www-form-urlencoded" },
{ "client_id", "[client-id here]" },
{ "scope", string.Join(" ",scopes) },
{ "code", code },
{ "redirect_uri", [redirectUri here] },
{ "grant_type", "authorization_code" },
{ "client_secret", "[client-secret here]" },
{ "state", state },
};
var content = new FormUrlEncodedContent(values);
var response = await httpClient.PostAsync("https://login.microsoftonline.com/organizations/oauth2/v2.0/token", content);
var jsonString = await response.Content.ReadAsStringAsync();
var oathToken = JsonConvert.DeserializeObject<OathToken>(jsonString);
var oauth2 = new SaslMechanismOAuth2("[Email here]", oathToken.access_token);
var stringBuilder = new StringBuilder();
using (var client = new ImapClient())
{
try
{
await client.ConnectAsync("outlook.office365.com", 993, SecureSocketOptions.Auto);
await client.AuthenticateAsync(oauth2);
var inbox = client.Inbox;
inbox.Open(FolderAccess.ReadOnly);
for (int i = 0; i < inbox.Count; i++)
{
var message = inbox.GetMessage(i);
stringBuilder.AppendLine($"Subject: {message.Subject}");
}
await client.DisconnectAsync(true);
return Content(stringBuilder.ToString());
}
catch (Exception e)
{
return Content(e.Message);
}
}
}
The error Authentication failed occurs at the line
await client.AuthenticateAsync(oauth2);
The problem was the scope "email".
We had to remove that. Exactly why, I don't know. It was no problem when used in the console app. Maybe it had to do with the fact we used pca.AcquireTokenInteractive(scopes) in that.
I need to get emails from my Office365 account programmatically (C#).
I decided to use Mailkit and to create an application password on Azure portal.
I registered a new app, set its Redirect Uri and gave it some permissions:
I then created a client secret to access the account without user interaction.
Now, here is my code:
var opt = new ConfidentialClientApplicationOptions()
{
ClientId = "xxx_clientid",
TenantId = "xx_tenant_id",
ClientSecret = "xxx_client_secret_value",
RedirectUri = "http://localhost",
};
var scopes = new string[] {
"email",
"offline_access",
"https://outlook.office.com/IMAP.AccessAsUser.All", // Only needed for IMAP
//"https://outlook.office.com/POP.AccessAsUser.All", // Only needed for POP
//"https://outlook.office.com/SMTP.Send", // Only needed for SMTP
};
var app = ConfidentialClientApplicationBuilder.CreateWithApplicationOptions(opt).Build();
var authToken = await app.AcquireTokenForClient(scopes).ExecuteAsync(); // <--- Exception
var oauth2 = new SaslMechanismOAuth2(authToken.Account.Username, authToken.AccessToken);
using (var client = new ImapClient(new ProtocolLogger("imapLog.txt")))
{
client.Connect("outlook.office365.com", 993, SecureSocketOptions.SslOnConnect);
//client.AuthenticationMechanisms.Remove("XOAUTH2");
client.Authenticate(oauth2);
var inbox = client.Inbox;
inbox.Open(MailKit.FolderAccess.ReadOnly);
Console.WriteLine("Total messages: {0}", inbox.Count);
Console.WriteLine("Recent messages: {0}", inbox.Recent);
client.Disconnect(true);
}
Running the code I get this exception:
Microsoft.Identity.Client.MsalServiceException: 'AADSTS70011: The provided request must include a 'scope' input parameter. The provided value for the input parameter 'scope' is not valid. The scope email offline_access https://outlook.office.com/IMAP.AccessAsUser.All is not valid.
I tried following the guide found on GitHub:
Using OAuth2 With Exchange
The problem is that I need to use an app password instead.
This is my first time doing email verification,i followed a tutorial which used papercut and an example email,but i wanted to try do this with an actual gmail but i get the following error:The SMTP server does not support authentication. When using email verification with identit
i assume there something wrong with my json file
"Email": {
"Server": "smtp.gmail.com",
"Port": 587,
"SenderName": "LoveLetter",
"SenderEmail": "crunchgymemailbot#gmail.com",
"Account": "emailbot3123***#gmail.com",
"Password": "mypassword"
},"
here is a part of my register code
if (result.Succeeded) {
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var link = Url.Action(nameof(VerifyEmail), "Home", new { userId = user.Id, code }, Request.Scheme, Request.Host.ToString());
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
await _emailService.SendAsync(model.Email, "email verify", $"Verify Email", true);
return RedirectToAction("EmailVerification");
}
and my startup.cs
var mailKitOptions = Configuration.GetSection("Email").Get<MailKitOptions>();
services.AddMailKit(config => config.UseMailKit(mailKitOptions));
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();