I'm trying to call a Web API 2 method that requires auth from a console app running on my desktop where I have authorization, but I'm getting 401 Unathorized. I know I have authorization because when I make the same call from a web browser it works fine. So a browser can get my default login id/pw to send to the API but .NET's WebClient can't? That seems insane. There has to be a way to do this without entering my id/pw into the console app.
The below is what I'm using in a console app and it's not working.
This is using Windows Auth as it's intranet stuff.
This throws an exception "The remote server returned an error: (401) Unauthorized."
using(var c = new WebClient())
{
c.UseDefaultCredentials = true;
string value = c.DownloadString("http://localhost:62659/api/Store/GetData");
}
I also tried the below and when I mouse over DefaultNetworkCredentials the username/pw are blank strings. Why wouldn't .NET be able to figure this out?
using(var c = new WebClient())
{
var creds = new CredentialCache();
var uri = new Uri("http://localhost:62659/api/Store/GetData");
creds.Add(uri, "ntlm", System.Net.CredentialCache.DefaultNetworkCredentials);
c.Credentials = creds;
string value = c.DownloadString(uri);
}
It is likely that you do not have your credential information stored within Windows Credential Manager. You can access that via Control Panel > Credential Manager. From there you can add whatever credential you need. CredentialCache.DefaultCredentials and CredentialCache.DefaultNetworkCredentials contains the login credentials of the currently logged-in or impersonated user. If what you are connecting to requires different credentials then these will not work. You will need to add those credentials to the Credential Manager in Windows.
The reason you are connecting fine within Chrome is that Chrome will store credentials within itself that you have designated to save.
Login credentials being used as functional ids can be set to never expire, or it will need to be added to a list of monthly/yearly maintenance items to update the password for those accounts.
You would also want to handle bad login information within your application. If this is an automated task, have it email or otherwise notify someone that the credentials need to be updated.
If a user runs this, you could simply prompt the current user to provide a new password, which you can use to update the stored credentials right then.
Another option would be to set the user running the application as a user on the receiving end using those same credentials. That way the entire process is tied to the user(s) that will be running the application.
Using DefaultCredentials should work to use Windows Auth from console application. As long as you have the appropriate authorization header that your web api is looking for. Same with my comment I recommend testing the api call using Postman so that you can troubleshoot and check what you are missing.
Regarding the credentials as blank, this is maybe because you are using DefaultNetworkCredentials.
Try this:
using(var c = new WebClient())
{
var uri = new Uri("http://localhost:62659/api/Store/GetData");
c.Credentials = System.Net.CredentialCache.DefaultCredentials;
string value = c.DownloadString(uri);
}
If you want to use NetworkCredential you should be inputting network credentials like so:
c.Credentials = new NetworkCredential(username, password, domain);
I created this type of console application and used it as a service and I can tell you that this should work. You should just need to troubleshoot and bits by bits get the real problem.
Related
So this is an issue that has been plaguing me for a bit and my deadline is coming up. I'm working on an application that sends emails and my workplace uses Office365 via Exchange. I'm using a C# webapp and using Mailkit to deliver emails.
The issue (not really an issue but good practice that's getting in my way) is that we made an email account to deliver mail yet our organization requires MFA. After talking about it with my director, creating an app password would not be a good idea for how this program is deployed so I'm trying to find ways to authenticate properly.
I eventually landed on using the Microsoft.Identity.Client library to require logging in via a registered Azure application. I could then cache this and refresh as needed, this way making sure the access is still valid.
However, I'm stuck on something. I have the app registration set to public with no client secrets or certificates with all of the necessary permissions. However right at the var oauth2 step, it fails while giving the error "Original exception: AADSTS7000218: The request body must contain the following parameter: 'client_assertion' or 'client_secret'."
The issue is that the application is public and is defined to allow public client flows. So I'm not getting how the request could still require a client secret when that's not how I'm building the request at all. I tried using private, but because of the MFA requirement, that fails too.
Below is what I have. Ignore that I'm hard coding stuff; it's temporary until I can get this sorted out. I'm also only scoping the SMTP permissions because all this application needs to do is send an email; IMAP isn't needed since it's not reading or anything else.
var options = new PublicClientApplicationOptions
{
ClientId = "[clientID]",
TenantId = "[tenandID]",
RedirectUri = "http://localhost"
};
var publicClientApplication = PublicClientApplicationBuilder
.CreateWithApplicationOptions(options)
.Build();
var scopes = new string[] {
"email",
"offline_access",
"https://outlook.office.com/SMTP.Send" // Only needed for SMTP
};
var authToken = await publicClientApplication.AcquireTokenInteractive(scopes).ExecuteAsync();
//Here is where it returns that it needs a client_secret and won't advance.
// The login window appears and states it was successfully authenticated,
// but the application crashes with that error at this step.
var oauth2 = new SaslMechanismOAuth2(Config.Env.smtpUser, authToken.AccessToken);
I am creating a console application that connects to Microsoft Graph using the Microsoft Graph API (as shown in https://github.com/microsoftgraph/console-csharp-connect-sample).
Everything is working fine, but I wonder if there is a way where I can authenticate a user (when I already know their user/password) without them needing to manually enter their credentials on the "Sing in to your account" window rendered on the desktop.
The idea is basically to run the application unattended, so there is no need for the user to be entering their credentials when the application starts. I canĀ“t find any relevant information on the subject.
Is that even possible?
EDIT
After following the link #DanSilver posted about geting access without a user, I tried the sample suggested in that link (https://github.com/Azure-Samples/active-directory-dotnet-daemon-v2). Although that is an MVC application that forces users to authenticate (precisely what I wanted to avoid) I have managed to use part of the authentication code in that sample with my console application. After giving authorization to the application manually through a request to https://login.microsoftonline.com/myTenantId/adminconsent I can create a GraphServiceClient in my console app that connects to Graph without user interaction. So I mark the answer as valid.
Just in case someone is in the same situation, the GraphServiceclient is created as:
GraphServiceClient graphServiceClientApplication = new GraphServiceClient("https://graph.microsoft.com/v1.0", new DelegateAuthenticationProvider(
async (requestMessage) =>
{
string clientId = "yourClientApplicationId";
string authorityFormat = "https://login.microsoftonline.com/{0}/v2.0";
string tenantId = "yourTenantId";
string msGraphScope = "https://graph.microsoft.com/.default";
string redirectUri = "msalXXXXXX://auth"; // Custom Redirect URI asigned in the Application Registration Portal in the native Application Platform
string clientSecret = "passwordGenerated";
ConfidentialClientApplication daemonClient = new ConfidentialClientApplication(clientId, String.Format(authorityFormat, tenantId), redirectUri, new ClientCredential(clientSecret), null, new TokenCache());
AuthenticationResult authResult = await daemonClient.AcquireTokenForClientAsync(new string[] { msGraphScope });
string token = authResult.AccessToken;
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", token);
}
));
One idea is using the "app only" authorization flow. The idea is that you can have long running apps access the Microsoft Graph without user authentication. The main difference is instead of the access token granting access to a particular user, it grants your app access to resources that you've consented to in advance. There will be no user login dialog and you can programmatically fetch access tokens to call the Graph API.
To reiterate that these tokens aren't for a particular user, consider making a GET request to 'https://graph.microsoft.com/v1.0/me'. This will return an error since the access token isn't for a particular user and "me" doesn't mean anything. Requests should be sent with full user ids "like graph.microsoft.com/users/someuser#contosos.com".
More information on this can be found at the Get access without a user documentation page.
Another idea is to let the user authenticate the first time they use your app and then store a refresh token. These tokens live longer (a few months IIRC) and then you won't need to prompt for user consent each time the app runs. Refresh tokens can be exchanged for access tokens that live 60 minutes and those can be used to call Graph API on behalf of users.
More info on refresh tokens: https://developer.microsoft.com/en-us/graph/docs/concepts/auth_v2_user#5-use-the-refresh-token-to-get-a-new-access-token
I did want to come back out here and share, since I ran into this problem yesterday, and the idea of granting read/write mailbox access for my application... to EVERYONE'S EMAIL BOX IN THE ENTIRE ORGANIZATION... was way over the top for my needs. (And that is exactly what happens when you start talking about granting Application level permissions instead of delegated permissions to your registered app).
It's a simple use case: I had a nightly process that needed to automate sending of emails from a shared mailbox using a traditional AD service account.
Thankfully... even though they are on the march to eliminate passwords (lol)... someone at Microsoft still recognizes my use case, and it's lack of apples-to-apples alternatives in Azure AD. There is still an extension method we can lean on to get the job done:
private AuthenticationContext authContext = null;
authContext = new AuthenticationContext("https://login.microsoftonline.com/ourmail.onmicrosoft.com",
new TokenCache());
result = authContext.AcquireTokenAsync("https://graph.microsoft.com/",
"12345678-1234-1234-1234-1234567890",
new UserPasswordCredential(
Environment.GetEnvironmentVariable("UID", EnvironmentVariableTarget.User),
Environment.GetEnvironmentVariable("UPD", EnvironmentVariableTarget.User)
)).Result;
You can replace those GetEnvironmentVariable calls with your Username (UID) and Password (UPD). I just stuff them in the environment variables of the service account so I didn't have to check anything into source control.
AcquireTokenAsync is an extension method made available from the Microsoft.IdentityModel.Clients.ActiveDirectory namespace. From there, it's a simple business to fire up a GraphClient.
string sToken = result.AccessToken;
Microsoft.Graph.GraphServiceClient oGraphClient = new GraphServiceClient(
new DelegateAuthenticationProvider((requestMessage) => {
requestMessage
.Headers
.Authorization = new AuthenticationHeaderValue("bearer", sToken);
return Task.FromResult(0);
}));
The last bit of magic was to add these permissions to Application registration I created in Azure AD (where that GUID came from). The application has be defined as a Public client (there's a radio button for that towards the bottom of the authentication tab). I added the following 5 DELEGATED permissions (NOT application permissions):
Microsoft Graph
1. Mail.ReadWrite.Shared
2. Mail.Send.Shared
3. User.Read
4. email
5. openid
Since user consents are actually blocked in our organization, another permissions admin had to review my application definition and then do an admin level grant of those rights, but once he did, everything lit up and worked like I needed: limited access by a service account to a single shared mailbox, with the actual security of that access being managed in Office 365 and not Azure AD.
I am getting a consent error when trying to obtain a token. Because of our application, we can't show an interactive dialog to give consent.
"AADSTS65001: The user or administrator has not consented to use the
application with ID <'my native client app id'>. Send an
interactive authorization request for this user and resource.
AuthenticationContext ctx = new AuthenticationContext(
string.Format("https://login.microsoftonline.com/{0}","mytenant.onmicrosoft.com"));
UserPasswordCredential cred = new UserPasswordCredential("login#mytenant.onmicrosoft.com", "Password");
var result = ctx.AcquireTokenAsync("my api uri", "my native client id", cred);
We are using the grant_type=password and client_id is a Native app id, and resource is the Web API app URI.
Permissions-wise, from the client app, a delegated permission has been given to access the api app and have also tried setting oauth2AllowImplicitFlow : true in the manifest.
All applications have been created in the new preview Azure AD section of the new portal (portal.azure.com)
Unfortunately if your application needs access to certain resources like the Graph API, you will need to prompt for consent at least one time.
Even if your app doesn't have an interactive login experience, you should be able to prompt this once to unblock your scenario in your tenant.
Use the following URL:
https://login.microsoftonline.com/<TenantID>/oauth2/authorize?client_id=<AppID>&response_type=code&redirect_uri=<RedirectURI>&resource=<ResourceURI>&prompt=admin_consent
You can see here we have just simply generated the login URL which would be generated as part of an interactive login experience. You will need to fill out your own specific data like Reply URL, App ID, Resource URI, etc...
Note that we added a final query string at the end where we are forcing a "consent" prompt. This should be done by an Administrator, who would be able to consent on behalf of the whole tenant. Once you have done that, the username/password flow should start working for you.
Also, as an additional note, implicit grant flow has nothing to do with consent.
Please read this section in the OAuth 2 spec:
https://www.rfc-editor.org/rfc/rfc6749#section-1.3.2
You should only use this setting if you are creating a single-page application with something like JavaScript; Otherwise, there are significant security concerns with this setting on applications that should not have it.
I'm developing UWP application using C#.net and it has WCF service with Windows Authentication enabled. I struggling to pass default NetworkCredential after consume a service call using Add service reference option.
Please find below my examinations.
When I pass correct windows authentication credentials, it is working as expected.
var service = new ServiceReference.Service1Client();
service.ClientCredentials.Windows.ClientCredential =new NetworkCredential("pradeep","****");
var test = await service.GetDataAsync(1);
but, I wanted pass default network credentials while using my service methis
var service = new ServiceReference.Service1Client();
service.ClientCredentials.Windows.ClientCredential = System.Net.CredentialCache.DefaultNetworkCredentials;
var test = await service.GetDataAsync(1);
I also tried below option.
service.ClientCredentials.Windows.ClientCredential = (NetworkCredential)CredentialCache.DefaultCredentials;
When I pass default credentials. I'm getting below exception.
The HTTP request is unauthorized with client authentication scheme
'Negotiate'. The authentication header received from the server was
'Negotiate, NTLM'.
I tested same service call with default NetworkCredential in WPF application which is working as expected.
In order to pass the default credentials for the WCF Windows Authentication in UWP by using the System.Net.CredentialCache.DefaultNetworkCredentials, first please make sure that you have added the Enterprise Authentication and Private Networks(Client & Server) capabilities as following:
For the Enterprise Authentication capability, it is because that Windows domain credentials enable a user to log into remote resources using their credentials, and act as if a user provided their user name and password. The enterprise Authentication special capability is typically used in line-of-business apps that connect to servers within an enterprise.
For the Private Networks(Client & Server) capability, it is because that currently in Windows Runtime we can only pass the default credential in the Intranet. For the Internet we have to use the Username and Password as credential.
For more information about the Capabilities, please check:
https://msdn.microsoft.com/en-us/library/windows/apps/hh464936.aspx .
After that please try to use your Computer name or Fully Qualified Computer name instead of the IP address for your WCF Services like this: http://YourComputerName:YourPortNumber/Service1.svc.
At last please use another computer as client to test the WCF Windows Authentication in UWP with the System.Net.CredentialCache.DefaultNetworkCredentials, then it should work fine.
Thanks.
So I have a windows forms application that loads an infopath form(.xml) from a sharepoint library, and does some processing to it. I created an exe out of it and I just supply a sharepoint library url to it through cmd and it pulls up the form.
I also have a web service which runs on an IIS server that calls the exe and displays infomartion for specific users. Now the problem is when I call the exe from then web service, it kept on asking for a login prompt. So i figured the web service must be running as a System account, and i supplied credentals through a number of ways
WebRequest request = WebRequest.Create(sharepoint_url);
request.Credentials = CredentialCache.DefaultNetworkCredentials;
or
request.Credentials = new NetworkCredential("","");
I also tried System.Net.CredentialCache mycache = new System.Net.CredentialCache();
mycache.Add(formUrlName, "Basic", new System.Net.NetworkCredential("", "")); and
request.Credentials = mycache;
But all the time i got an exception being thrown....Infopath cannot open this frm...the signature on this form is not from a trusted publisher.
Then I tried loading my project along with the web service and creating a new form ( Form form2 = new Form()), but doing the authentication procedure first. Now I get a http 401 unauthorized error.
(I use FormControl.Open(url) to open the form fromm the sharepoint library)
What am i doing wrong?
UPDATE:
I checked with the admin of the sharepoint library...apparently, the credentials are not being received at all. I dont know if they are not being sent properly or whether sharepoint is just dropping the credentials and not accepting it.