I have an MVC4 Web Application on Web Server A that is consuming the Dynamics CRM Web Service using the OrganizationServiceProxy, which is on Web Server B. The MVC4 application is setup with ASP .NET Impersonation and Windows Authentication enabled. When I call the WhoAmI I get an error:
'The caller was not authenticated by the service.'
Now if I move the MVC4 Application to Web Server B (same as CRM) with the same Authentication as it had on Web Server A it calls WhoAmI without an exception.
Here is the code being used to connect to the server.
string serviceURL = ConfigurationManager.AppSettings["CRMROOTURL"].ToString() + "XRMServices/2011/Organization.svc";
this.CRMService = GetCRMService(serviceURL);
private OrganizationServiceProxy GetCRMService(string serviceURL)
{
ClientCredentials credentials = new ClientCredentials();
credentials.Windows.ClientCredential = CredentialCache.DefaultNetworkCredentials;
OrganizationServiceProxy client
= new OrganizationServiceProxy(new Uri(serviceURL), null, credentials, null);
return client;
}
Here is a screenshot of the authentication on the IIS Web Site.
Per the correct answer I just wanted to provide some snippets to help anyone else.
string loggedUser = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
ClientCredentials credentials = new ClientCredentials();
credentials.Windows.ClientCredential = new NetworkCredential(username, password, domain);
OrganizationServiceProxy client
= new OrganizationServiceProxy(new Uri(serviceURL), null, credentials, null);
client.ClientCredentials.Windows.ClientCredential = credentials.Windows.ClientCredential;
// -- Retrieve the user.
QueryExpression expression = new QueryExpression
{
EntityName = "systemuser",
ColumnSet = new ColumnSet("systemuserid")
};
expression.Criteria.AddCondition("domainname", ConditionOperator.Equal, loggedUser);
EntityCollection ec = client.RetrieveMultiple(expression);
if (ec.Entities.Count > 0)
{
// -- Impersonate the logged in user.
client.CallerId = ec.Entities[0].Id;
}
Thanks!
Unless you explicitly state otherwise (and without any code to see how you are creating your OrganizationServiceProxy), on premise OrganizationServiceProxies will use the current AD account (of the service account, not the user's specific account) to connect to CRM. I'm guessing that the App pool you're running on Server A isn't a CRM user, and the one on Server B is. If so, either change Server A's user to be the same user as Server B, or make the Server A's user a user in CRM.
Edit
You're using the default network credentials to connect to CRM. This mean that no matter what IIS authentication you are using, you will connect to CRM as the App Pool User Account. This works as long as the App Pool user is a CRM user, but is probably not what you want.
You can set the network credential manually using this method:
creds.Windows.ClientCredential = new System.Net.NetworkCredential("UserId", "Password", "DomainName");
Then get the ASP.Net User's domain name and use impersonation to connect to CRM to ensure that all of the security for that individual is correctly applied.
Something stupid - be careful you aren't escaping your user name!
creds.Windows.ClientCredential = new NetworkCredential("domain\user", "PASSWORD");
Notice that the \u is an escape sequence - you need to type "domain\user".
Related
I'm trying to build a (C#) web app that allows clients to make appointments with me.
I'd like the web app to be able to read and add entries to my outlook calendar.
Many users will use the web app, but the web app will only access one outlook calendar - mine.
All of the examples I have been able to get working have involved the web app user interactively authenticating - but my users will not know my password.
I would like to hard code my username/email address and password in the web app.
When trying to acquire a token I get an error:
Microsoft.IdentityModel.Clients.ActiveDirectory.AdalServiceException:
AADSTS65001: The user or administrator has not consented to use the application.
Send an interactive authorization request for this user and resource.
I am not an administrator of the tenant. Is there any way I can get this to work without administrator involvement?
Would using some kind of certificate rather than a user name and password as user credentials help?
My code (currently in a simple C# console application) is as follows:
UserCredential uc = new UserCredential(MyUsername, MyPassword);
var AuthContext = new AuthenticationContext("https://login.windows.net/Common");
// this doesn't work unless an unexpired token already exists
ar = AuthContext.AcquireToken("https://outlook.office365.com/", MyClientId, uc);
// this does work, but requires the app user to know the password
ar = AuthContext.AcquireToken("https://outlook.office365.com/", MyClientId, new Uri(MyReturnURI));
To enable use the username and password to request the token directly, we need to consent to use the app.
We can use the OAuth 2.0 authorization code grant flow to grant the consent by user. Here is an sample use the ADAL authentication library(3.13.1.846) to acquire the delegate token:
static string authority= "https://login.microsoftonline.com/common";
public static string GetDeligateToken(string resource, string clientId,string redirectURL)
{
AuthenticationContext authContext = new AuthenticationContext(authority);
AuthenticationResult authResult= authContext.AcquireTokenAsync(resource, clientId,new Uri(redirectURL), new PlatformParameters(PromptBehavior.Auto)).Result;
return authResult.AccessToken;
}
After we consent the app, now we can use the code in your post to acquire the token.
I have setup a SharePoint dev environment and managed to get a provider hosted app working with Certs etc (High Security)
This is all on-premise and we won't have a connection to ACS (now, I may have miss understood ACS, I presume its azure based and servers need to talk to server outside of the server room :) ).
My problem is:
The SharePoint site will not be using Windows Auth, we will be using a login form which will read details from another store.
If I review the code that VS generates I can see that it expects a Windows identity.
Can this be done? I would have expected my provider app not to need any auth as its hosted via SharePoint, it gets the claim from SharePoint so why does it need a Windows Identity as well as the SharePoint Claims.
As you said : The SharePoint site will not be using Windows Auth, we will be using a login form which will read details from another store.
Ans : You are talking about Form based authentication.
If I review the code that VS generates I can see that it expects a Windows identity.
Ans : You are correct. VS generates the code that expects windows identity [For dev environments]. You need to write separate function to get clientcontext for FBA using SharePointContextProvider class.
Context can be :CreateAppOnlyClientContextForSPHost(),CreateUserClientContextForSPHost()
I would have expected my provider app not to need any auth as its hosted via SharePoint
Ans : You can make IIS site [Hosting your provider hosted apps] to allow annonymous authentication.
You can get access token with this code.
var contextTokenString = TokenHelper.GetContextTokenFromRequest(Page.Request);
var appWeb = new Uri(clientContext.Web.Url);
if (contextTokenString != null)
{
SharePointContextToken contextToken =
TokenHelper.ReadAndValidateContextToken(contextTokenString, Request.Url.Authority);
string accessToken =
TokenHelper.GetAccessToken(contextToken, appWeb.Authority).AccessToken;
}
Get current user :-
// Get current context to get load siteurl
var spContext = SharePointContextProvider.Current.GetSharePointContext(HttpContext);
string webUrl =spContext.SPHostUrl.ToString();
//using (var clientContext = spContext.CreateUserClientContextForSPHost())
using (ClientContext clientContext = new ClientContext(webUrl))
{
clientContext.AuthenticationMode = ClientAuthenticationMode.FormsAuthentication;
clientContext.FormsAuthenticationLoginInfo = new FormsAuthenticationLoginInfo(uName, pswd);
Web web = clientContext.Web;
clientContext.Load(web);
clientContext.ExecuteQuery();
// Load SP user from login name found from httpcontext
string currentSPUser = string.Concat("<<<FBADomainPrefix>>>",User.Identity.Name);
var currentUser = clientContext.Web.EnsureUser(currentSPUser);
clientContext.Load(currentUser);
clientContext.ExecuteQuery();
}
I have got an ASP.NET MVC 4 Intranet Application.
The application uses the windows authentication for authenticating users.
I can get the Username with User.Identity.Name. This contain the domain name and username (MyDomain\Username).
I now want to add an appointment to the users calender via the Exchange Web Service API.
I can do this like the following:
var service = new ExchangeService(ExchangeVersion.Exchange2010_SP2);
service.Credentials = new WebCredentials(Settings.MyAccount, Settings.MyPassword);
service.Url = new Uri(Settings.ExchangeServer);
var appointment = new Microsoft.Exchange.WebServices.Data.Appointment(service);
appointment.Subject = setAppointmentDto.Title;
appointment.Body = setAppointmentDto.Message;
appointment.Location = setAppointmentDto.Location;
...
appointment.Save(SendInvitationsMode.SendToAllAndSaveCopy);
This adds an appointment for the user specified in the credentials.
I do not have the password for the currently logged in user.
Since I am using Windows Authentication (Active Directory Account), is there a way to somehow use this authentication information to use the Exchange Web service with the account of the user who uses the web application?
Because of security it is not possible to retrieve the user's password from Active Directory.
Is there another way of doing this?
Is it possible to create an appointment for another user as the user who uses the service?
Greetings
Alexander
You have two options for setting the Credentials.
// Connect by using the default credentials of the authenticated user.
service.UseDefaultCredentials = true;
or
// Connect by using the credentials of user1 at contoso.com.
service.Credentials = new WebCredentials("user1#contoso.com", "password");
Source for the above and full info is here http://msdn.microsoft.com/EN-US/library/office/ff597939(v=exchg.80).aspx
Microsoft also recommends using Autodiscover to set the URL endpoint
// Use Autodiscover to set the URL endpoint.
service.AutodiscoverUrl("user1#contoso.com");
If you wanted to create an appointment for another user you would use
appointment.RequiredAttendees.Add("user2#contoso.com");
or
appointment.OptionalAttendees.Add("user3#contoso.com");
Depending if they were required or optional.
However, this changes the appointment to a meeting. The meeting request is just an appointment with attendees.
I have a service that is setup to retrieve a secure token from ADFS and use that token to communicate with other services. When I contact my ADFS windowsmixed endpoint from my local development machine hitting the ADFS service I am able to successfuly retrieve the token. However, when I install my service on the same machine that is running ADFS I receive the following error:
Secure channel cannot be opened because security negotiation with the remote endpoint has failed. This may be due to absent or incorrectly specified EndpointIdentity in the EndpointAddress used to create the channel. Please verify the EndpointIdentity specified or implied by the EndpointAddress correctly identifies the remote endpoint.
I am able to reproduce the error with the following code that simply gets the token. Again this code works when I am on my dev machine hitting the remote server, but it fails when on the server directly. I am using the same user credentials on both. I get the same error within the IIS web service using the app pool credentials and with a simple test client using the code below.
private static SecurityToken GetToken()
{
string stsEndpoint = "https://adfsserver.com/adfs/services/trust/13/windowsmixed";
string appliesTo = "http://domain.com/application/myapplication";
var factory = new WSTrustChannelFactory(
new WindowsWSTrustBinding(SecurityMode.TransportWithMessageCredential),
stsEndpoint);
factory.TrustVersion = TrustVersion.WSTrust13;
var rst = new RequestSecurityToken
{
RequestType = RequestTypes.Issue,
AppliesTo = new EndpointAddress(appliesTo),
KeyType = KeyTypes.Symmetric
};
var channel = factory.CreateChannel();
return channel.Issue(rst);
}
I turned on tracing in the Windows Event Log for ADFS 2.0 debug. When hitting that windowsmixed endpoint directly on the server, I do not receive any entries which leads me to belive that it is not actually getting to the endpoint.
I do receive quite a few audit failures in the security log that are related to the services that I am running:
A handle to an object was requested.
Subject:
Security ID: DOMAIN\ODI$ODIController
Account Name: ODI$ODIController
Account Domain: DOMAIN
Logon ID: 0x1a574b5
Object:
Object Server: SC Manager
Object Type: SERVICE OBJECT
Object Name: WinHttpAutoProxySvc
Handle ID: 0x0
Process Information:
Process ID: 0x1f8
Process Name: C:\Windows\System32\services.exe
Access Request Information:
Transaction ID: {00000000-0000-0000-0000-000000000000}
Accesses: Query status of service
Start the service
Query information from service
Access Reasons: -
Access Mask: 0x94
Privileges Used for Access Check: -
I am able to access the usernamemixed endpoint using stored credentials and receive the proper token, so it seems to be something with authenticating the user to even be able to communicate with the ADFS endpoint.
If I set specific credentials in the code above, it is able to connect. Which again leads me to believe that it is not passing the correct credentials for my Windows user when on the same machine.
factory.Credentials.Windows.ClientCredential = new System.Net.NetworkCredential("UserID", "password1", "dev.domain");
Thank you for any assistance you can provide.
Brian
I had a similar issue. I was able to get it working using the example from here: http://blogs.southworks.net/mwoloski/2009/07/17/getting-a-token-from-adfs-ex-geneva-server-using-wcf/
The difference between your code and the working example is that you modify the message security to use the current security credentials in the binding rather than on the client. If you are using WIF 4.0, you need to modify the code to use a WSTrustChannelFactory instead of WSTrustClient. The other code doesn't change much though.
My code for the factory looks like this:
var binding = new WS2007HttpBinding(SecurityMode.TransportWithMessageCredential);
binding.Security.Message.ClientCredentialType = MessageCredentialType.Windows;
binding.Security.Message.EstablishSecurityContext = false;
var factory = new WSTrustChannelFactory(
binding,
new EndpointAddress(new Uri(sts), EndpointIdentity.CreateUpnIdentity(adfsUpn)));
I have a custom WCF web-service confugured with windows authentication and a WPF client application that needs to call the former. The service checks the username and pull some specific data from a database. So I have to call the service using credentials of the user running the application.
The problem is my service is hosted under another site with windows authentication and users can authenticate there with another accounts. Windows (or IE?) caches last accout used and then my client app uses it too!
Example:
I enter the website under "MYDOMAIN\AdminUser"
I run following code (from the client app, it's not web code)
var client = new TestServiceClient();
var currentUser = WindowsIdentity.GetCurrent(); // just informative field nothing more, i don't use it anyhow
// currentUser.Name = "MYDOMAIN\\MyUserName" - it's current value, i'm not trying to set it
client.ClientCredentials.Windows.ClientCredential = CredentialCache.DefaultNetworkCredentials;
var data = client.GetTestData();
Service gets called by "MYDOMAIN\AdminUser"..
I know I can create NetworkCredential with name and password but I then will have to store it somewhere, encript it and so on..
To clarify the problem: client process running under one account calls the service under another account by itself, just becouse windows supplies the call with another credentials under the hood.