How can I log into gmail IMAP using MailKit - gmail

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.

Related

Unable to get AccessToken with Azure B2C, IdToken present but no scope permissions

I'm following this tutorial to setup a client / server using Azure B2C.
I think I've done everything correctly but I'm experiencing a couple of issues.
AccessToken is null, but IdToken is populated
When I try to access protected resources, the following code is executed after I sign in at https://login.microsoftonline.com. The following line fails because AccessToken is null:
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
TaskWebApp.Controllers.TaskController.cs (Client App):
public async Task<ActionResult> Index()
{
try
{
// Retrieve the token with the specified scopes
var scope = new string[] { Startup.ReadTasksScope };
string signedInUserID = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
TokenCache userTokenCache = new MSALSessionCache(signedInUserID, this.HttpContext).GetMsalCacheInstance();
ConfidentialClientApplication cca = new ConfidentialClientApplication(Startup.ClientId, Startup.Authority, Startup.RedirectUri, new ClientCredential(Startup.ClientSecret), userTokenCache, null);
var user = cca.Users.FirstOrDefault();
if (user == null)
{
throw new Exception("The User is NULL. Please clear your cookies and try again. Specifically delete cookies for 'login.microsoftonline.com'. See this GitHub issue for more details: https://github.com/Azure-Samples/active-directory-b2c-dotnet-webapp-and-webapi/issues/9");
}
AuthenticationResult result = await cca.AcquireTokenSilentAsync(scope, user, Startup.Authority, false);
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, apiEndpoint);
// Add token to the Authorization header and make the request
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken); //AccessToken null - crash
//request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.IdToken); //This does work however
}
...
}
Contents of result AquireTokenSilentAsync:
IdToken doesn't contain Scope permissions
If I use IdToken in place of AccessToken - I get a little further but I'm hitting a new stumbling block. It fails here:
TaskService.Controllers.TasksController.cs (WebAPI):
public const string scopeElement = "http://schemas.microsoft.com/identity/claims/scope";
private void HasRequiredScopes(String permission)
{
if (!ClaimsPrincipal.Current.FindFirst(scopeElement).Value.Contains(permission)) //Crashes here as token doesn't contain scopeElement
{
throw new HttpResponseException(new HttpResponseMessage
{
StatusCode = HttpStatusCode.Unauthorized,
ReasonPhrase = $"The Scope claim does not contain the {permission} permission."
});
}
}
And here is a screenshot of my ClaimsPrincipal.Current:
Any advice is appreciated.
Edit
Signin URL:
https://login.microsoftonline.com/te/turtlecorptesting.onmicrosoft.com/b2c_1_email/oauth2/v2.0/authorize?client_id=03ef2bd...&redirect_uri=https%3a%2f%2flocalhost%3a44316%2f&response_mode=form_post&response_type=code+id_token&scope=openid+profile+offline_access+https%3a%2f%2fturtlecorptesting.onmicrosoft.com%2fread+https%3a%2f%2fturtlecorptesting.onmicrosoft.com%2fwrite&state=OpenIdConnect.AuthenticationProperties%3daDQntAuD0Vh=...&nonce=63655.....YWRmMWEwZDc.....
Under Azure AD B2C go to your application
Under API access click add, select your API and its scope(s)
You should now get AccessToken in your response.

How to get back to app after google login

