How to pass through OUATH verification in C# - c#

I'm trying to log into my Outlook mailbox via two-factor authentication. For this I use the MailKit library. I have implemented the example [https://github.com/jstedfast/MailKit/blob/master/ExchangeOAuth2.md]
from the documentation into my example, but nothing is outputting. How is this possible? What am I doing wrong?
I am also confused about what represents in scopes string offline_access?
public async void OAUTH()
{
var options = new PublicClientApplicationOptions
{
ClientId = "ID",
TenantId = "ID",
RedirectUri = "https://login.microsoftonline.com/common/oauth2/nativeclient"
};
var publicClientApplication = PublicClientApplicationBuilder
.CreateWithApplicationOptions(options)
.Build();
var scopes = new string[] {
"mailaddress",
"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 authToken = await publicClientApplication.AcquireTokenInteractive(scopes).ExecuteAsync();
var oauth2 = new SaslMechanismOAuth2(authToken.Account.Username, authToken.AccessToken);
using (var client = new ImapClient())
{
using (var cancel = new CancellationTokenSource())
{
await client.ConnectAsync("outlook.office365.com", 993, SecureSocketOptions.SslOnConnect);
await client.AuthenticateAsync(oauth2);
//client.Authenticate(emailParser.Username, emailParser.Password, cancel.Token);
var inbox = client.Inbox;
inbox.Open(FolderAccess.ReadOnly, cancel.Token);
Console.WriteLine("Total messages: {0}", inbox.Count);
Console.WriteLine("Recent messages: {0}", inbox.Unread);
for (int i = 0; i < inbox.Count; i++)
{
var message = inbox.GetMessage(i, cancel.Token);
Console.WriteLine(message.TextBody);
}
}
}
}

Related

Google-Api and Google-Api.gmail - C# - How do I make my answer be understood as an answer?

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?

Authenticate with Oauth2 to read IMAP in C# MVC web-application

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.

Unable to send large attachment using graph api

I am trying to add a large attachment to an email using Microsoft Graph.
Steps:
Get Token:
public static async Task<GraphServiceClient> GetAuthenticatedClientForApp(IConfidentialClientApplication app)
{
GraphServiceClient graphClient = null;
// Create Microsoft Graph client.
try
{
var token = await GetTokenForAppAsync(app);
graphClient = new GraphServiceClient(
"https://graph.microsoft.com/beta",
new DelegateAuthenticationProvider(async(requestMessage) =>
{
requestMessage.Headers.Authorization =
new AuthenticationHeaderValue("bearer", token);
}));
return graphClient;
}
catch (Exception ex)
{
Logger.Error("Could not create a graph client: " + ex.Message);
}
return graphClient;
}
/// <summary>
/// Get Token for App.
/// </summary>
/// <returns>Token for app.</returns>
public static async Task<string> GetTokenForAppAsync(IConfidentialClientApplication app)
{
AuthenticationResult authResult;
authResult = await app
.AcquireTokenForClient(new string[] { "https://graph.microsoft.com/.default" })
.ExecuteAsync(System.Threading.CancellationToken.None);
return authResult.AccessToken;
}
Create Draft:
Message draft = await client
.Users[emailDTO.FromEmail]
.Messages
.Request()
.AddAsync(msg);
Attach file:
if (emailDTO.FileAttachments != null && emailDTO.FileAttachments.Count() > 0)
{
foreach (EmailAttachment emailAttachment in emailDTO.FileAttachments)
{
if (emailAttachment.UploadFile != null && emailAttachment.UploadFile.Length > 0)
{
var attachmentItem = new AttachmentItem
{
AttachmentType = AttachmentType.File,
Name = emailAttachment.FileName,
Size = emailAttachment.UploadFile.Length
};
var session = await client
.Users[emailDTO.FromEmail]
.MailFolders
.Drafts
.Messages[draft.Id]
.Attachments
.CreateUploadSession(attachmentItem)
.Request()
.PostAsync();
var stream = new MemoryStream(emailAttachment.UploadFile);
var maxChunkSize = 320 * 1024 * 1024;
var provider = new ChunkedUploadProvider(session, client, stream, maxChunkSize);
var readBuffer = new byte[maxChunkSize];
var chunkRequests = provider.GetUploadChunkRequests();
//var uploadedItem = await provider.UploadAsync();
var trackedExceptions = new List<Exception>();
foreach (var rq in chunkRequests)
{
var result = await provider.GetChunkRequestResponseAsync(rq, readBuffer, trackedExceptions);
}
}
}
}
Error:
{
Code: InvalidAudienceForResource
Message: The audience claim value is invalid for current resource.
Audience claim is 'https://graph.microsoft.com', request url is
'https://outlook.office.com/api/beta/User
I believe the problem here is that the session URL that gets created points to a resource that is not on Microsoft Graph. However, when you use the same client to call that endpoint it passes the bearer token that belongs to Graph. I believe the session URL has an access token in the URL that is sufficient.
You could update your DelegateAuthenticationProvider function to only add the Authorization header for hosts that are graph.microsoft.com. Or you could use our LargeFileUploadTask instead of the ChunkedUploadProvider and it will do much of this work for you. Sadly, I haven't finished the docs for it yet. I'll come back and update this post soon with a docs link.
var task = new Task(() =>
{
foreach(var attachment in attachments) {
using(MemoryStream stream = new MemoryStream()) {
var mimePart = (MimePart)attachment;
mimePart.Content.DecodeTo(stream);
var size = MeasureAttachmentSize(mimePart);
var attachmentItem = MapAttachmentItem(attachment, size);
// Use createUploadSession to retrieve an upload URL which contains the session identifier.
var uploadSession = client.Users[mailbox]
.Messages[addedMessage.Id]
.Attachments
.CreateUploadSession(attachmentItem)
.Request()
.PostAsync()
.GetAwaiter()
.GetResult();
// Max slice size must be a multiple of 320 KiB
int maxSliceSize = 320 * 1024;
var fileUploadTask = new LargeFileUploadTask<FileAttachment>(uploadSession
,stream
,maxSliceSize
,client);
// Create a callback that is invoked after each slice is uploaded
IProgress<long> progress = new Progress<long>(prog =>
{
Console.WriteLine($"Uploaded {prog} bytes of {stream.Length} bytes");
});
try {
// Upload the file
var uploadResult = fileUploadTask.UploadAsync(progress, 3).Result;
if(uploadResult.UploadSucceeded) {
// The result includes the location URI.
Console.WriteLine($"Upload complete, LocationUrl: {uploadResult.Location}");
}
else {
Console.WriteLine("Upload failed");
}
}
catch(ServiceException ex) {
Console.WriteLine($"Error uploading: {ex.ToString()}");
throw ex;
}
}
}
});
task.RunSynchronously();

Receiving 403 error with Users.Messages.Send in Gmail API

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!

Open Identity Server 4 Unauthorized UserInfo

I'm currently struggling with OpenIdentityServer 4 in ASP Core 1.1.
I'm able to grant tokens, using ResourceOwnerPassword grant type etc. Created my custom ResourcePasswordValidator etc.
Currently in my test application I retrieve a token with user credentials and all issues fine, however when I try to access the IdentityController with an [Authorize] attribute I'm redirected to unauthorized page and sent a 403 forbidden http code
I'm not sure what the issue is. I suspect it could be from scope/resource issue
Any help whatsoever appreciated.
Sample code for consumer
public class TestAuthentication
{
private HttpClient _client;
public TestAuthentication()
{
_client = new HttpClient();
}
public async Task RunTest()
{
var token = await GetToken();
if (string.IsNullOrWhiteSpace(token)) return;
await GetClaims(token);
}
private async Task<string> GetToken()
{
var response = "";
var disco = await DiscoveryClient.GetAsync("http://localhost:5000");
//var tokenClient = new TokenClient(disco.TokenEndpoint, "EduOne", "secret");
//var tokenResponse = await tokenClient.RequestClientCredentialsAsync("api");
var tokenClient = new TokenClient(disco.TokenEndpoint, "ro.client", "secret");
var tokenResponse = await tokenClient.RequestResourceOwnerPasswordAsync("alice#mail.com", "Password1!", "api1");
// var tokenResponse = await tokenClient.RequestResourceOwnerPasswordAsync("alice#mail.com", "Password1!", "openid");
if (tokenResponse.IsError)
{
Console.Out.WriteLine("Error:");
Console.Out.WriteLine(tokenResponse.Error);
Console.Out.Write(tokenResponse.ErrorDescription);
}
else
{
var extraClaims = new UserInfoClient(disco.UserInfoEndpoint);
var identityClaims = await extraClaims.GetAsync(tokenResponse.AccessToken);
response = tokenResponse.Json.ToString();
Console.Out.WriteLine($"token: {response}");
}
return response;
}
private async Task GetClaims(string token)
{
try
{
var obj = JObject.Parse(token);
var tok = obj["access_token"]?.ToString();
_client = new HttpClient();
_client.SetBearerToken(tok);
var response = await _client.GetAsync("http://localhost:5000/api/v1/identity");
if (!response.IsSuccessStatusCode)
{
Console.WriteLine(response.StatusCode);
}
else
{
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(JArray.Parse(content));
}
}
catch (Exception e)
{
var m = e.Message;
//throw;
}
}
~TestAuthentication()
{
_client = null;
}
}
Code for setups:
Client =>
new Client
{
ClientId = "ro.client",
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
ClientSecrets =
{
new Secret("secret".Sha256())
},
AllowedScopes = {"api1" },
AccessTokenType = AccessTokenType.Reference
},
User =>
new TestUser
{
SubjectId = "1",
Username = "alice#mail.com",
Password = "Password1!",
Claims =
{
new Claim(JwtClaimTypes.Email, "mail#mail.com")
}
},
Resource =>
new IdentityResource("api1", new string[]{JwtClaimTypes.Email})
Startup =>
app.UseIdentityServer();
// app.UseIdentity();
// app.UseIdentity();
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
ApiSecret = "secret",
Authority = "http://localhost:5000",
RequireHttpsMetadata = false,
DiscoveryDocumentRefreshInterval = TimeSpan.FromMinutes(5),
ApiName = "FiserOpenIdentityApi",
SupportedTokens = IdentityServer4.AccessTokenValidation.SupportedTokens.Both,
AllowedScopes = { "openid", "profile", "email", "api1", "FiserOpenIdentityApi" }
});
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "Cookies"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
AuthenticationScheme = "oidc",
SignInScheme = "Cookies",
Authority = "http://localhost:5000",
ClientId = "ro.client",
RequireHttpsMetadata = false,
ClientSecret = "secret",
SaveTokens = false
});
// app.UseJwtBearerAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
routes.MapRoute(
name: "RESTApiV1",
template: "api/v1/{controller}/{action}/{id?}");
});
app.UseMongoDbForIdentityServer();
I am using Hybrid flow. i think you are missing authenticationHeader in HttpClient.
Please follow below code, its working and it may help you.
var client = new HttpClient();
var accessToken = await HttpContext
.GetTokenAsync(OpenIdConnectParameterNames.AccessToken);
var disco = await client.GetDiscoveryDocumentAsync("https://localhost:44323");
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var response = await client.GetUserInfoAsync(new UserInfoRequest
{
Address = disco.UserInfoEndpoint,
Token = accessToken
});
var address = response.Claims.FirstOrDefault(c => c.Type == "address")?.Value;
var add = new AddressViewModel();
add.Address = address;
return View(add);

Categories