I'm trying to call Send on the GmailService from a C# .NET MVC app. and I keep getting a 403 error when I call send.
I've checked my scopes, the Gmail setup definitely has the Gmail API enabled, and my ClientID and ClientSecret are fresh.
var httpClient = new HttpClient{
BaseAddress = new Uri("https://www.googleapis.com")
};
var requestUrl = $"oauth2/v4/token?code={code}&client_id={ClientId}&client_secret={SecretKey}&redirect_uri={RedirectUrl}&grant_type=authorization_code";
var dict = new Dictionary<string, string>{
{ "Content-Type", "application/x-www-form-urlencoded" }
};
var req = new HttpRequestMessage(HttpMethod.Post, requestUrl){Content = new FormUrlEncodedContent(dict)};
var response = await httpClient.SendAsync(req);
var token = JsonConvert.DeserializeObject<GmailToken>(await response.Content.ReadAsStringAsync());
Session["user"] = token.AccessToken;
//var obj = await GetuserProfile(token.AccessToken);
var obj = await DoSendEmail(token);
public void DoSendEmail(GmailToken inToken) {
const string fromAcct = "XXXXXXXX#gmail.com";
TokenResponse token = new TokenResponse();
token.AccessToken = inToken.AccessToken;
token.ExpiresInSeconds = inToken.ExpiresIn;
token.IdToken = inToken.IdToken;
token.TokenType = inToken.TokenType;
token.IssuedUtc = DateTime.UtcNow;
IAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer {
ClientSecrets = secrets,
Scopes = SCOPES,
ProjectId = "Xcent CP"
});
UserCredential credential = new UserCredential(flow, fromAcct, token);
if (credential.Token.IsExpired(credential.Flow.Clock)) {
bool success = credential.RefreshTokenAsync(CancellationToken.None).Result;
if (!success) {
throw new Exception("Could not refresh token");
}
}
GmailService gs = null;
try {
gs = new GmailService(new Google.Apis.Services.BaseClientService.Initializer() {
ApplicationName = APP_NAME,
HttpClientInitializer = credential
});
var mailMessage = new System.Net.Mail.MailMessage();
mailMessage.From = new System.Net.Mail.MailAddress(fromAcct);
mailMessage.To.Add("XXXXXXXX#comcast.net");
mailMessage.ReplyToList.Add(fromAcct);
mailMessage.Subject = "Test email";
mailMessage.Body = "<html><body>Hi <b>Lee</b>, this is <b>yet another</b> test message.</body></html>";
mailMessage.IsBodyHtml = true;
var mimeMessage = MimeKit.MimeMessage.CreateFromMailMessage(mailMessage);
var gmailMessage = new Google.Apis.Gmail.v1.Data.Message {
Raw = Encode(mimeMessage.ToString())
};
gs.Users.Messages.Send(gmailMessage, fromAcct).Execute();
}
catch (Exception ex) {
throw ex;
}
finally {
if (gs != null) {
gs.Dispose();
}
gs = null;
}
}
I'm not sure where to look...I've been through many many many online articles and tutorials, tried seemingly everything, and I'm still stuck with the 403 error. Help!
Thanks,
Lee
So after many hours spent looking at this I figured out the problem. My link to the Google login was this:
Response.Redirect($"https://accounts.google.com/o/oauth2/v2/auth?client_id={ClientId}&response_type=code&scope=openid%20email%20profile&redirect_uri={RedirectUrl}&state=abcdef");
"openid%20email%20profile" was the only scope I was specifying for the login, hence the 403 error about the scope I was using for the flow variable.
phew!
Related
i am using fcm to send push notifications from api to the clients the code which i was using it below:
if (notificationModel.IsDeviceAndroid){
FcmSettings settings = new FcmSettings(){
SenderId = _notificationSettings.SenderId,
ServerKey = _notificationSettings.ServerKey
};
string authorizationKey = string.Format("keyy={0}", settings.ServerKey);
string deviceToken = notificationModel.DeviceId;
HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", authorizationKey);
httpClient.DefaultRequestHeaders.Accept
.Add(new MediaTypeWithQualityHeaderValue("application/json"));
DataPayload dataPayload = new DataPayload();
dataPayload.Title = notificationModel.Title;
dataPayload.Body = notificationModel.Body;
GoogleNotification notification = new GoogleNotification();
notification.Data = dataPayload;
notification.Notification = dataPayload;
var fcm = new FcmSender(settings,httpClient);
try {
var fcmSendResponse = await fcm.SendAsync(deviceToken, notification);
if (fcmSendResponse.IsSuccess()) {
var resposne = new GetNotificationResponseDto(){
Title = notificationModel.Title,
Message = notificationModel.Body
};
return new ServiceResponse<GetNotificationResponseDto>(resposne);
} else {
var resposne = new GetNotificationResponseDto(){
Title = "Error",
Message = "Notification dose not sent"
};
return new ServiceResponse<GetNotificationResponseDto>(resposne);
}
}catch(ArgumentException ex){
return new ServiceResponse<GetNotificationResponseDto>(default) {
Error = new ResponseError(ex.Message)
};
}
The code is working on the local machine and send notification to the client successfully.
but i got the internal server error 500 on the remote server so please can anyone give me the solution for this error
thank you
With this code I can identify which thread I want to reply to, I can answer the e-mail but it only appears as answered to me, to the recipient it appears as a new e-mail.
public async Task<ActionResult> ReplyEmail([FromServices] IGoogleAuthProvider auth, [FromBody] EmailModel emailModel)
{
try
{
var cred = await auth.GetCredentialAsync();
var service = new GmailService(new BaseClientService.Initializer()
{
HttpClientInitializer = cred
});
var profile = await service.Users.GetProfile(Email).ExecuteAsync();
var desireThread = await service.Users.Threads.Get(Email, emailModel.ThreadId).ExecuteAsync();
var subject = desireThread.Messages[0].Payload.Headers
.FirstOrDefault(header => header.Name == "Subject");
var mailMessage = new System.Net.Mail.MailMessage
{
From = new System.Net.Mail.MailAddress(profile.EmailAddress),
To = {emailModel.To},
Subject = subject!.Value,
Body = emailModel.Body,
Headers = { }
};
var mimeMessage = MimeMessage.CreateFromMailMessage(mailMessage);
var gmailMessage = new Message
{
Raw = Encode(mimeMessage),
ThreadId = emailModel.ThreadId,
};
await service.Users.Messages.Send(gmailMessage, Email).ExecuteAsync();
return Ok($"E-mail successfully sent from {profile.EmailAddress} to {emailModel.To}!");
}
catch (Exception e)
{
Console.WriteLine(e);
return BadRequest(e.Message);
}
}
How it appears to me:
To the receiver:
What is missing in the message configuration so that it appears as a reply to the receiver as well?
I would like to read e-mails from a signed in user using IMAP.
I have created a console application for testing purposes and making sure the app-registration settings in azure are correct.
The console application is working as intended.
A Microsoft login window is shown where the user can enter their credentials.
An access token is received and is passed to MailKit in order to get the user's emails.
The problem
When I try to authenticate using MailKit in a MVC .net standard web-application, I get an error saying "Authentication failed".
However, when I copy the access-token I acquired using the console- application and use it in my web-application I do not get the authorization error and can successfully authenticate and read emails. (I use the access-token as second parameter in
var oauth2 = new SaslMechanismOAuth2("[Email here]", oathToken.access_token);).
I have used DotNetOpenAuth in order to show a Microsoft login window.
(I could not find a MSAL example for a web-application where i didn't have to add OWIN as middleware. I only want to authenticate in order to get emails, not for application wide authentication and authorization.)
Console application code (this works):
// Using Microsoft.Identity.Client 4.22.0
// Configure the MSAL client to get tokens
var pcaOptions = new PublicClientApplicationOptions
{
ClientId = "[client-id here]",
AadAuthorityAudience = AadAuthorityAudience.AzureAdMultipleOrgs,
};
var pca = PublicClientApplicationBuilder
.CreateWithApplicationOptions(pcaOptions).Build();
var scopes = new string[] {
"email",
"offline_access",
"https://outlook.office365.com/IMAP.AccessAsUser.All" };
// Make the interactive token request
var authResult = await pca.AcquireTokenInteractive(scopes).ExecuteAsync();
var oauth2 = new SaslMechanismOAuth2(authResult.Account.Username, authResult.AccessToken);
using (var client = new ImapClient())
{
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);
Console.WriteLine("Subject: {0}", message.Subject);
}
await client.DisconnectAsync(true);
}
Web-application (this doesn't work):
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 problems occurs on this line: await client.AuthenticateAsync(oauth2);
I receive an error saying "Authentication failed"
However, when using the access-token from the console application in the web-application i do not get this error and can successfully authenticate and read emails in the web-application.
Can anyone point me in the right direction?
Thanks.
The goal it use Graph API to send an email.
I am able to get the Authorization token by using the below code - https://login.microsoftonline.com/Some_Tenant_ID/oauth2/v2.0/authorize?client_id=SOME_Client_ID&response_type=code&redirect_uri=https://localhost&response_mode=query&scope=offline_access%20user.read%20mail.read&state=12345
The scope is user.read and mail.send with offline access. Now from using this authorization code, I want to get the refresh token. From my understanding this should work without any problem but for some reason the code is breaking at this line var httpResponse = (HttpWebResponse)request.GetResponse(); and I not sure why.
The exception code error 400 Bad Request.
my console output.
Can any one help me here and is there another way to get the Access token and/or refresh token from Authorization token. the end goal is to send email from graph API.
string tokenUrl = "https://login.microsoftonline.com/myAppTenantID/oauth2/token";
//string tokenUrl = "https://login.microsoftonline.com/myAppTenantID/oauth2/v2.0/token"; I have tried this URL too
string grant_type= "authorization_code";
string ClientID = "MyAppClientID";
string Auth_Code = "My Auth Code";
string RedirectURI = "https://localhost";
string ClientSecret = "my App secret";
Dictionary<string, string> res_dic = null;
string TargetURL = String.Format(tokenUrl);
var request = (System.Net.HttpWebRequest)WebRequest.Create(TargetURL);
request.ContentType = "application/x-www-form-urlencoded";
string RefreshToken = null;
string requestBody = String.Format(#"client_id={0}&scope=user.read%20mail.read&code={1}&redirect_uri={2}&grant_type=authorization_code&client_secret={3}", ClientID, Auth_Code,RedirectURI, ClientSecret);
request.Method = "POST";
using (var streamwriter = new StreamWriter(request.GetRequestStream()))
{
Console.WriteLine("stage 0....");
streamwriter.Write(requestBody);
streamwriter.Flush();
streamwriter.Close();
}
try
{
Console.WriteLine("stage 1....");
//Console.WriteLine("prting response"+ (HttpWebResponse)request.GetResponse());
var httpResponse = (HttpWebResponse)request.GetResponse();
Console.WriteLine("Stage 2....");
using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
{
Console.WriteLine("Stage 3");
string Result = streamReader.ReadToEnd();
string StatusCode = httpResponse.StatusCode.ToString();
res_dic = JsonConvert.DeserializeObject<Dictionary<string, string>>(Result);
}
}
catch (WebException ex)
{
Console.WriteLine("stage 4");
string ErrorMessage = ex.Message.ToString();
Console.WriteLine(ErrorMessage);
}
RefreshToken = res_dic["refresh_token"].ToString();
}
This is better for you to debug for full error message, there are many situations where this error occurs.
The scope in your code needs to add offline_access because refresh_token will be only provided if offline_access scope is requested.
You could use SDK. Code sample here:
string[] scopes = new string[] { "", "" };
IConfidentialClientApplication app = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithRedirectUri(redirectUri)
.WithClientSecret(clientSecret)
.WithAuthority(authority)
.Build();
AuthorizationCodeProvider auth = new AuthorizationCodeProvider(app, scopes);
GraphServiceClient graphServiceClient = new GraphServiceClient(new DelegateAuthenticationProvider(async (requestMessage) => {
// Retrieve an access token for Microsoft Graph (gets a fresh token if needed).
var authResult = await app.AcquireTokenByAuthorizationCode(scopes, auth_code).ExecuteAsync();
// Add the access token in the Authorization header of the API request.
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
})
);
And this API is used for sending mail, you need to add Mail.Send permission first.
var message = new Message
{
Subject = "Meet for lunch?",
Body = new ItemBody
{
ContentType = BodyType.Text,
Content = "The new cafeteria is open."
},
ToRecipients = new List<Recipient>()
{
new Recipient
{
EmailAddress = new EmailAddress
{
Address = "fannyd#contoso.onmicrosoft.com"
}
}
},
CcRecipients = new List<Recipient>()
{
new Recipient
{
EmailAddress = new EmailAddress
{
Address = "danas#contoso.onmicrosoft.com"
}
}
}
};
var saveToSentItems = false;
await graphClient.Me
.SendMail(message,saveToSentItems)
.Request()
.PostAsync();
I'm trying to get an authorization token for the Twitter REST api but it seems I'm doing something wrong. Is there something wrong with my code?
//Authorization
var customerKey = "xxxxxxxxxx";
var customerSecret = "xxxxxxxxxxxxxxxxxxx";
var b64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(string.Format("{0}:{1}", WebUtility.UrlEncode(customerKey), WebUtility.UrlEncode(customerSecret))));
var req = new HttpRequestMessage(HttpMethod.Post, "https://api.twitter.com/oauth2/token");
req.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", b64);
req.Content = new FormUrlEncodedContent(new Dictionary<string, string>() {
{ "grant_type", "client_credentials" }
});
var token = "";
using (var res = await http.SendAsync(req))
{
if (res.IsSuccessStatusCode)
token = Regex.Match(await res.Content.ReadAsStringAsync(), "\"access_token\":\"([^\"]+)").Groups[1].Value;
}
http.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer ", token);
In response I'm getting this message:
{"errors":[{"code":99,"message":"Unable to verify your credentials","label":"authenticity_token_error"}]}