I have an asp.net application and I need to authenticate users using X509 certificates. That is, the user must install a certificate issued by me so that he can browse my website and I can identify which user is, by this certificate.
I have already configured SSL on IIS, but it's not what I'm looking for right now, and I don't know where to start.
How can I achieve this in asp.net c#?
To create a secure authentication mechanism you would use both client certificates and username / password. The reason is that a certificate is something that can be stolen (copied) but a password is something that is only known by the person. An alternative could be a certificate on a smartcard, protected by a PIN.
To use client certificates in ASP.NET applications you need to do the following:
Step 1: In IIS Manager, open your application or web site, choose SSL Settings and choose both Require SSL and Require Client certificate.
Now when the user opens your web site, the browser will prompt him to select a client certificate that will be used in the communication.
Important At this point you have to make sure that the certificate is issued by someone you trust (since anyone can create their own self-signed certificates).
Step 2: Add a configuration item (either web.config, database etc.). In this list you would add the thumbprints of the whole CA (certificate authority) chain for your client certificates.
<add key="ClientCertificateIssuerThumbprints" value="4901f5b87d736cd88792bd5ef7caee91bf7d1a2b,0113e31aa85d7fb02740a1257f8bfa534fb8549e,c9321de6b5a82666cf6971a18a56f2d3a8675602"/>
Step 3: Create a classic username / password login page. Verify the username/password.
Step 4: Add the following code to your login page:
var x509 = new X509Certificate2(this.Request.ClientCertificate.Certificate);
var chain = new X509Chain(true);
chain.ChainPolicy.RevocationMode = X509RevocationMode.Offline;
chain.Build(x509);
var validThumbprints = new HashSet<string>(
System.Configuration.ConfigurationManager.AppSettings["ClientCertificateIssuerThumbprints"]
.Replace(" ", "").Split(',', ';'),
StringComparer.OrdinalIgnoreCase);
// if the certificate is self-signed, verify itself.
for (int i = chain.ChainElements.Count > 1 ? 1 : 0; i < chain.ChainElements.Count; i++)
{
if (!validThumbprints.Contains(chain.ChainElements[i].Certificate.Thumbprint))
throw new UnauthorizedAccessException("The client certificate selected is not authorized for this system. Please restart the browser and pick the certificate issued by XXXXX");
}
// certificate Subject would contain some identifier of the user (an ID number, SIN number or anything else unique). here it is assumed that it contains the login name and nothing else
if (!string.Equals("CN=" + login, x509.Subject, StringComparison.OrdinalIgnoreCase))
throw new UnauthorizedAccessException("The client certificate selected is authorized for another user. Please restart the browser and pick another certificate.");
Only when both the password and the certificate have been checked, the user should be allowed in the system.
Assuming you have IIS 7.0 or higher, you can configure Client Certificate Mapping Authentication
Using Active Directory (Extremely easy, leaves the mapping work to the AD server)
<location path="Default Web Site">
<system.webServer>
<security>
<access sslFlags="Ssl, SslNegotiateCert" />
<authentication>
<windowsAuthentication enabled="false" />
<anonymousAuthentication enabled="false" />
<digestAuthentication enabled="false" />
<basicAuthentication enabled="false" />
<clientCertificateMappingAuthentication enabled="true" />
</authentication>
</security>
</system.webServer>
</location>
Or using IIS (More configuration needed in IIS, needs access to the client certificate, but works standalone, no roundtrips to the AD). In this case, you specify (one or more) user credentials and
map each user to a certificate's public key to a user whose credentials you specify, or
map multiple certificates to a user based on values in the certificate's fields
Configuration (many to one):
<location path="Default Web Site">
<system.webServer>
<security>
<authentication>
<windowsAuthentication enabled="false" />
<anonymousAuthentication enabled="false" />
<digestAuthentication enabled="false" />
<basicAuthentication enabled="false" />
<iisClientCertificateMappingAuthentication enabled="true"
manyToOneCertificateMappingsEnabled="true">
<manyToOneMappings>
<add name="Contoso Employees"
enabled="true"
permissionMode="Allow"
userName="Username"
password="[enc:AesProvider:57686f6120447564652c2049495320526f636b73:enc]">
<rules>
<add certificateField="Subject"
certificateSubField="O"
matchCriteria="Contoso"
compareCaseSensitive="true" />
</rules>
</add>
</manyToOneMappings>
</iisClientCertificateMappingAuthentication>
</authentication>
<access sslFlags="Ssl, SslNegotiateCert" />
</security>
</system.webServer>
</location>
(Sample configuration rather shamelessly copied from the samples on the iis.net documentation pages, which are quite elaborate.)
Or you can configure your application to use Claims-Based Authentication with a Security Token Service (STS) that authenticates clients based on client certificates. ADFS 2.0 can fullfil this role, or if it is not available, you could look at the Thinktecture Identity Server.
Related
I deployed my ASP.net Website on a server using IIS. The problem is that it denies Access to anyone accessing the website on the same domain. What i do is once a user tries to Access the website on the server i read the NT login for the user and match it to NT logins present in my Db and give the person Access if he his/her NT login is present in the DB. Now After Debugging i checked the variable used which stores the User NT login before checking it with the DB, and no matter who access the website the Login passed is the one for the server on which the website is deployed.
Webcofig:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.web>
<identity impersonate="true" />
</system.web>
<system.webServer>
<defaultDocument>
<files>
<add value="User_login_Portal.aspx" />
</files>
</defaultDocument>
</system.webServer>
<connectionStrings>
<add name="myConnectionString"
connectionString="Server=Server;Database=Backend_UM;User
ID=sa;Password=Password;Trusted_Connection=False;"
providerName="System.Data.SqlClient" />
</connectionStrings>
</configuration>
ASPCode:
string x = Environment.UserName;
string y = Page.User.Identity.Name;
string strName = HttpContext.Current.User.Identity.Name.ToString();
WindowsIdentity currentIdentity = WindowsIdentity.GetCurrent();
string NTlogin = currentIdentity.Name.ToString();
string[] login_name = NTlogin.Split('\\');
login = loginid.Text = login_name[1];
Authentications:
Windows Authentication: Enabled
ASP.net impersonation : Enabled
Forum Authentication: Disabled
Anonymous Authentication : Disabled
Identity: ApplicationPoolIdentity
The Login Variable in the Asp code is suppose to be the NT login Username of the Person trying to Access it, but whoever tries to Access the Website the Login Variable is the Username of the Server On which the portal is deployed.
The Issue was resolved. While deploying the website i needed to check the windows authentication check box. Since i was not doing that before the website would only take the user name of the server on which it was deployed.
I have an application pool that I use for development… and I have it running under my credentials (so I don't have to worry about permission/access issues). Two things make me think my credentials might be just sitting in a file (or registry entry)… which is worrisome:
When I change my password, I have to update the stored credentials
The setup dialog has a confirm password field
If IIS was just storing some authentication token or something, I would expect to only enter my password once (because authentication was happening immediately).
Anyone know where my credentials are being stored? Are they just encrypted using some system key then pulled out and used when the app pool spins up?
Here is the dialog where I'm entering the identity's credentials:
I open that dialog from the app pool's Advanced Settings:
Other Info
IIS 7.5 on Windows 7
I am using virtual accounts for other application pools, but that's not what I'm using here: I'm using actual Windows account credentials
UPDATE
Based on nicolas-dietrich's response, I found the following…
The application pool credentials (and general settings) for IIS 7.5 are stored in %systemroot%\System32\Inetsrv\config\applicationHost.config.
Encryption is handled by AesProtectedConfigurationProvider, which is the standard (?) way to protect sensitive config info (like db connection strings or–you know–passwords)
Here are the relevant sections with sensitive/irrelevant info replaced by ellipses (…):
<configProtectedData>
<providers>
<!-- … -->
<add name="IISWASOnlyAesProvider" type="Microsoft.ApplicationHost.AesProtectedConfigurationProvider" description="Uses an AES session key to encrypt and decrypt" keyContainerName="iisWasKey" cspProviderName="" useOAEP="false" useMachineContainer="true" sessionKey="…" />
</providers>
</configProtectedData>
<system.applicationHost>
<applicationPools>
<add name="DefaultAppPool" queueLength="5000" managedRuntimeVersion="v4.0" />
<add name="GeneralDev" queueLength="5000" autoStart="true">
<processModel identityType="SpecificUser" userName="mydomain\myusername" password="[enc:IISWASOnlyAesProvider:…:enc]" />
</add>
<!-- … -->
<applicationPoolDefaults managedRuntimeVersion="v4.0">
<processModel identityType="ApplicationPoolIdentity" loadUserProfile="true" setProfileEnvironment="false" />
</applicationPoolDefaults>
</applicationPools>
<!-- … -->
</system.applicationHost>
Hopefully, safe enough? ¯\_(ツ)_/¯
In IIS6 the AppPool identities were stored within the IIS metabase (%systemroot%\System32\Inetsrv\metabase.xml) in an encrypted string located under W3SVC/AppPools//WAMUserPass.
That was not so secured though as it was possible to decrypt and to show it as plain text (http://www.jasonsamuel.com/2010/04/28/how-to-get-the-iusr-and-iwam-user-account-passwords-on-an-iis-server/)
I am making a .Net Web API that gets data by calling an SQL server. The user is authenticated via Windows Authentication (Kerberos). I would like the user credentials to be passed to the SQL server via delegation, but the SQL server sees an anonymous user.
This is what I have done:
IIS application:
Windows Authentication and asp.net impersonation enabled. Anonymous and forms authentication disabled.
Enable kernel mode authentication is checked.
Providers: Negotiate, Kerberos.
Use app pool credentials: True.
Application pool:
Managed pipeline mode: Classic.
Identity: Network service.
In AD, the computer the web server runs on is set to "Trust this computer for delegation to any specific service (Kerberos only)"
The connection string to the SQL server contains Integrated Security=SSPI;
Edit: In my web.config I have
<system.web>
<authentication mode="Windows" />
<identity impersonate="true" />
</system.web>
and
<security>
<authentication>
<windowsAuthentication enabled="true">
<providers>
<clear />
<add value="Negotiate" />
<add value="Kerberos" />
</providers>
<extendedProtection tokenChecking="None" />
</windowsAuthentication>
<anonymousAuthentication enabled="false" />
</authentication>
</security>
The generic HOST spn is set for the machine.
From the browser I access the web application via http://machinename.domain.net.
I would expect in this setup that my IIS application is run under the machine account?
When I catch a request in the debugger on the web server, I can see that WindowsIdentity.GetCurrent().Name is the account of the user browsing the web application and WindowsIdentity.GetCurrent().AuthenticationType is set to "Kerberos". So that should be good.
However WindowsIdentity.GetCurrent().ImpersonationLevel is only set to "Impersonate". I would have expected it to be set to "Delegate"?
When I make a request to the SQL server, I get "Login failed for user 'NT AUTHORITY\ANONYMOUS LOGON'" so obviously the user credentials are not passed to the SQL server.
I hope someone can see what I am doing wrong. I really need a push in the right direction.
For future reference if someone runs into the same issue:
The issue was that we tried from Chrome. It works in IE, but on Chrome the registry change mentioned in this post was needed: Kerberos delegation doesn't work in chrome
You should be able to set the Authentication to ASP.NET Impersonation within IIS. You will probably be required to set the following in your web.config file too, as part of < system.web> section.
<identity impersonate="true" />
This may be required in the < system.webServer> section to, although not always recommended due to security concerns.
<validation validateIntegratedModeConfiguration="false" />
I have a hub
Its called by 2 types of clients, browser with identity cookies and c# client with client certs
I can make eitheer work. BUt If I have iis configued to require client certs on /signalr then the browser client connects the user gets prompted for a cert - not good.
I tried
var str = String.Format("https://{0}/cert_signalr", url.Host);
m_hub = new HubConnection(str, false);
setting iis to require certs on /cert_signalr and adding a rewrite rule of cert_signalr -> signalr. Didnt work. Seems like the HubConnection paid no attention to me specifying the URL
my code works - what I did wrong was in web config. I needed to have
<location path="cert_signalr">
<system.webServer>
<security>
<access sslFlags="SslNegotiateCert" />
</security>
</system.webServer>
</location>
I have a Windows Azure WebAPI application with
OWIN
Oauth 2
token authentication
Identity 2 Framework
VS2013 Update 2
IIS (I am not sure which version. As I just updated everything in the stack then I assume the latest version)
Before a user is authenticated when there's a <script> in my index.html file I notice cookies sent to the server looking like this:
Accept-Language: en-US,en;q=0.8
Cookie: .AspNet.Cookies=GWLL4LgeFkn7jDndAwf-Pk_eZAPZ5LYZugSmv- ...
After a user is authenticated I notice the cookies change:
Accept-Language: en-US,en;q=0.8
Cookie: .AspNet.Cookies=OqLMSpIv2aQ8KUcw3pWdAYtPYUI_tYMl4rEYKe16N ...
I thought I was using token authentication so my first question is "why do the cookies get changed and why are they sent at all"?
Once a user is authenticated then with each $http request to the server I send a header like this:
Authorization: Bearer abcdefgetc....
My authorization on the server works when I have WebAPI methods decorated like:
[Authorize(Roles = "Admin")]
Here is the main web-config that shows the security settings:
<system.web>
<authentication mode="None" />
<compilation debug="true" targetFramework="4.5.1" />
<httpRuntime targetFramework="4.5.1" />
</system.web>
<system.webServer>
<modules>
<remove name="FormsAuthenticationModule" />
</modules>
</system.webServer>
Now I would like to add some security to some static javascript files that I have on the server. I know how I can code it so the files can be retrieved by my client and added to the DOM in two ways. Either way is okay for me to use although I prefer the first way if when I do that way then there can be authentication happen through cookies or otherwise:
With a script tag and a load
var el = doc.createElement("script"),
loaded = false;
el.onload = el.onreadystatechange = function () {
if ((el.readyState && el.readyState !== "complete" && el.readyState !== "loaded") || loaded) {
return false;
}
el.onload = el.onreadystatechange = null;
loaded = true;
// done!
};
el.async = true;
el.src = path;
document.getElementsByTagName('head')[0].insertBefore(el, head.firstChild);
With a $http call and then adding it directly to the DOM (I can supply bearer token)
$http({
url: '/Bundles/admin/admin1Bundle.js',
method: "GET"
})
.success(function (result) {
var m = document.createElement('script');
m.appendChild(document.createTextNode(result));
document.getElementsByTagName('head')[0].appendChild(m);
Once added the javascript becomes available. To add security I created a web.config in the folder to protect these files and allow only users with Admin role to have access:
Here is the folders web-config that shows the security settings:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<security>
<authorization>
<remove users="*" roles="" verbs="" />
<add accessType="Allow" roles="Admin" verbs='GET'/>
</authorization>
</security>
</system.webServer>
</configuration>
When a user with the role of Admin tries to access files in the folder with a GET that a has a bearer token they get a message saying:
Most likely causes:
•No authentication protocol (including anonymous) is selected in IIS.
•Only integrated authentication is enabled, and a client browser was used that does not support integrated authentication.
•Integrated authentication is enabled and the request was sent through a proxy that changed the authentication headers before they reach the Web server.
•The Web server is not configured for anonymous access and a required authorization header was not received.
•The "configuration/system.webServer/authorization" configuration section may be explicitly denying the user access.
This error occurs when the WWW-Authenticate header sent to the Web server is not supported by the
server configuration. Check the authentication method for the resource, and verify which authentication
method the client used. The error occurs when the authentication methods are different. To determine
which type of authentication the client is using, check the authentication settings for the client.
It seems like my IIS (the version I am using on my development environment when I click Debug > Start Debugging) is using a different kind of authentication from that used by my WebAPI. Can someone explain to me:
Should I be using <system.web> or <system.webServer> for the security?
How can I make the IIS use the same security path as WebAPI is using when I decorate my WebAPI methods? Note that I need a way to do this with web.config as I don't have access to make changes to the IIS directly once the application is published to the cloud.
I am using token authentication so why is the cookie information sent? Could I just use this cookie information to secure my javascript files from getting downloaded?
Notes:
Here is the way I have authentication set up in Startup.Auth.cs
// Configure the application for OAuth based flow
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true
};
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
Should I be using or for the security?
How can I make the IIS use the same security path as WebAPI is using
when I decorate my WebAPI methods? Note that I need a way to do this
with web.config as I don't have access to make changes to the IIS
directly once the application is published to the cloud.
You could use the authorization attribute in the webconfig to restrict files and folders, the example below restricts restricts a specific js file to admins only.
<location path="resources/scripts/yourtopsecretjsfile.js">
<system.webServer>
<security>
<authorization>
<remove users="*" roles="" verbs="" />
<add accessType="Allow" roles="Administrators" />
</authorization>
</security>
</system.webServer>
</location>
I am using token authentication so why is the cookie information sent?
It is used by the server to identify the authenticated user. If you don't want to do without sending the cookie info you can look at doing at sending a signed token with every request instead. Check out this article, loosely covers how to do that (but that with an angular JS/ Web API 2 point of view) http://bitoftech.net/2014/06/01/token-based-authentication-asp-net-web-api-2-owin-asp-net-identity/