"Unable to validate data" using FormsAuthentication between different web apps - forms-authentication

I have two .NET web applications running on the same server - sentinel (hosted at https://sentinel.mydomain.com/) and fortknox (at http://www.mydomain.com/fortknox)
Sentinel is an authentication 'portal'. FortKnox is a 'proof of concept' app that uses forms authentication but has the loginUrl set to https://sentinel.mydomain.com/login (along with a special Application_EndRequest handler to qualify the ReturnUrl). Sentinel is written in .NET 4.0 using MVC 4 and Razor; FortKnox is ASP.NET MVC 2 using .NET 2.0.
I'm using ASP.NET FormsAuthentication with the cookie domain set to .mydomain.com so that cookies set by sentinel.mydomain.com will be sent with requests to www.mydomain.com, and vice versa. The cookie part is working perfectly - both applications are getting the same .ASPXAUTH encrypted forms ticket.
The problem is that, on our production servers, fortknox can't decrypt cookies created by sentinel - even though they have identical machine keys. Even when both apps are running on the same physical box, it doesn't work.
A user hits fortknox, they're redirected to sentinel, they log in, the cookie is set, they're redirected back to fortknox, and then I get "Unable to validate data":
Exception: Unable to validate data.
at System.Web.Configuration.MachineKeySection.EncryptOrDecryptData(Boolean fEncrypt, Byte[] buf, Byte[] modifier, Int32 start, Int32 length, IVType ivType, Boolean useValidationSymAlgo, Boolean signData)
at System.Web.Configuration.MachineKeySection.EncryptOrDecryptData(Boolean fEncrypt, Byte[] buf, Byte[] modifier, Int32 start, Int32 length, IVType ivType, Boolean useValidationSymAlgo)
at System.Web.Security.FormsAuthentication.Decrypt(String encryptedTicket)
at FortKnox.Web.MvcApplication.Application_BeginRequest()
The machine keys are identical - I've gone as far as including this chunk of (nasty!) code in the mark-up of each page:
try {
var cookie = Request.Cookies[".ASPXAUTH"].Value;
Response.Write("Cookie: " + cookie + Environment.NewLine);
var ticket = FormsAuthentication.Decrypt(cookie);
Response.Write("Ticket name: " + ticket.Name + Environment.NewLine);
} catch (Exception x) {
Response.Write("Exception: " + x.Message + Environment.NewLine);
Response.Write(x.StackTrace);
}
Response.Write("<hr /></pre>");
var machineConfigMachineKey = (MachineKeySection)WebConfigurationManager.OpenMachineConfiguration().SectionGroups["system.web"].Sections["machineKey"];
var webConfigMachineKey = (MachineKeySection)WebConfigurationManager.OpenWebConfiguration("").SectionGroups["system.web"].Sections["machineKey"];
Response.Write("<pre>");
Response.Write("<b>machine.config decrypt: </b>" + machineConfigMachineKey.DecryptionKey + "<br />");
Response.Write("<b>web.config decrypt: </b>" + webConfigMachineKey.DecryptionKey + "<br />");
Response.Write("<br />");
Response.Write("<b>machine.config validate: </b>" + machineConfigMachineKey.ValidationKey + "<br />");
Response.Write("<b>web.config validate: </b>" + webConfigMachineKey.ValidationKey + "<br />");
Response.Write("</pre>");
Response.Write("<hr />");
and verified that the machine keys being used at runtime are exactly the same.
What's especially frustrating is that this has been working on our development and staging servers, and has only failed in production. The only difference between the servers is that the production boxes have Windows Updates installed frequently whilst our dev/staging boxes are potentially missing some updates; they're otherwise identical (cloned from the same image and created using the same setup scripts)
So... same server; same machine key. ASP.NET 4 sets a FormsAuthentication cookie. ASP.NET 2 app can't decrypt it. Bug only happening on certain servers; on others, it's working. At this point, I'm completely stuck... any ideas?
EDIT: Live server has been brought right up to the latest patch level. I have tried applying
<add key="aspnet:UseLegacyEncryption" value="true" />
as both true AND false, to both the login app and the fortknox app. Still no luck...

Any chance it has something to do with the old 2010 padding oracle security patch - http://weblogs.asp.net/scottgu/archive/2010/09/28/asp-net-security-update-now-available.aspx? Try setting
<add key="aspnet:UseLegacyEncryption" value="true" />
to force the patched servers to act like they used to before the patch?
(or, you know... patch your servers. Your choice.)

Did you try all the following keys? http://support.microsoft.com/kb/2425938
<add key="aspnet:UseLegacyEncryption" value="true" />
<add key="aspnet:UseLegacyMachineKeyEncryption" value="true" />
<add key="aspnet:ScriptResourceAllowNonJsFiles" value="true" />

Related

Cannot disable ASP.NET cookieless mode

I'm trying to find out why UrlHelper.RouteUrl returns me cookieless URLs that start with /(F(. This only seems to happen for Bing requests (Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)).
I already disabled cookieless mode 3 times:
<authentication mode="None">
<forms cookieless="UseCookies" />
</authentication>
<anonymousIdentification enabled="true" cookieless="UseCookies" />
<sessionState cookieless="UseCookies" />
I also added the following assertion:
if (url.StartsWith("/(F(", StringComparison.Ordinal))
throw new Exception(
FormsAuthentication.CookieMode + " " +
FormsAuthentication.CookiesSupported + " " +
HttpContext.Current.Request.Browser.Cookies);
This throws in case of bing bot. But it claims that CookieMode == UseCookies && CookiesSupported == true && Browser.Cookies == true. This means that the config setting took, as well as that ASP.NET thinks that Bing bot does support cookies. There should be no reason whatsoever to prepend this cookieless string to the URL.
I cannot reproduce it locally on Windows 7 .NET 4.7. The production server runs Server 2008 R2 with .NET 4.7.
I tried really hard disabling this nasty feature. How can I escape this madness?
Update: The F seems to mean that the forms authentication feature is responsible. But clearly it is disabled in the web.config?! I'm not using it in any way as far as I know (might be a wrong assumption).
Also, I tested the "app path modifier" value which is being used by MVC:
var x =
(string)typeof(HttpResponse)
.GetField("_appPathModifier", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy)
.GetValue(HttpContext.Current.Response);
I added this value to the assert and indeed the nasty /(F( string is present here. I have no idea how it comes to be that the .NET Framework sets this value.
Because before that, the value of SessionId has been removed (CookielessHelper.RemoveCookielessValuesFromPath) from url path.
HttpContext's Init method process that.
private void Init(HttpRequest request, HttpResponse response)
{
this._request = request;
this._response = response;
this._utcTimestamp = DateTime.UtcNow;
this._principalContainer = this;
if (this._wr is IIS7WorkerRequest)
{
this._isIntegratedPipeline = true;
}
if (!(this._wr is StateHttpWorkerRequest))
{
this.CookielessHelper.RemoveCookielessValuesFromPath();
}
// other codes ...
}
Please Ref Possible Bug With ASP.NET MVC 3 Routing?
I will check Response._appPathModifier value at Application_BeginRequest event,
protected void Application_BeginRequest(object sender, EventArgs e)
{
//到這時,Url已被改成沒包含SessionId
//但它的值會放在 Response._appPathModifier 變數之中
var appPathModifierFieldInfo = Context.Response.GetType().GetField("_appPathModifier",
BindingFlags.NonPublic | BindingFlags.Instance);
var appPathModifier = appPathModifierFieldInfo.GetValue(Context.Response);
if (appPathModifier != null)
{
//url 中有 SessionId
throw new HttpException(404, "Not found");
}
}

smtp connection works in telnet but not in ASP -- same server, same credentials

I'm trying to send email from an ASP.NET using my SendGrid account. It works on my dev machine, but not in production, even though the credentials are the same. Likewise, in production I can connect to the SMTP server via telnet (using base64 encoded credentials), but the ASP site can't connect--I get error "Unauthenticated senders not allowed."
I've tried a mix of port numbers (25, 587, 465 -- my site is SSL). Using port 465 times out. 25 and 587 return respond immediately--but with the login error. This is really baffling because, like I say, it's the same credentials on dev machine and production.
I looked very briefly at Microsoft Network Monitor 3.4, but could not make heads or tails of it. I was hoping it would tell me the blow-by-blow commands being sent since I suspect the web site is doing something a little different from how telnet connects, but I don't know what.
Note I also asked my web host if outgoing traffic on these ports were blocked on production firewall, but they aren't.
Here's the actual code--like I say works fine on localhost, but SMTP connection fails in production
[AllowAnonymous]
[HttpPost]
public ActionResult ResetPasswordSend(string email)
{
List<string> userList = new List<string>();
try
{
string[] invalidChars = new string[] { ";", "," };
foreach (var invalidChar in invalidChars) if (email.Contains(invalidChar)) throw new Exception("Email contains invalid character.");
int count = 0;
// since emails are not unique, I must launch resets for all of them
var users = _db.HsProfile.Query("[Email]=#0", SqlDb.Params(email));
foreach (var profile in users)
{
count++;
userList.Add(profile.UserName);
var token = WebSecurity.GeneratePasswordResetToken(profile.UserName, 15);
WebMail.Send(profile.Email, "HumaneSolution.com Password Reset for user " + profile.UserName,
"You received this email because you or someone with your email address requested a password reset on HumaneSolution.com. " +
"If you didn't do this, then you don't need to take any action, and nothing will happen.\n\n" +
"To proceed with the password reset, click the link below within 15 minutes:\n\n" +
Url.BaseUrl("Account/EnterNewPassword/" + token) + "\n\n" +
"Sent to: " + email + " at " + DateTime.Now.ToLongDateString() + " " + DateTime.Now.ToLongTimeString() + "\n" +
"User name: " + profile.UserName);
}
if (count == 0) throw new Exception("Email " + email + " is not registered at HumaneSolution.com.");
}
catch (Exception exc)
{
ViewBag.Error = exc.Message;
}
return View(userList);
}
Based on suggestion from SendGrid, I re-wrote the email code so it does not use WebMail.Send but rather the SmtpClient and MailMessage objects explicitly. SendGrid says there might be some kind of timing problem in how ASP.NET loads credentials from the config file automatically. Here's exactly what they said:
Are you by chance storing your SendGrid credentials in a configuration file, separate from the code that connects to our SMTP server? The reason I ask is because I have seen rails and C# configurations like this receive the unauthenticated error due to the credentials not being passed at the correct time. Usually this is solved by moving the credentials directly in with the code instead of a separate configuration file. Give that a try and see if you notice a difference.<<
I didn't follow their advice completely -- i.e. I'm still using config file, but I'm loading the config values in subclasses of SmtpClient and MailMessage so I avoid hardcoding creds in my app. Anyway, it worked, all is well again.

ASHX sends HttpWebRequest with Impersonation On, which works to URLs on same server but not on Remote ArcGIS server

This is a complicated issue, so bear with me.
Scenario: using a ASHX proxy to relay request to an ArcGIS server.
Trying to use ASP.NET impersonation, so that the logged in ASP.NET user credentials are used by the proxy, when sending request to the ArcGIS server.
Issue: the proxy request to ArcGIS server is refused 401, even though I know the impersonated account (sean.ryan-B + sean.ryan) does have access.
There are 4 machines:
1. machine hosting proxy page. I am logged in as: sean.ryan-B
2. a test machine. I am logged in as sean.ryan-B
3. my laptop. I am logged in as sean.ryan
4. the arcgis server.
All 4 machines are on the same domain.
web.config:
<authentication mode="Windows"/>
<identity impersonate="true" /> <!-- userName="EUROPE\sean.ryan-B" password="xxx" -->
<authorization>
<deny users="?"/>
</authorization>
Test-1. Opening a test page, in same web app as proxy, via the proxy:
http://myHost.com/sean/ProxyAsp.Net/ArcGisProxy.ashx?http://myHost.com/sean/ProxyAsp.Net
[ok on all boxes 1-3]
This looks OK - the impersonation seems look OK,
since with impersonation OFF: WindowsIdentity.GetCurrent().Name = the AppPool account
with impersonation ON: WindowsIdentity.GetCurrent().Name = EUROPE\sean.ryan or EUROPE\sean.ryan-B
Test-2. opening an image that is hosted on the same IIS (but a different site), via the proxy:
http://myHost.com/sean/ProxyAsp.Net/ArcGisProxy.ashx?http://myHost.com:10400/sites/CaSPER/SiteAssets/CaSPER.jpg
[ok on boxes 1-3]
Test-3. opening the ArcGIS map URL, via the proxy:
http://myHost.com/sean/ProxyAsp.Net/ArcGisProxy.ashx?http://mapserver1.com/ArcGIS/rest/services/Global/2D_BaseMap_SurfaceGeology/MapServer?f=json&callback=dojo.io.script.jsonp_dojoIoScript1._jsonpCallback
[fails on boxes 2,3 but succeeds on the proxy host (box 1)!]
code for the ASHX code-behind:
public partial class ArcGisProxy : IHttpHandler, IReadOnlySessionState //ASHX implements IReadOnlySessionState in order to be able to read from session
{
public void ProcessRequest(HttpContext context)
{
try
{
HttpResponse response = context.Response;
// Get the URL requested by the client (take the entire querystring at once
// to handle the case of the URL itself containing querystring parameters)
string uri = context.Request.Url.Query;
uri = uri.Substring(1); //the Substring(1) is to skip the ?, in order to get the request URL.
System.Net.HttpWebRequest req = (System.Net.HttpWebRequest)WebRequest.Create(uri);
{
req.Credentials = CredentialCache.DefaultCredentials; //this works on local box, with -B account. this is the account the web browser is running under (rather than the account logged into CaSPER with, as ASHX has separate server session).
req.ImpersonationLevel = TokenImpersonationLevel.Impersonation;
}
//to turn off caching: req.CachePolicy = new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore);
req.Method = context.Request.HttpMethod;
req.ServicePoint.Expect100Continue = false;
req.Referer = context.Request.Headers["referer"];
// Set body of request for POST requests
req.Method = "GET";
// Send the request to the server
System.Net.WebResponse serverResponse = null;
try
{
serverResponse = req.GetResponse();
}
catch (System.Net.WebException webExc)
{
//logger.Log(GetMyUrl(), webExc, context.Request);
response.StatusCode = 500;
response.StatusDescription = webExc.Status.ToString();
response.Write(webExc.ToString());
response.Write(webExc.Response);
response.Write("Username = " + context.User.Identity.Name + " " + context.User.Identity.IsAuthenticated + " " + context.User.Identity.AuthenticationType);
response.End();
return;
}
// Set up the response to the client
....
......
response.End();
}
catch (Exception ex)
{
throw;
}
}
public bool IsReusable
{
get
{
return false;
}
}
}
note: the following changes, meant proxy request to the map server DOES succeed:
a) set the identity in the web.config to explicitly set username, password to the sean.ryan-B account:
-OR-
b) set the App Pool account to be sean.ryan-B and turn OFF impersonation in the web.config file.
however these changes are not acceptable for Production.
The problem seems to be that:
- ASP.NET impersonation works well enough for test page + image hosted on same IIS (tests 1 and 2)
but NOT well enough for the map server.
as far as I know, the ArcGIS map server is using Negotiate, and then Kerberos authentication.
With WireShark, I monitored a successful proxy request, and found:
after 401, proxy sends GET with AUTH using SPNEGO (Kerberos)
Has anyone had similar issue with ArcGIS proxy ?
My theory is, that the impersonation on box 1 'works better', because browser is running on same box as the proxy.
Could the ArcGIS Server (or the IIS site it is using) be restricted to prevent accepting impersonation ?
Any suggestions welcome ...
p.s. had a hard time getting this post through - had to format most of it as code, as s-o is detecting it as source code !

How to deploy asp.net web application to development team via TFS when setting up ADFS authentication

I am working on a asp.net web application that has is a part of TFS and is used by the development team. Recently as part of the project we setup ADFS and are now attempting to enforce authentication of the project to an ADFS server.
On my development machine I have gone through the steps of adding STS reference which generates the Federation Meta-Data as well as updates the web.config file for the project. Authorization within the web.config uses thumbprint certification which requires me to add to my local machine the ADFS certificate as well as generate a signed certificate for the dev machine and add this to ADFS.
All is setup and working but in looking at the web.config. and FederationMetadata.xml document these "appear" to be machine specific. I suspect that if I check the project/files into TFS the next developer or tester that takes a build will end up with a broken build on their machine.
My question is within TFS what is the process for a scenario like this to check in and still allow my team to check out, build, and test the project with the latest code in their development or test environments?
My work around at this time is to exclude the FederationMetaData.xml and web.config from check in then on each development machine manually setup ADFS authentication as well as for product test. Once done each person can prevent their local copy of the FederationMetatData.xml and web.config from being checked in.(aka have their own local copy) then when checking in/out just ensure that each developer preserves their own copy (or does not check them into TFS)
This seems extremely inefficient, and all but bypasses the essence of source code management as developers are being required to keep local copies of files on their machine. This also seems to introduce the opportunity for accidental check-in of local files or overwriting local files.
Does anyone have any references, documentation or information on how to check-in code for (ADFS) machine specific configurations and not hose up the entire development environment?
Thanks in advance,
I agree that the way that the WIF toolset does configuration is not great for working in teams with multiple developers and test environments. The approach that I've taken to get past this is to change WIF to be configured at runtime.
One approach you can take is to put a dummy /FederationMetadata/2007-06/FederationMetadata.xml in place and check that in to TFS. It must have valid urls and be otherwise a valid file.
Additionally, you will need a valid federationAuthentication section in web.config with dummy (but of valid form) audienceUris, issuer and realm entries.
<microsoft.identityModel>
<service>
<audienceUris>
<add value="https://yourwebsite.com/" />
</audienceUris>
<federatedAuthentication>
<wsFederation passiveRedirectEnabled="true" issuer="https://yourissuer/v2/wsfederation" realm="https://yourwebsite.com/" requireHttps="true" />
<cookieHandler requireSsl="false" />
</federatedAuthentication>
etc...
Then, change your application's ADFS configuration to be completely runtime driven. You can do this by hooking into various events during the ADFS module startup and ASP.NET pipeline.
Take a look at this forums post for more information.
Essentially, you'll want to have something like this in global.asax.cs. This is some code that I've used on a Windows Azure Web Role to read from ServiceConfiguration.cscfg (which is changeable at deploy/runtime in the Azure model). It could easily be adapted to read from web.config or any other configuration system of your choosing (e.g. database).
protected void Application_Start(object sender, EventArgs e)
{
FederatedAuthentication.ServiceConfigurationCreated += OnServiceConfigurationCreated;
}
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
/// Due to the way the ASP.Net pipeline works, the only way to change
/// configurations inside federatedAuthentication (which are configurations on the http modules)
/// is to catch another event, which is raised everytime a request comes in.
ConfigureWSFederation();
}
/// <summary>
/// Dynamically load WIF configuration so that it can live in ServiceConfiguration.cscfg instead of Web.config
/// </summary>
/// <param name="sender"></param>
/// <param name="eventArgs"></param>
void OnServiceConfigurationCreated(object sender, ServiceConfigurationCreatedEventArgs eventArgs)
{
try
{
ServiceConfiguration serviceConfiguration = eventArgs.ServiceConfiguration;
if (!String.IsNullOrEmpty(RoleEnvironment.GetConfigurationSettingValue("FedAuthAudienceUri")))
{
serviceConfiguration.AudienceRestriction.AllowedAudienceUris.Add(new Uri(RoleEnvironment.GetConfigurationSettingValue("FedAuthAudienceUri")));
Trace.TraceInformation("ServiceConfiguration: AllowedAudienceUris = {0}", serviceConfiguration.AudienceRestriction.AllowedAudienceUris[0]);
}
serviceConfiguration.CertificateValidationMode = X509CertificateValidationMode.None;
Trace.TraceInformation("ServiceConfiguration: CertificateValidationMode = {0}", serviceConfiguration.CertificateValidationMode);
// Now load the trusted issuers
if (serviceConfiguration.IssuerNameRegistry is ConfigurationBasedIssuerNameRegistry)
{
ConfigurationBasedIssuerNameRegistry issuerNameRegistry = serviceConfiguration.IssuerNameRegistry as ConfigurationBasedIssuerNameRegistry;
// Can have more than one. We don't.
issuerNameRegistry.AddTrustedIssuer(RoleEnvironment.GetConfigurationSettingValue("FedAuthTrustedIssuerThumbprint"), RoleEnvironment.GetConfigurationSettingValue("FedAuthTrustedIssuerName"));
Trace.TraceInformation("ServiceConfiguration: TrustedIssuer = {0} : {1}", RoleEnvironment.GetConfigurationSettingValue("FedAuthTrustedIssuerThumbprint"), RoleEnvironment.GetConfigurationSettingValue("FedAuthTrustedIssuerName"));
}
else
{
Trace.TraceInformation("Custom IssuerNameReistry type configured, ignoring internal settings");
}
// Configures WIF to use the RsaEncryptionCookieTransform if ServiceCertificateThumbprint is specified.
// This is only necessary on Windows Azure because DPAPI is not available.
ConfigureWifToUseRsaEncryption(serviceConfiguration);
}
catch (Exception exception)
{
Trace.TraceError("Unable to initialize the federated authentication configuration. {0}", exception.Message);
}
}
/// <summary>
/// Configures WIF to use the RsaEncryptionCookieTransform, DPAPI is not available on Windows Azure.
/// </summary>
/// <param name="requestContext"></param>
private void ConfigureWifToUseRsaEncryption(ServiceConfiguration serviceConfiguration)
{
String svcCertThumbprint = RoleEnvironment.GetConfigurationSettingValue("FedAuthServiceCertificateThumbprint");
if (!String.IsNullOrEmpty(svcCertThumbprint))
{
X509Store certificateStore = new X509Store(StoreName.My, StoreLocation.LocalMachine);
try
{
certificateStore.Open(OpenFlags.ReadOnly);
// We have to pass false as last parameter to find self-signed certs.
X509Certificate2Collection certs = certificateStore.Certificates.Find(X509FindType.FindByThumbprint, svcCertThumbprint, false /*validOnly*/);
if (certs.Count != 0)
{
serviceConfiguration.ServiceCertificate = certs[0];
// Use the service certificate to protect the cookies that are sent to the client.
List<CookieTransform> sessionTransforms =
new List<CookieTransform>(new CookieTransform[] { new DeflateCookieTransform(),
new RsaEncryptionCookieTransform(serviceConfiguration.ServiceCertificate)});
SessionSecurityTokenHandler sessionHandler = new SessionSecurityTokenHandler(sessionTransforms.AsReadOnly());
serviceConfiguration.SecurityTokenHandlers.AddOrReplace(sessionHandler);
Trace.TraceInformation("ConfigureWifToUseRsaEncryption: Using RsaEncryptionCookieTransform for cookieTransform");
}
else
{
Trace.TraceError("Could not find service certificate in the My store on LocalMachine");
}
}
finally
{
certificateStore.Close();
}
}
}
private static void ConfigureWSFederation()
{
// Load the federatedAuthentication settings
WSFederationAuthenticationModule federatedModule = FederatedAuthentication.WSFederationAuthenticationModule as WSFederationAuthenticationModule;
if (federatedModule != null)
{
federatedModule.PassiveRedirectEnabled = true;
if (!String.IsNullOrEmpty(RoleEnvironment.GetConfigurationSettingValue("FedAuthWSFederationRequireHttps")))
{
federatedModule.RequireHttps = bool.Parse(RoleEnvironment.GetConfigurationSettingValue("FedAuthWSFederationRequireHttps"));
}
if (!String.IsNullOrEmpty(RoleEnvironment.GetConfigurationSettingValue("FedAuthWSFederationIssuer")))
{
federatedModule.Issuer = RoleEnvironment.GetConfigurationSettingValue("FedAuthWSFederationIssuer");
}
if (!String.IsNullOrEmpty(RoleEnvironment.GetConfigurationSettingValue("FedAuthWSFederationRealm")))
{
federatedModule.Realm = RoleEnvironment.GetConfigurationSettingValue("FedAuthWSFederationRealm");
}
CookieHandler cookieHandler = FederatedAuthentication.SessionAuthenticationModule.CookieHandler;
cookieHandler.RequireSsl = false;
}
else
{
Trace.TraceError("Unable to configure the federated module. The modules weren't loaded.");
}
}
}
This will then allow you to configure the following settings at runtime:
<Setting name="FedAuthAudienceUri" value="-- update with audience url. e.g. https://yourwebsite/ --" />
<Setting name="FedAuthWSFederationIssuer" value="-- update with WSFederation endpoint. e.g. https://yourissuer/v2/wsfederation--" />
<Setting name="FedAuthWSFederationRealm" value="-- update with WSFederation realm. e.g. https://yourwebsite/" />
<Setting name="FedAuthTrustedIssuerThumbprint" value="-- update with certificate thumbprint from ACS configuration. e.g. cb27dd190485afe0f62e470e4e3578de51d52bf4--" />
<Setting name="FedAuthTrustedIssuerName" value="-- update with issuer name. e.g. https://yourissuer/--" />
<Setting name="FedAuthServiceCertificateThumbprint" value="-- update with service certificate thumbprint. e.g. same as HTTPS thumbprint: FE95C43CD4C4F1FC6BC1CA4349C3FF60433648DB --" />
<Setting name="FedAuthWSFederationRequireHttps" value="true" />