I'm trying to implement google login in my app using xamarin.auth like below
var auth = new OAuth2Authenticator("284202576320-7kgdhaa5sgvkoe03jmmcv0p8lfdma306.apps.googleusercontent.com","cAZW7uegD-h2-
tNMMf5q1UGQ","https://www.googleapis.com/auth/userinfo.email",new
Uri("https://accounts.google.com/o/oauth2/auth"),new
Uri("http://dev.myfav.restaurant/Account/LoginComplete"),new
Uri("https://accounts.google.com/o/oauth2/token"),null,true)
{
AllowCancel = true,
};
but Completed event not firing and its going to web page after login :(
I'm getting below error
i need to get back user to my app how can i achieve this ???? Can anyone help me on this please.
Thanks in advance
Hey follow these two examples one is using web view and one is using google sign in sdk for google auth.
https://timothelariviere.com/2017/09/01/authenticate-users-through-google-with-xamarin-auth/
and
https://developer.xamarin.com/samples/xamarin-forms/WebServices/OAuthNativeFlow/
So according to this issue reported by Mounika.Kola .I think u should refer that authenticator.Completed -= OnAuthCompleted is there in ur code. For reference just see these codes which i used for google authorization in Xamarin Forms.
void OnLoginClicked(object sender, EventArgs e)
{
string clientId = null;
string redirectUri = null;
switch (Device.RuntimePlatform)
{
case Device.iOS:
clientId = Constants.iOSClientId;
redirectUri = Constants.iOSRedirectUrl;
break;
case Device.Android:
clientId = Constants.AndroidClientId;
redirectUri = Constants.AndroidRedirectUrl;
break;
}
var authenticator = new OAuth2Authenticator(
clientId,
null,
Constants.Scope,
new Uri(Constants.AuthorizeUrl),
new Uri(redirectUri),
new Uri(Constants.AccessTokenUrl),
null,
true);
authenticator.Completed += OnAuthCompleted;
authenticator.Error += OnAuthError;
AuthenticationState.Authenticator = authenticator;
var presenter = new Xamarin.Auth.Presenters.OAuthLoginPresenter();
presenter.Login(authenticator);
}
async void OnAuthCompleted(object sender, AuthenticatorCompletedEventArgs e)
{
var authenticator = sender as OAuth2Authenticator;
if (authenticator != null)
{
authenticator.Completed -= OnAuthCompleted;
authenticator.Error -= OnAuthError;
}
User user = null;
if (e.IsAuthenticated)
{
// If the user is authenticated, request their basic user data from Google
// UserInfoUrl = https://www.googleapis.com/oauth2/v2/userinfo
var request = new OAuth2Request("GET", new Uri(Constants.UserInfoUrl), null, e.Account);
var response = await request.GetResponseAsync();
if (response != null)
{
// Deserialize the data and store it in the account store
// The users email address will be used to identify data in SimpleDB
string userJson = await response.GetResponseTextAsync();
user = JsonConvert.DeserializeObject<User>(userJson);
}
if (account != null)
{
store.Delete(account, Constants.AppName);
}
await store.SaveAsync(account = e.Account, Constants.AppName);
await DisplayAlert("Email address", user.Email, "OK");
}
}
I hope it helps you.
In iOS once you have completed the authentication with Xamarin.Auth you just need to dismiss the viewController and you will be put back in your app.
You do this subscribing to the Completed event of the OAuth2Authenticator
auth.Completed += (sender, e) =>
{
DismissViewController(true, null);
};
If the "Native UI" is used (the last parameter in the constructor is set to true), which means that external/system browser is used for login not WebView. So, on Android instead of WebView [Chrome] CustomTabs is used and on iOS instead of UIWebView (or WKWebView) SFSafariViewController is used.
With native UI user is leaving your app and the only way to return to your app is app-linking (or deep-linking) and this requires completely different approach.
1st you cannot use http[s] scheme for redirect_url (OK on Android it is possible, but on iOS not). Use custom scheme for that.
See the sample[s] (Xamarin.Forms ComicBook):
https://github.com/moljac/Xamarin.Auth.Samples.NugetReferences
And the docs in the repo:
https://github.com/xamarin/Xamarin.Auth/tree/master/docs

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.

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;
}

Azure API failed to authenticate the request

I am using the next code to get the token for Azure AD authentication
errorMessage = "";
AuthenticationResult result = null;
var context = new AuthenticationContext(String.Format(CultureInfo.InvariantCulture, ConfigurationManager.AppSettings["login"], ConfigurationManager.AppSettings["tenantId"]),false);
ClientCredential clientCredential = new ClientCredential(ConfigurationManager.AppSettings["clientId"], ConfigurationManager.AppSettings["key"]);
try
{
result = context.AcquireToken(ConfigurationManager.AppSettings["apiEndpoint"], clientCredential);
}
catch (AdalException ex)
{
if (ex.ErrorCode == "temporarily_unavailable")
{
errorMessage = "Temporarily Unavailable";
return null;
}
else
{
errorMessage = "Unknown Error";
return null;
}
}
string token = result.AccessToken;
var credential = new TokenCloudCredentials(ConfigurationManager.AppSettings["subscriptionId"],token);
//string certificateString = ConfigurationManager.AppSettings["managementCertificate"];
//var cert = new X509Certificate2(Convert.FromBase64String(base64cer));
return credential;
After that I am doing the next to create a website in Azure
using (var computeClient = new WebSiteManagementClient(credentials))
{
var result = computeClient.WebSites.IsHostnameAvailable(websiteName);
if (result.IsAvailable)
{
await computeClient.WebSites.CreateAsync(WebSpaceNames.WestEuropeWebSpace, new WebSiteCreateParameters() {
Name= websiteName,
ServerFarm= ConfigurationManager.AppSettings["servicePlanName"]
});
}
else
{
return ResultCodes.ObjectNameAlreadyUsed;
}
}
But every time I execute that I got the following error:
ForbiddenError: The server failed to authenticate the request. Verify that the certificate is valid and is associated with this subscription.
I tried to import the management certificate as they said here:
https://www.simple-talk.com/cloud/security-and-compliance/windows-azure-management-certificates/
And also tried this one:
http://technetlibrary.com/change-windows-azure-subscription-azure-powershell/198
For importing management certificate.
Also I gave the application permissions to access management API.
Azure AD Authentication DOES NOT use the management certificate authentication.
There is a good documentation and code sample on MSDN on how to resolve your current issue.
Authenticating Service Management Requests
Looks like your application does not have permission to access the Azure API's.
Please use this link to get permissions.
After this please add permissions to access API in app permission or user permission.

Resources