Gmail authentication is failing with MailKit - gmail

I'm writing an app that reads email folders and messages from gmail.
I started out with sample code I found for using MailKit, and it was working fine for a while.
Then it just started complaining about the scope I was passing to GoogleAuthorizationCodeFlow. I was using #"https:\mail.google.com" instead of "https://mail.google.com/". I don't understand why the scope was working earlier and then just started getting rejected.
But after fixing the scope, ImapClient.AuthenticateAsync() is now throwing an "Authentication failed" exception.
I can't find any detail in the exception as to why it's failing. I checked my settings on Google Cloud and everything seems to be in order.
Can anyone help point me to where I should look to debug the issue?
I don't know why it's failing after working for a while, so I haven't really tried anything yet.
Here's the function that's failing. The last line below calling _client.AuthenticateAsync is throwing an "Authentication failed." exception.
private async Task<bool> connectToGmailAsync()
{
var clientSecrets = new ClientSecrets
{
ClientId = Server.ClientId,
ClientSecret = Server.ClientSecret
};
var codeFlow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
DataStore = new FileDataStore("CredentialCacheFolder", false),
ClientSecrets = clientSecrets,
Scopes = new[] { "https://mail.google.com/" }
});
// Note: For a web app, you'll want to use AuthorizationCodeWebApp instead.
var codeReceiver = new LocalServerCodeReceiver();
var authCode = new AuthorizationCodeInstalledApp(codeFlow, codeReceiver);
var credential = await authCode.AuthorizeAsync(string.Format("{0}#{1}", UserId, Server.Name), CancellationToken.None);
if (credential.Token.IsExpired(SystemClock.Default))
await credential.RefreshTokenAsync(CancellationToken.None);
_oauth2 = new SaslMechanismOAuth2(credential.UserId, credential.Token.AccessToken);
_client = new ImapClient();
await _client.ConnectAsync("imap." + Server.Name, Server.PortNumber, SecureSocketOptions.SslOnConnect);
await _client.AuthenticateAsync(_oauth2);
return true;
}

Related

Create custom extension through Graph API with Client Credentials auth

I have a .NET Web API that I am using to do some interaction with Microsoft Graph and Azure AD. However, when I attempt to create an extension on the user, it comes back with Access Denied.
I know it is possible from the documentation here however, it doesnt seem to work for me.
For the API, I am using client credentials. So my web app authenticates to the API using user credentials, and then from the API to the graph it uses the client.
My app on Azure AD has the Application Permission Read and Write Directory Data set to true as it states it needs to be in the documentation for a user extension.
I know my token is valid as I can retrieve data with it.
Here is my code for retrieving it:
private const string _createApprovalUrl = "https://graph.microsoft.com/beta/users/{0}/extensions";
public static async Task<bool> CreateApprovalSystemSchema(string userId)
{
using(var client = new HttpClient())
{
using(var req = new HttpRequestMessage(HttpMethod.Post, _createApprovalUrl))
{
var token = await GetToken();
req.Headers.Add("Authorization", string.Format("Bearer {0}", token));
req.Headers.TryAddWithoutValidation("Content-Type", "application/json");
var requestContent = JsonConvert.SerializeObject(new { extensionName = "<name>", id = "<id>", approvalLimit = "0" });
req.Content = new StringContent(requestContent, Encoding.UTF8, "application/json");
using(var response = await client.SendAsync(req))
{
var content = await response.Content.ReadAsStringAsync();
ApprovalSystemSchema schema = JsonConvert.DeserializeObject<ApprovalSystemSchema>(content);
if(schema.Id == null)
{
return false;
}
return true;
}
}
}
}
Is there anyone who may have a workaround on this, or information as to when this will be doable?
Thanks,
We took a look and it looks like you have a bug/line of code missing. You appear to be making this exact request:
POST https://graph.microsoft.com/beta/users/{0}/extensions
Looks like you are missing the code to replace the {0} with an actual user id. Please make the fix and let us know if you are now able to create an extension on the user.

acquiretokenasync not working in a web app

