I implemented a windows service which downloads DoubleClick report data for several DFA accounts. The windows service is installed twice on the same server but the services import the downloaded reports into different SQL server (dev and live). Both services run daily at a specific time but not parallel. The windows service uses the the DFA Reporting API 1.3. and the Google APIs Client Library 1.7 (beta).
The first day after restarting the windows services all data will be loaded without any errors but the next days I get an "Authentication token has expired" error. This is the code
public ImportData(string loginUserName, string sourceName)
{
Logger.Info(string.Format("Import for Source {0} started.", sourceName));
this.dbContext = new DatabaseContext();
// Create new log item
this.importExportLog = this.dbContext.ImportExportLogs.Create();
this.importExportLog.SetData("DoubleClick " + sourceName, DateTime.Now);
this.dbContext.ImportExportLogs.Add(this.importExportLog);
this.dbContext.SaveChanges();
this.StartDate = DateTime.Today.AddDays(0 - DoubleClickImporterSettings.Default.ImportDataForDays);
this.EndDate = DateTime.Today;
this.userName = loginUserName;
this.sourceName = sourceName;
// Reset report data
campaignObjs = new List<Campaign>();
clientObjs = new List<Advertiser>();
siteObjs = new List<DfaSite>();
adObjs = new List<AdBase>();
this.user = new DfaUser();
ClientSecrets secret = new ClientSecrets();
secret.ClientId = this.user.Config.OAuth2ClientId;
secret.ClientSecret = this.user.Config.OAuth2ClientSecret;
UserCredential credential = GoogleWebAuthorizationBroker.AuthorizeAsync(secret, new string[] { DfaReportingScope }, this.user.Config.OAuth2PrnEmail, CancellationToken.None, new FileDataStore(DoubleClickImporterSettings.Default.OAuthStorePath)).Result;
// Keep auth toke for 6 hours
credential.Token.ExpiresInSeconds = 21600;
// Create the dfa reporting service.
DfareportingService dfars = new DfareportingService(new BaseClientService.Initializer
{
HttpClientInitializer = credential,
ApplicationName = "Double Click Importer",
});
// log in for dfa api
if ((user.Config as DfaAppConfig).AuthorizationMethod == DfaAuthorizationMethod.OAuth2)
{
// Set the OAuth2 scope.
user.Config.OAuth2Scope = DfaReportingScope;
// Since we are using a console application, set the callback url to null.
user.Config.OAuth2RedirectUri = null;
}
else
{
throw new Exception("Authorization mode is not OAuth2.");
}
// Set the username. This is required for the LoginService to work
// correctly when authenticating using OAuth2.
(user.Config as DfaAppConfig).DfaUserName = userName;
this.user.OAuthProvider.RefreshAccessToken();
startDateTime = DateTime.Now;
ImportReportData(dfars);
this.user.OAuthProvider.RefreshAccessToken();
while (!ImportClientData(user))
{
System.Threading.Thread.Sleep(waitUntilNextTry);
this.user.OAuthProvider.RefreshAccessToken();
}
this.user.OAuthProvider.RefreshAccessToken();
while (!ImportAdvertiserData(user))
{
System.Threading.Thread.Sleep(waitUntilNextTry);
this.user.OAuthProvider.RefreshAccessToken();
}
// Import other data...
}
Why the error doesn't occur on first day but occur on the other days?
Related
Blazor server application
I have a web appliction that is using AzureAd and OpenIdConnect to login to this application.
I am sending mail by using Microsoft graph and I am using the example in Microsoft doc with some changes like this:
#inject Microsoft.Graph.GraphServiceClient GraphServiceClient
#inject MicrosoftIdentityConsentAndConditionalAccessHandler ConsentHandler
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;
try
{
await GraphServiceClient.Me
.SendMail(message,saveToSentItems)
.Request()
.PostAsync();
}
catch(Exception ex)
{
ConsentHandler.HandleException(ex);
}
Scenario of the error
This works perfect but just for one time , if I try to send the same email again it doesn't work and I got no error but just redirect me to empty page.
If I logout and login again, then it works fine.
The error that I got in the second time:
Message = "IDW10502: An MsalUiRequiredException was thrown due to a challenge for the user. See https://aka.ms/ms-id-web/ca_incremental-consent. "
MsalUiRequiredException = {"No account or login hint was passed to the AcquireTokenSilent call. "}
My Question
How can fix the code up to send multi emails?. I think that I have a problem with token but I don't know where should I start?.
Thanks
Add the other catch block to retrieve the specific issue if we have any related to permissions or other ServiceExceptions.
try
{
SendMail(); // Sending mail code here.
}
catch (Microsoft.Graph.ServiceException e)
{
// get the error here if we have any.
}
After some search, I think that I have to move the the permission Delegated permissions to Application Permissions like this:
Why should I use application permission?
In my case the user logged in for the first time and clicked on the button then the email will be send, but in the second time the application has to communicate with API graph without interaction from the user, that means without user and this exactly what I need(application permission).
I adjust the code like the following:
Client credentials provider:
The client credential flow enables service applications to run without user interaction. Access is based on the identity of the application. this is from Microsoft doc
private GraphServiceClient CreateGraphServiceClient()
{
// The client credentials flow requires that you request the
// /.default scope, and preconfigure your permissions on the
// app registration in Azure. An administrator must grant consent
// to those permissions beforehand.
var scopes = new[] { "https://graph.microsoft.com/.default" };
// Multi-tenant apps can use "common",
// single-tenant apps must use the tenant ID from the Azure portal
var tenantId = "common";
// Values from app registration
var clientId = "YOUR_CLIENT_ID";
var clientSecret = "YOUR_CLIENT_SECRET";
// using Azure.Identity;
var options = new TokenCredentialOptions
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};
var clientSecretCredential = new ClientSecretCredential(
tenantId, clientId, clientSecret, options);
return new GraphServiceClient(clientSecretCredential, scopes);
}
Send mail with UserId, you can see the code in Microsoft doc:
puplic SendMyEmail()
{
GraphServiceClient graphClient = CreateGraphServiceClient;
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;
//See GetUserId down
string userId = await GetUserId();
await graphClient.Users[UserId]
.SendMail(message,saveToSentItems)
.Request()
.PostAsync();
}
}
UserId:
To get user Id you need AuthenticationStateProvider, this has to inject in the service of your application and then add to the constructor of your class, then you can use it.
puplic class MyClass
{
private readonly MicrosoftIdentityConsentAndConditionalAccessHandler ConsentHandler;
private readonly AuthenticationStateProvider authenticationState;
puplic MyClass(
MicrosoftIdentityConsentAndConditionalAccessHandler ConsentHandler,
AuthenticationStateProvider authenticationState)
{
this.authenticationState = authenticationState;
this.ConsentHandler = ConsentHandler;
}
public async Task<string> GetUserId()
{
var authSate = await authenticationState.GetAuthenticationStateAsync();
return authSate.User.FindFirstValue("http://schemas.microsoft.com/identity/claims/objectidentifier");
}
//Here your
private GraphServiceClient CreateGraphServiceClient() { ...}
puplic SendMyEmail() {....}
}
I'm running .NET Core v3.1 and Blazor and have implemented authorization using Google limited to our domain in Google G Suite as mentioned here: https://www.jerriepelser.com/blog/forcing-users-sign-in-gsuite-domain-account/
Login/logout is working fine, but when the user who logged in is blocked or removed in Google G Suite the user stays logged in into my application until he logs out from the application. When he doesn't logs out he can keep using the application.
I'm looking for a refresh every hour.
This is my login.cshtml.cs:
public async Task<IActionResult> OnGetCallbackAsync(string returnUrl = null, string remoteError = null)
{
// Get the information about the user from the external login provider
var GoogleUser = User.Identities.FirstOrDefault();
if (GoogleUser.IsAuthenticated)
{
var authProperties = new AuthenticationProperties
{
IsPersistent = true,
RedirectUri = Request.Host.Value,
IssuedUtc = System.DateTime.UtcNow,
ExpiresUtc = System.DateTime.UtcNow.AddHours(1)
};
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(GoogleUser), authProperties);
}
return LocalRedirect("/");
}
I already added IssuedUtc and ExpiresUtc but that didn't change anything.
You have to enable the ability to call Google APIs (https://www.googleapis.com/auth/admin.directory.user, https://www.googleapis.com/auth/admin.directory.group) to get this information, but, before you can do that, the G-Suite Domain Admin has to authorize that access using https://developers.google.com/admin-sdk/directory/v1/guides/authorizing
This explains the process:
https://developers.google.com/admin-sdk/directory/v1/guides/delegation
You will want to see this GitHub repo for code samples:
https://github.com/googleapis/google-api-dotnet-client
Here is some psudo code:
string[] Scopes = {
DirectoryService.Scope.AdminDirectoryGroup,
DirectoryService.Scope.AdminDirectoryUser
};
GoogleCredential credential;
//redirectUrl = this.Request.Host.Value;
string keyfilepath = "yourKeyFile.json";
using (var stream = new FileStream(keyfilepath, FileMode.Open, FileAccess.Read))
{
// As we are using admin SDK, we need to still impersonate user who has admin access
// https://developers.google.com/admin-sdk/directory/v1/guides/delegation
credential = GoogleCredential.FromStream(stream)
.CreateScoped(Scopes).CreateWithUser(EmailOfGoogleDomainAdmin);
}
// Create Directory API service.
var service = new DirectoryService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "ApplicationName",
});
// G Suite User to get information about
// This test user should be suspended
var gs_email = UserToCHeck;
var request = service.Users.Get(gs_email);
var result = request.Execute();
Console.WriteLine("Full Name: {0}", result.Name.FullName);
Console.WriteLine("Email: {0}", result.PrimaryEmail);
Console.WriteLine("ID: {0}", result.Id);
Console.WriteLine("Is Admin: {0}", result.IsAdmin);
Console.WriteLine("Is Suspended: {0}", result.Suspended);
I need to send an email to users from an ASP.NET Core 2 application, following some business rules. However, I need to ensure that the account the email is being sent to actually exists (for some reason, it may be that the account stopped being valid). The customer is using Azure Active Directory, so I need to query AAD somehow so it lets me know whether the account exists or not.
So far I have been looking for Microsoft Graph as a way to do this, however every example I have seen so far requires prior authentication and use a delegate authentication mechanism. I don't want my users having to authenticate nor to prompt the authentication screen.
Given this situation, what would you recommend using? If you can also point me to an example, that would be great. Thanks!
You don't really need to throw/catch exception for every invalid user as you're doing in current code. I have nothing against exception handling in general for other reasons but to see if the user exists or not you can try using Filter.
So your graph query could look like -
https://graph.microsoft.com/v1.0/users?$filter=startswith(userPrincipalName,'someuser#mytenant.onmicrosoft.com')
I have shown startswith here becuase eq didn't work for me in a quick trial. Although I would recommend two things:
Go through Microsoft documentation on Filters here and see what works best for your requirements - Use query parameters to customize responses with Microsoft Graph
Play a little bit with different queries in Microsoft Graph Explorer it's very simple and easy to use.
Here is a modified version for your code.
Note that I'm checking for the collection count to be > 0 and not checking for it to be null, as even in case user is not found the UsersCollectionPage was not null for my test run.
using Microsoft.Identity.Client;
using Microsoft.Graph.Auth;
using Microsoft.Graph;
...
private async Task<bool> ValidateAccounts(string accounts) {
var confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create("clientId here")
.WithTenantId("tokenId here")
.WithClientSecret("secret here")
.Build();
var authProvider = new ClientCredentialProvider(confidentialClientApplication);
var graphClient = new GraphServiceClient(authProvider);
var valid = true;
try {
foreach (var account in accounts.Split(';')) {
var user = await graphClient.Users.Request().Filter("startswith(userPrincipalName, '" + account + "')").GetAsync();
if (user.Count <= 0) {
valid = false;
break;
}
}
} catch (ServiceException ex) {
valid = false;
}
return valid;
}
On a side note, I'm not not sure of your requirements but you could probably get creative by combining multiple user names in single query and then checking for result counts or other propertes. You could use or between multiple criteria or probably use any operator. I haven't really tried this out though.
Finally I came up with something workable. It's not nice, and it uses preview software. First, install Microsoft.Graph and Microsoft.Identity.Client packages. Then install Microsoft.Graph.Auth, which at the time of this writing, is in preview (v1.0.0-preview.1) so you'll need to tick "include prerelease" checkbox in nuget manager.
Then in your AAD, you need to get the ClientId, TenantId and SecretId. In my case, my app was already using AAD authentication so I already had ClientId and TenantId in my appsettings.json file. I only needed to create a new SecretId (in the Certificate & secrets section of my app registration). Then I needed to add permissions (in the API permissions section of my app registration) to include Microsoft.Graph with at least User.Read.All permission.
using Microsoft.Identity.Client;
using Microsoft.Graph.Auth;
using Microsoft.Graph;
...
private async Task<bool> ValidateAccounts(string accounts) {
var confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create("clientId here")
.WithTenantId("tokenId here")
.WithClientSecret("secret here")
.Build();
var authProvider = new ClientCredentialProvider(confidentialClientApplication);
var graphClient = new GraphServiceClient(authProvider);
var valid = true;
try {
foreach (var account in accounts.Split(';')) {
var user = await graphClient.Users[account]
.Request()
.GetAsync();
if (user == null) {
valid = false;
break;
}
}
} catch (ServiceException ex) {
valid = false;
}
return valid;
}
Here, the function takes a semicolon-separated string for each account. The GetAsync method will throw a ServiceException if the user does not exist. I don't like that, but couldn't find another way. So that's about it. Hope this helps someone else, and hope someone could come up with a better solution eventually.
Import following namespaces (You needs to install relevant packages using nuget):
using Microsoft.Graph;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
Set your Azure AD app values:
private string _tenant => "your_tenant_id";
private string _appId => "your_ad_app_client_id";
private string _appSecret => "your_app_client_secret";
Create Graph Service Client using this:
public static GraphServiceClient CreateGraphServiceClient()
{
var clientCredential = new ClientCredential(_appId, _appSecret);
var authenticationContext = new AuthenticationContext("https://login.microsoftonline.com/{_tenant}");
var authenticationResult = authenticationContext.AcquireTokenAsync("https://graph.microsoft.com", clientCredential).Result;
var delegateAuthProvider = new DelegateAuthenticationProvider((requestMessage) =>
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", authenticationResult.AccessToken);
return Task.FromResult(0);
});
return new GraphServiceClient(delegateAuthProvider);
}
var graphServiceClient = GraphServiceClientHelper.CreateGraphServiceClient();
Then call graph api and filter users by Email Address as follows:
var user = await graphServiceClient.Users.Request().Filter("mail eq '" + UserEmailAddress + "'").GetAsync();
if (user.Count == 0) {
//user not exist
}
Below code worked for me.
using Microsoft.Graph;
using Microsoft.Graph.Auth;
using Microsoft.Identity.Client;
private static async Task<bool> ValidateAccounts(string accounts)
{
var confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create("client id")
.WithTenantId("tenant id")
.WithClientSecret("client secret")
.Build();
var authProvider = new ClientCredentialProvider(confidentialClientApplication);
var graphClient = new GraphServiceClient(authProvider);
var valid = true;
try
{
foreach (var account in accounts.Split(';'))
{
var user = await
graphClient.Users.Request().Filter($"identities/any(c:c/issuerAssignedId eq
'{account}' and c/issuer eq 'xyz.onmicrosoft.com')").GetAsync();
if (user.Count <= 0)
{
valid = false;
break;
}
}
}
catch(Exception ex)
{
valid = false;
}
return valid;
}
I've spent several days attempting to get to grips with ServiceStack and it seems great. Only issue is with authentication which seems to be a lot of friction, hard work and tears.
I want MonoTouch to register users, authenticate against ServiceStack/authenticate against OAuth and generally minimise calls to the database when authenticating.
So far I have got this:
var client = new JsonServiceClient(newbaseUri);
// register a new user:
var registration = new Registration {
FirstName = "john"
UserName = "user" ,
Password = "pass",
Email = "john#john.com",
};
var registerResponse = client.Send<RegistrationResponse>(registration);
--------------------------------
// user registered...later on I authenticate:
var authResponse = client.Send<AuthResponse>(new Auth {
UserName = "user",
Password = "pass",
RememberMe = true
});
var authResponse = clientlogin.Send<AuthResponse>(auth);
--------------------------------
// somehow I need to store 'authresponse' for later calls, not sure how without a browser
// tried manually setting the cookies and credentials parameters later but no joy
// but now I need to call a secured ([Authenticate] decorated) service method:
var client = new JsonServiceClient(newbaseUri);
var response = client.Send<HelloResponse>(new Hello { Name = "World!" });
return response.Result;
-----------------------------------------
// heres the configuration
var appSettings = new AppSettings();
//Default route: /auth/{provider}
Plugins.Add(new AuthFeature(() => new CustomUserSession(),
new IAuthProvider[] {
new CredentialsAuthProvider(appSettings), // never seems to get called
//new FacebookAuthProvider(appSettings), // not sure how to get this to work on monotouch
//new TwitterAuthProvider(appSettings), // same issue as facebook
new BasicAuthProvider(appSettings) // works but what about caching/tokens/cookies?
}));
//Default route: /register
Plugins.Add(new RegistrationFeature()); // how do i send extra params to this as created in mongodb collection
var mongoClient = new MongoClient("mongodb://localhost");
var server = mongoClient.GetServer();
var db = server.GetDatabase("users");
container.Register<ICacheClient>(new MemoryCacheClient());
container.Register<IUserAuthRepository>(new MongoDBAuthRepository(db, true));
My question is:
1) How do I enable extra fields to be passed in along with registration (as the mongodb [Servicestack.Authentication.Mongodb] has lots of empty fields i.e. birthdate, firstline, city, timezone, etc) that are not present in ServiceStack.Common.ServiceClient.Web.Registration object?
2) How can I transfer the cookie (or even maybe a token system) sent in the 'authresponse' to subsequent calls in order to allow ServiceStack to match against the session for ongoing authentication rather than more ongoing database calls that what seems to be issue with 'basic authentication' method (i.e CredentialsAuthProvider doesnt get called on server side)?
Please help...I've read documentation, run tests, examined social bootstrap and now I'm seriously losing days over this and thinking of integrating SS with simplemembership or even throwing ServiceStack away completely for old skool soap/wcf which is far easier to implement by the looks of it :(
1) If you want to use the Registration Plugin I don't think you can add additional fields since the Registration request/class is already defined. You could make your own registration Service and call into the RegistrationService/Plugin. Also, this post might be helpful.
[Route("/myregistration")]
public class MyRegistration : Registration //Add Additional fields for registration
{
public DateTime? BirthDate { get; set; }
public string Gender { get; set; }
}
public class MyRegisterService : Service
{
public IUserAuthRepository UserAuthRepo { get; set; }
public object Post(MyRegistration request)
{
using (var registrationService = base.ResolveService<RegistrationService>())
{
//handle the registration
var response = registrationService.Post(request.TranslateTo<Registration>());
}
//save the additional data
var userAuth = request.TranslateTo<UserAuth>();
UserAuthRepo.SaveUserAuth(userAuth);
//can make your own response or grab response from RegistrationService above
return new MyRegistrationResponse();
}
}
2) You can authenticate your JsonServiceClient and reuse it to make multiple requests.
var client = new JsonServiceClient(newbaseUri);
var authResponse = client.Send<AuthResponse>(new Auth {
UserName = "user",
Password = "pass",
RememberMe = true
}); //if successful your 'client' will have a populated CookieContainer with 'ss-id' and 'ss-pid' values
//reusing 'client' (after successful authentication) to make a request
//to a service requiring authentication
var response = client.Send<HelloResponse>(new Hello { Name = "World!" });
If reusing your 'client' is not an option you can try to store the ss-id. I don't know much about MonoTouch and how it stores 'browser sessions' so I'm not sure how you would accomplish this. After you authenticate and store the ss-id you can add it to the client using a Request Filter
//Get ss-id value
foreach(Cookie cookie in previousAuthenticatedClient.GetCookies(new Uri(newbaseUri)))
{
if (cookie.Name == "ss-id")
{
//store ss-id
}
}
var newClient = new JsonServiceClient(newbaseUri)
{
LocalHttpWebRequestFilter = (req) =>
{
req.CookieContainer.Add(new Uri("http://localhost:56006"), new System.Net.Cookie("ss-id", ssId));
}
};
I'm developing Windows Phone 8 application, and at first run I'm letting user auth from his Microsoft Account.
User credentials are saving great, but permission screen ("Let this app access your info") is appearing everytime I'm running application.
How can I save my choice and display permission screen only once (first time I'm running app)?
Code snippet:
private LiveConnectSession _session;
private async Task<Users> Authenticate()
{
var liveIdClient = new LiveAuthClient("XXXXXXXXXXXXXXX");
while (_session == null)
{
var result = await liveIdClient.LoginAsync(new[]
{
"wl.signin",
"wl.offline_access"
});
if (result.Status == LiveConnectSessionStatus.Connected)
{
_session = result.Session;
var client = new LiveConnectClient(result.Session);
var meResult = await client.GetAsync("me");
var user = await App.MobileService.LoginAsync(result.Session.AuthenticationToken);
return new Users
{
UserName = user.UserId,
RealName = string.Format("{0} {1}", meResult.Result["first_name"], meResult.Result["last_name"]),
TimeStamp = DateTime.Now,
IsAuthorised = false
};
}
else
{
_session = null;
MessageBox.Show("You must log in.", "Login Required", MessageBoxButton.OK);
}
}
return null;
}
And in the constructor (public MainPage()):
(DataContext as MainViewModel).User = await Authenticate();
your app needs wl.offline_access scope in order to not keep requesting auth over and over.
wl.offline_access
http://msdn.microsoft.com/en-us/library/live/hh243646.aspx#wlofflineaccess
Scopes and permissions (Live Connect)
http://msdn.microsoft.com/en-us/library/live/hh243646.aspx
this is the post I used to discover offline_access
http://dotnet.dzone.com/articles/things-know-about-uploading?mz=27249-windowsphone7
The code didn't work with my live account on emulator.
It is working well on device and emulator with other account.