For my final year project I've developed an ASP.NET website and I've implemented a Single Sign On Login System using Windows Identity Foundation (in a similar manner to the tutorial shown here: http://www.primaryobjects.com/2013/08/08/using-single-sign-on-with-windows-identity-foundation-in-mvc-net )
This means that I currently have a 2 Websites, my Identity Provider Site and the site that uses the IP and contains most of the functionality.The IP uses X509 certificate to generate the token and for this I've been able so far to use a self signed certificate. This is the code that I've been using to retrieve the certificate from the local machine:
var store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
X509Certificate2Collection certificates = null;
store.Open(OpenFlags.ReadOnly);
try
{
certificates = store.Certificates;
var certs = certificates.OfType<X509Certificate2>().Where(x => x.SubjectName.Name.Equals(subjectName, StringComparison.OrdinalIgnoreCase)).ToList();
if (certs.Count == 0)
throw new ApplicationException(string.Format("No certificate was found for subject Name {0}", subjectName));
else if (certs.Count > 1)
throw new ApplicationException(string.Format("There are multiple certificates for subject Name {0}", subjectName));
return new X509Certificate2(certs[0]);
}
In order to be able to present my project I will be asked to host it on the web, but this will mean that I'll need a substitute for my self signed certificate.
I could use something like azure websites to host my websites but I wasn't able to find a solution so far that would allow me to generate a self signed certificate on a service like azure and retrieve it programatically.
Could you please suggest me a solution for this problem?
Ideally, you wouldn't have to create your own IDP. You could use something like ADFS.
Azure AD is an IDP and has its own certificates so you don't need to create any.
All you need is the metadata and you can find the URL under AppServices / Endpoints.
There's an example here but note that this uses WS-Fed OWIN not WIF. (WIF is somewhat old school).
To make your life easier, you can update your web.config programmatically by adding code to global.asax.
Or just move your whole solution to a VM in Azure.
Related
I'm tried to pull some SharePoint 2013 list data I created which works fine when running locally on my machine and when run locally one the server. I'm user the same credentials when running both locally and locally on the server. The issue is when I publish and navigate to my ASP.NET app on the server I get the "The remote server returned an error: (401) Unauthorized." Error...
I've looked at a bunch of the posts on stackoverflow and some other articles on the web
This points out that the context seems to be using IUSR:
http://blogs.msdn.com/b/sridhara/archive/2014/02/06/sharepoint-2013-csom-call-from-web-part-fails-with-401-for-all-users.aspx
This one mentions to try setting the default network credentials:
https://sharepoint.stackexchange.com/questions/10364/http-401-unauthorized-using-the-managed-client-object-model
I've tried using the fixes mentioned in the article as well as trying to force the context to use DefaultNetworkCredentials but no luck. I would like for the app to use the credentials of the logged in user and not the machine...
Here is the code I'm using:
SP.ClientContext context = new SP.ClientContext("MySPDevInstance");
context.Credentials = CredentialCache.DefaultNetworkCredentials;
Entity entity = context.Web.GetEntity(collectionNamespace, collectionName);
LobSystem lobSystem = entity.GetLobSystem();
LobSystemInstanceCollection lobSystemInstanceCollection = lobSystem.GetLobSystemInstances();
context.Load(lobSystemInstanceCollection);
context.ExecuteQuery();
LobSystemInstance lobSystemInstance = lobSystemInstanceCollection[0];
FilterCollection filterCollection = entity.GetFilters(filter);
filterCollection.SetFilterValue("LimitFilter", 0, 1000);
EntityInstanceCollection items = entity.FindFiltered(filterCollection, filter, lobSystemInstance);
The server is running IIS 6.0
Any advice would be much appreciated!
Thank you
I presume your ASP.NET web site is using Windows Integrated (NTLM) authentication. A user authenticated this way cannot authenticate to a second location from the server side (the web server.) You are experiencing what is known as the "double-hop" (1) limitation of NTLM. You must use a dedicated account on the server side, or if you really do want to use the logged-in user's identity, you must use an authentication scheme that permits delegation, such as Kerberos.
If you really need the user's identity to access SharePoint data and you cannot change the authentication scheme, then the best way to do this is to use the JavaScript CSOM. This means the user is authenticating directly to the SharePoint server (a single hop, not double) and your ASP.NET site serves the page containing this script to the user.
(1) http://blogs.msdn.com/b/knowledgecast/archive/2007/01/31/the-double-hop-problem.aspx
Use Default Credentials worked for me:
HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(url);
httpWebRequest.UseDefaultCredentials = true;
Setup the crendentials by code:
SP.ClientContext context = new SP.ClientContext("MySPDevInstance");
context.Credentials = new NetworkCredential("username", "password");
You should put this at the configuration file to change it without publishing or recompiling the application.
Just to add one more setting that I encountered. If the account is restricted to access only certain servers than add the client machine to that account as well. For example if a web application is hosted on Server A and trying to connect to SharePoint 2010 on Server B with account ABC then make sure that account has access to Server A in Active Directory. Normally the AD account doesn't have restrictions to connect to machines but in my case the account was restricted to only certain machines. I added my web application hosted server to the account and it worked.
`
//Signing a text using etoken class 3 platinum
static byte[] Sign(string text, string certSubject)
{
// Access Personal (MY) certificate store of current user
X509Store my = new X509Store(StoreName.My, StoreLocation.CurrentUser);
my.Open(OpenFlags.ReadOnly);
// Find the certificate we'll use to sign
RSACryptoServiceProvider csp = null;
foreach (X509Certificate2 cert in my.Certificates)
{
if (cert.Subject.Contains(certSubject))
{
// We found it.
// Get its associated CSP and private key
csp = (RSACryptoServiceProvider)cert.PrivateKey;
}
}
if (**csp == null**)
{
throw new Exception("No valid cert was found");
}
// Hash the data
SHA1Managed sha1 = new SHA1Managed();
UnicodeEncoding encoding = new UnicodeEncoding();
byte[] data = encoding.GetBytes(text);
byte[] hash = sha1.ComputeHash(data);
// Sign the hash
return csp.SignHash(hash, CryptoConfig.MapNameToOID("SHA1"));
}`
This codesnipt works well in visual studio but returns csp as null while using this code in domain server and also in local IIS Server.Please tell me what is the point that i am missing.
The application pool identity has no access to the certificate.
Note that you are accessing the store of the current user. This works in VS because you are the current user. However, when you deploy the application to iis, the application pool identity is most probably different.
A recommendation - do not use the current user's cert store. The reason for that is when you switch app pool's identities, you have to log into each account and import certificates to corresponding cert stores. Rather, use local machine cert store which is shared by all users. There is a small issue here - you have to place certificates in the Personal store of the local machine store and manually set permission to access the private key to the app pool's identity.
You do this by right clicking the certificate, picking the "manage private keys" and then adding the app pool's identity to the list of users allowed to access the private key.
When I change StoreLocation.CurrentUser to StoreLocation.local machine,then it shows subject as localhost and issuer also as localhost.this changes generate appropriate results.What more changes i need ,so to integrate the facility of using class 3 etoken in my website,so that any one can upload or login using their own class 3 etoken.
I have a website where a users identity is needed, I'd really prefer not to make them create yet another username/password combo that they have to remember
are there SDK's for allowing authentication from an Microsoft account?
That's rather easy as a default empty template of an ASP.NET 4.5 website shows how to have OAuth2 authentication with google/facebook/liveid/twitter.
http://www.asp.net/aspnet/overview/aspnet-45/oauth-in-the-default-aspnet-45-templates
Check out the Principal Context class. You can create it using a localhost (Machine) or domain context and use the ValidateCrentials(string username, string password) method to authenticate using Windows credentials.
http://msdn.microsoft.com/en-us/library/bb154889.aspx
Here's how I've used it in my website. (Put this in a POST method of your authentication controller or something)
The code below will take a username say "bob" or "localhost\bob" or "DOMAIN\bob" etc., and get the right PrincipalContext for authenticating the user. NOTE: it's case insensitive here.
public bool ValidateCredentials(string username, System.Security.SecureString password)
{
string domain = Environment.MachineName;
if (username.Contains("\\"))
{
domain = username.Split('\\')[0];
username = username.Split('\\')[1];
}
if (domain.Equals("localhost", StringComparison.CurrentCultureIgnoreCase))
domain = Environment.MachineName;
if (domain.Equals(Environment.MachineName, StringComparison.CurrentCultureIgnoreCase))
using (PrincipalContext context = new PrincipalContext(ContextType.Machine))
{
return context.ValidateCredentials(username, password.ToUnsecureString());
}
else
using(PrincipalContext context = new PrincipalContext(ContextType.Domain))
{
//return context.ValidateCredentials(domain + "\\" + username, password.ToUnsecureString());
return context.ValidateCredentials(username, password.ToUnsecureString());
}
}
Microsoft provides the Live Connect SDK for integration Microsoft services into your applications, including the Microsoft Accounts identity provider.
There is a specific example on Server-Side Scenarios which should cover all you need to get integrated.
Do you mean from an active directory windows account? If so you could use windows authentication and just have the index page sign them in automatically.
http://msdn.microsoft.com/en-us/library/ff647405.aspx
Use the following commands in your code behind file to get the relevant information for signing in:
System.Security.Principal.WindowsIdentity.GetCurrent().Name
User.Identity.IsAuthenticated
User.Identity.AuthenticationType
User.Identity.Name
The amount of changes / rebranding / deprecation / dead links from Microsoft drives me crazy. In any case, the latest version of this from what I've found is "Microsoft Account external login", which can be first set up on the Microsoft Developer Portal.
I found a guide that explains how to do this for .Net Core at https://learn.microsoft.com/en-us/aspnet/core/security/authentication/social/microsoft-logins, though the first half (e.g. setting the Redirect URI) isn't framework-specific.
I also found some relevant source code for .Net Core at https://github.com/aspnet/Security/blob/master/src/Microsoft.AspNetCore.Authentication.MicrosoftAccount/MicrosoftAccountOptions.cs, which shows some of the Claims (user details) that are retrieved:
ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "id");
ClaimActions.MapJsonKey(ClaimTypes.Name, "displayName");
ClaimActions.MapJsonKey(ClaimTypes.GivenName, "givenName");
ClaimActions.MapJsonKey(ClaimTypes.Surname, "surname");
ClaimActions.MapCustomJson(ClaimTypes.Email,
user => user.Value<string>("mail") ?? user.Value<string>("userPrincipalName"));
The support from the latest version of .Net Core suggests to me that this external login API still works. I haven't tested them out yet, I will update if I get to do this login integration.
Simply use "Live Connect" via Oauth 2.0:
http://msdn.microsoft.com/en-us/library/live/hh243647.aspx
or
https://dev.onedrive.com/
I've got a code signing certificate from Thawte that I'm using to dynamically generate and sign ClickOnce deployment manifests. The problem is that the ASP.NET application that generates and signs these manifests works fine when we restart IIS or re-import the signing certificate, but over time the following function fails to find the certificate:
private static X509Certificate2 GetSigningCertificate(string thumbprint)
{
X509Store x509Store = new X509Store(StoreLocation.CurrentUser);
try
{
x509Store.Open(OpenFlags.ReadOnly);
X509Certificate2Collection x509Certificate2Collection = x509Store.Certificates.Find(
X509FindType.FindByThumbprint, thumbprint, false);
if (x509Certificate2Collection.Count == 0)
throw new ApplicationException(
"SigningThumbprint returned 0 results. Does the code signing certificate exist in the personal store?",
null);
if (x509Certificate2Collection.Count > 1)
throw new ApplicationException(
"SigningThumbprint returned more than 1 result. This isn't possible", null);
var retval = x509Certificate2Collection[0];
if(retval.PrivateKey.GetType() != typeof(RSACryptoServiceProvider))
throw new ApplicationException("Only RSA certificates are allowed for code signing");
return retval;
}
finally
{
x509Store.Close();
}
}
Eventually the application starts throwing errors that it can't find the certificate. I'm stumped because I think the cert is installed correctly (or mostly correct) because it does find the cert when we start the ASP.NET application, but at some point we hit the Count==0 branch and it's just not true: the cert is in the application pool user's "Current User\Personal" cert store.
Question: Why might a cert all of a sudden "disappear" or not be able to be found?
Figured it out on our own (painfully).
The certificate needed to be installed in the LocalMachine store and the application pool account read permissions to the cert using WinHttpCertCfg or CACLS.exe if it's going to be used from an ASP.NET application. Using the CurrentUser store of the account running the application pool was causing the problem. I'm guessing there's some sort of race condition or something that's not entirely cool about accessing the CurrentUser store from a user that isn't running in a interactive logon session.
We were unable to do this at first because we were invoking the MAGE tool to do the ClickOnce deployment manifest creation/signing, and that requires the code signing cert to be in the CurrentUser\My store. However, we've eliminated the need for MAGE by a) creating the manifest from a template file and replacing the values we need to substitute out and b) by signing the manifest by calling the code MAGE calls via reflection that exists in the BuildTasks.v3.5 DLL. As a result we have more control over what cert we use to sign the manifest and can put it wherever we want. Otherwise we'd be stuck had we not gone a little "lower level".
We had the same issue, and it turns out the application pool needed to be set to load the the user profile of the (domain) account that we use to run the application.
If you notice that accessing the My certificate store works while you're signed in with the application pool's user, but not if you were not signed in while the apppool spun up, then this might be the cause for you, too.
Our setup includes a WCF service and a number of clients written by us. Some of the clients include Silverlight applications, whereas others include Web and Windows applications.
I (think) I would like to authenticate clients based on X.509 certificates. Typically you would install a private key on the client to encrypt (aka digitaly sign) the messages. The server can the use the clients public key to de-crypt it to ensure the message has not been changed and prove the message is from who we expect (aka authenticated).
I dont want to install a certificate on a client machine. Its a hassel to deploy, and we cant really ask our clients to do it. I was speaking to someone the other day who sugested embeding the cert in a client assembly, reading it and using that. Is that possible?
It would be great if someone could point me to an example.
Thanks in advance,
David
Yes, you can load X509certificate2 by passing a certificate byte array with a password like
var certificate = new X509Certificate2(theByteArrary, "password");
To get the certificate byte array, you can simply copy paste the contents in .pfx file, which is a combination of .cer (public key) and .pvk (private key)
and then you can load this certificate on your client by doing:
var channelFactory = new ChannelFactory<IYourService>();
channelFactory.Credentials.ClientCertificate.Certificate =
clientCertificate;
If you use auto-generated client proxy, or you prefer configure the certificate via .config file then you might want to have a look at this from codeproject
Here is a suggestion. Could also be tweaked to use an embedded certificate.
http://www.codeproject.com/KB/WCF/wcfcertificates.aspx