I am trying to use the following overload :
authContext.AcquireTokenAsync(, )
This works fine with a simple console app and I am able to retrieve the token.
But when I run this from a web application, the call doesn't return and no exception is thrown. I checked in fiddler and it seems the connection closes on this call.
How to get this resolved? Is it related to HttpContext having restricted permissions?
Its probably because of the async/await, for my code I just added await before the graphClient.Users[FromUserEmail].SendMail(message, true).
This works:
var sendmail = graphClient.Users[FromUserEmail].SendMail(message, true);
Task.Run(async () =>
{
try
{
await sendmail.Request().PostAsync();
}
catch (Exception ex)
{
throw ex;
}
}).Wait();
Normally, we acquire the token using authorization code grant flow in a web application. To achieve the goal, we need to implement OnAuthorizationCodeReceived event like below(full code sample):
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification context)
{
var code = context.Code;
ClientCredential credential = new ClientCredential(clientId, appKey);
string userObjectID = context.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
AuthenticationContext authContext = new AuthenticationContext(Authority, new NaiveSessionCache(userObjectID));
// If you create the redirectUri this way, it will contain a trailing slash.
// Make sure you've registered the same exact Uri in the Azure Portal (including the slash).
Uri uri = new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path));
AuthenticationResult result = await authContext.AcquireTokenByAuthorizationCodeAsync(code, uri, credential, graphResourceId);
}
If you want to implement the client credentials flow, you can refer code below:
string authority = "https://login.microsoftonline.com/{tenantId}";
string clientId = "{clientId}";
string secret = "{secret}";
string resource = "https://graph.windows.net";
var credential = new ClientCredential(clientId, secret);
AuthenticationContext authContext = new AuthenticationContext(authority);
var token = authContext.AcquireTokenAsync(resource, credential).Result.AccessToken;
If you still have the problem, it is helpful to share the detail code.
it's just a common mistakes that developper makes regarding asyn/await
you have just to add async Task<> to your method and of course the await keyword near to the call of methode that retreive the token

Getting wrong Access Token from PowerBI api endpoint

I have a strange situation related to PowerBI endpoint
https://analysis.windows.net/powerbi/api
I have two users:
client-1-1#somedomain.com
client-9230-10609#somedomain.com
I use following piece of code to get AccessToken:
public async Task<string> GetAccessToken(PowerBiUser powerBiUser)
{
try
{
var username = powerBiUser.GetUserPrincipalName(AppSettingsRepository.PowerBiTenantName);
var password = powerBiUser.GetPassword();
var authContext = new AuthenticationContext("https://login.microsoftonline.com/{tenant-id}");
var userCredential = new UserPasswordCredential(username, password);
var token = await authContext.AcquireTokenAsync("https://analysis.windows.net/powerbi/api", ClientId, userCredential);
return token.AccessToken;
}
catch (Exception)
{
return null;
}
}
The second user has been removed from Active Directory and there is a problem that I am getting identical JWT access token for each user.
It looks like getting access tokens for PowerBI users doesn't work properly for me. Does anybody meet with this kind of situation? Is that any well known bug or something? Everything worked fine when user existed in Active Directory ({tenant-id}), but after removing the user it stopped working.

ADAL authentication without dialog box prompt