ASP.NET security exception with OpenWebConfiguration on shared host

After moving my web site from my local development environment to a shared host I get:
Security Exception
Description: The application attempted to perform an operation not allowed by
the security policy. To grant this application the required permission please
contact your system administrator or change the application's trust level in
the configuration file.
The problem occurs in my web application everywhere the following is called:
WebConfigurationManager.OpenWebConfiguration(Request.ApplicationPath)
Since my web application is only trying to open it's own web.config file, I don't know why this is flagged as a security exception. Maybe someone can explain... But more importantly I need a solution, the couple solutions I found via Google are painful.
One solution (from numerous posts) said to configure the trust level to Full, but I'm told that is not possible on my shared host.
Another solution (from https://web.archive.org/web/20210525032809/http://www.4guysfromrolla.com/articles/100307-1.aspx) says to not use OpenWebConfiguration(), but I need to use it to encrypt configuration sections (e.g. connectionStrings) using DPAPI (for more info see https://web.archive.org/web/20211020203213/https://www.4guysfromrolla.com/articles/021506-1.aspx).
Please advise on why IIS barfs on my web application trying to open it's own web.config, and a work-around to be able to encrypt parts of the web.config using DPAPI.
I have had experience of this issue in the past. The OpenWebConfiguration() method also reads the machine.config file. Under partial trust and without the correct permissions you can't use this method.
If you were to step into the .NET Framework assemblies with your debugger in Visual Studio 2008/2010 you can see exactly what is happening.
The following is a call stack captured when stepping into WebConfigurationManager.OpenWebConfiguration():
mscorlib.dll!System.IO.FileStream.Init(string path = "C:\\Windows\\Microsoft.NET\\Framework\\v2.0.50727\\Config\\machine.config", System.IO.FileMode mode = Open, System.IO.FileAccess access = Read, int rights = 0, bool useRights = false, System.IO.FileShare share = Read, int bufferSize = 4096, System.IO.FileOptions options = None, Microsoft.Win32.Win32Native.SECURITY_ATTRIBUTES secAttrs = null, string msgPath = "machine.config", bool bFromProxy = false) Line 326 C#
mscorlib.dll!System.IO.FileStream.FileStream(string path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share) Line 259 C#
System.Configuration.dll!System.Configuration.Internal.InternalConfigHost.StaticOpenStreamForRead(string streamName) + 0x56 bytes
System.Configuration.dll!System.Configuration.Internal.InternalConfigHost.System.Configuration.Internal.IInternalConfigHost.OpenStreamForRead(string streamName, bool assertPermissions) + 0x7d bytes
System.Configuration.dll!System.Configuration.Internal.InternalConfigHost.System.Configuration.Internal.IInternalConfigHost.OpenStreamForRead(string streamName) + 0xb bytes
System.Configuration.dll!System.Configuration.Internal.DelegatingConfigHost.OpenStreamForRead(string streamName) + 0xe bytes
System.Configuration.dll!System.Configuration.UpdateConfigHost.OpenStreamForRead(string streamName) + 0x2f bytes
System.Configuration.dll!System.Configuration.BaseConfigurationRecord.InitConfigFromFile() + 0x126 bytes
System.Configuration.dll!System.Configuration.BaseConfigurationRecord.Init(System.Configuration.Internal.IInternalConfigRoot configRoot, System.Configuration.BaseConfigurationRecord parent, string configPath, string locationSubPath) + 0xaa5 bytes
System.Configuration.dll!System.Configuration.MgmtConfigurationRecord.Init(System.Configuration.Internal.IInternalConfigRoot configRoot, System.Configuration.Internal.IInternalConfigRecord parent, string configPath, string locationSubPath) + 0x39 bytes
System.Configuration.dll!System.Configuration.MgmtConfigurationRecord.Create(System.Configuration.Internal.IInternalConfigRoot configRoot, System.Configuration.Internal.IInternalConfigRecord parent, string configPath, string locationSubPath) + 0x2a bytes
System.Configuration.dll!System.Configuration.Internal.InternalConfigRoot.GetConfigRecord(string configPath) + 0x12d bytes
System.Configuration.dll!System.Configuration.Configuration.Configuration(string locationSubPath, System.Type typeConfigHost, object[] hostInitConfigurationParams) + 0xfd bytes
System.Configuration.dll!System.Configuration.Internal.InternalConfigConfigurationFactory.System.Configuration.Internal.IInternalConfigConfigurationFactory.Create(System.Type typeConfigHost, object[] hostInitConfigurationParams) + 0x1e bytes
System.Web.dll!System.Web.Configuration.WebConfigurationHost.OpenConfiguration(System.Web.Configuration.WebLevel webLevel, System.Configuration.ConfigurationFileMap fileMap, System.Web.VirtualPath path, string site, string locationSubPath, string server, string userName, string password, System.IntPtr tokenHandle) Line 862 C#
System.Web.dll!System.Web.Configuration.WebConfigurationManager.OpenWebConfigurationImpl(System.Web.Configuration.WebLevel webLevel, System.Configuration.ConfigurationFileMap fileMap, string path, string site, string locationSubPath, string server, string userName, string password, System.IntPtr userToken) Line 77 + 0x1c bytes C#
System.Web.dll!System.Web.Configuration.WebConfigurationManager.OpenWebConfiguration(string path) Line 140 + 0x25 bytes C#
Unfortunately your only alternative is to use WebConfigurationManager.GetSection() which isn't as feature rich.
With regard to encrypting your connection strings. Sadly this feature demands Full Trust, there's no other way around it.

Resources