I have a console application registered in Azure AD that connects to CRM Online (configured using these steps). It queries the Web API.
The application needs to run with no user interaction... but unfortunately the call to AcquireTokenSilentAsync always fails and only AcquireTokenAsync works. This makes a user login dialog appear which fails the user interaction requirement!
Is there any way to prevent this prompt, either by saving the login somewhere on the client machine (which hasn't worked so far) or perhaps using a certificate (but how do you do this?) or something else?
I'm using the ADAL for .NET v3.10.305110106 release. The following code is used to authenticate:
private static async Task PerformOnlineAuthentication()
{
_authInfo = new AuthInfo(); // This is just a simple class of parameters
Console.Write("URL (include /api/data/v8.x): ");
var url = Console.ReadLine();
BaseUri = new Uri(url);
var absoluteUri = BaseUri.AbsoluteUri;
_authInfo.Resource = absoluteUri;
Console.Write("ClientId: ");
var clientId = Console.ReadLine();
_authInfo.ClientId = clientId;
Console.Write("RedirectUri: ");
var redirectUri = Console.ReadLine();
_authInfo.RedirectUri = new Uri(redirectUri);
var authResourceUrl = new Uri($"{_authInfo.Resource}/api/data/");
var authenticationParameters = await AuthenticationParameters.CreateFromResourceUrlAsync(authResourceUrl);
_authInfo.AuthorityUrl = authenticationParameters.Authority;
_authInfo.Resource = authenticationParameters.Resource;
_authInfo.Context = new AuthenticationContext(_authInfo.AuthorityUrl, false);
}
private static async Task RefreshAccessToken()
{
if (!IsCrmOnline())
return;
Console.WriteLine($"Acquiring token from: {_authInfo.Resource}");
AuthenticationResult authResult;
try
{
authResult = await _authInfo.Context.AcquireTokenSilentAsync(_authInfo.Resource, _authInfo.ClientId);
}
catch (AdalSilentTokenAcquisitionException astae)
{
Console.WriteLine(astae.Message);
authResult = await _authInfo.Context.AcquireTokenAsync(_authInfo.Resource, _authInfo.ClientId, _authInfo.RedirectUri, new PlatformParameters(PromptBehavior.RefreshSession));
}
HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
}
Thanks to #aravind who pointed out the active-directory-dotnet-native-headless sample.
The sample contains a FileCache class which inherits from Microsoft.IdentityModel.Clients.ActiveDirectory.TokenCache. That class manages caching of the credentials to an encrypted file on disk. This means that there is only one prompt on first run and after that the credentials are locally stored.
The final pieces of the puzzle are:
Calling a different constructor signature to initialize AuthenticationContext with the FileCache:
_authInfo.Context = new AuthenticationContext(
_authInfo.AuthorityUrl, false, new FileCache());
Obtaining credentials from the user into a Microsoft.IdentityModel.Clients.ActiveDirectory.UserPasswordCredential object (see the TextualPrompt() method in the sample)
Passing the credentials to a different method signature for AcquireTokenAsync():
authResult = await _authInfo.Context.AcquireTokenAsync(
_authInfo.Resource, _authInfo.ClientId, userCredential);
If "application needs to run with no user interaction" use ClientCredential flow eg:
public static string GetAccessTokenUsingClientCredentialFlow(Credential cred) {
AuthenticationContext ac = new AuthenticationContext(cred.Authority);
AuthenticationResult r = ac.AcquireTokenAsync(cred.ResourceId, new ClientCredential(cred.ClientId, cred.ClientSecret)).Result;
return r.AccessToken;
}

How can I log into gmail IMAP using MailKit

I am trying to use MailKit (http://jstedfast.github.io/MailKit/docs/index.html) to log into Gmail using oAuth. I am able to log into Google API using a refreshed AuthToken, but when I try to use the refreshed token in MailKit, I get an error "Invalid Credentials"
Any clues??
Thanks,
Jeff
Here is my code:
var secrets = new ClientSecrets()
{
ClientId = "xxx-yyy.apps.googleusercontent.com",
ClientSecret = "xyzSecret"
};
IAuthorizationCodeFlow flow =
new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = secrets,
Scopes = new string[] { GmailService.Scope.GmailReadonly }
});
var tokenResponse = flow.RefreshTokenAsync("", connection.RefreshToken, CancellationToken.None).Result;
using (var client = new MailKit.Net.Imap.ImapClient())
{
var credentials = new NetworkCredential("emailtoconnect#gmail.com", tokenResponse.AccessToken);
client.Connect("imap.gmail.com", 993, true, CancellationToken.None);
try
{
client.Authenticate(credentials, CancellationToken.None);
}
catch (Exception ex)
{
throw;
}
}
a clearer answer is that the scope was incorrect in the original code
Scopes = new string[] { GmailService.Scope.GmailReadonly }
needs to be
Scopes = new string[] { GmailService.Scope.MailGoogleCom }
in order to authenticate with imapi using a access token.
It was just a scope problem. The above code works fine for gmail oAuth!!
using (var client = new ImapClient())
{
client.Connect("imap.gmail.com", 993, true);
client.AuthenticationMechanisms.Remove("XOAUTH2");
client.Authenticate(EmailId, Password);
}
The above piece of code is used to log in to gmail using imap and MailKit tool. But before this, you have to log in to gmail manually and check "Enable Imap" option in Settings. This will surely work.

Resources