Which Membership Provider implements users stored in web.config? - asp.net

Having a code-blind moment.
ASP.NET 4.0.
Web.config:
<?xml version="1.0"?>
<configuration>
<system.web>
<authentication mode="Forms">
<forms name="DataViewer" loginUrl="login.aspx">
<credentials passwordFormat="Clear">
<user name="devuser" password="test" />
</credentials>
</forms>
</authentication>
<authorization>
<deny users="?" />
</authorization>
</system.web>
and a login control:
<asp:Login ID="login" runat="server" />
If I enter a username and password, and click Login, it hangs.
If I break, I can see in the call stack that login.AuthenticateUsingMembershipProvider() is in the middle of calling SqlMembershipProvider.ValidateUser(). There is no database defined or involved in this project at all, and I haven't specified that SqlMembershipProvider should be used.
So my question is, what membership provider should I use to get ASP.NET to use the usernames and passwords in the <credentials> element of web.config?

I'm amazed that considering how the framework designers went to the trouble of defining a <credentials /> element that they didn't implement any code to consume it.
I found a sort-of-working implementation of this here which I have fixed up and included below. All other members of MembershipProvider throw NotImplementedException.
using System.Configuration;
using System.Web.Configuration;
using System.Web.Security;
public class WebConfigMembershipProvider : MembershipProvider
{
private FormsAuthenticationUserCollection _users = null;
private FormsAuthPasswordFormat _passwordFormat;
public override void Initialize(string name,
System.Collections.Specialized.NameValueCollection config)
{
base.Initialize(name, config);
_passwordFormat = getPasswordFormat();
}
public override bool ValidateUser(string username, string password)
{
var user = getUsers()[username];
if (user == null) return false;
if (_passwordFormat == FormsAuthPasswordFormat.Clear)
{
if (user.Password == password)
{
return true;
}
}
else
{
if (user.Password == FormsAuthentication.HashPasswordForStoringInConfigFile(password,
_passwordFormat.ToString()))
{
return true;
}
}
return false;
}
protected FormsAuthenticationUserCollection getUsers()
{
if (_users == null)
{
AuthenticationSection section = getAuthenticationSection();
FormsAuthenticationCredentials creds = section.Forms.Credentials;
_users = section.Forms.Credentials.Users;
}
return _users;
}
protected AuthenticationSection getAuthenticationSection()
{
Configuration config = WebConfigurationManager.OpenWebConfiguration("~");
return (AuthenticationSection)config.GetSection("system.web/authentication");
}
protected FormsAuthPasswordFormat getPasswordFormat()
{
return getAuthenticationSection().Forms.Credentials.PasswordFormat;
}
}

You are going to need to write your own provider for this. It should be relatively straightforward to take the sample ReadOnlyXmlMembershipProvider in the MSDN documentation and change it to read users and credentials from web.config, instead of an external XML file.

I'm not sure if you have tried but....
The FormsAuthentication.Authenticate is in charge to do that for you (although it is deprecated now because the recommended behavior is to use the Membership object)
From MSDN:
The Authenticate method verifies user credentials that are stored in the credentials section of the application configuration file. Alternatively, you can use ASP.NET membership to store user credentials and call the ValidateUser to verify the credentials.
You can also remove the membership providers (because even when you do not declare them on your web.config, they are inherited from the machine.config file)
<membership>
<providers>
<remove name="AspNetSqlMembershipProvider"/>
</providers>
</membership>

Try using this method. I hope it helps. http://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.login.onauthenticate.aspx

Related

WebForms authentication as MVC filter

In a migration from WebForms to MVC, some .aspx pages remain. Authentication for these is currently file-based and happens through Web.config. MVC authentication happens by adding an AuthorizeAttribute to GlobalFilters.Filters and to the controllers/actions as needed.
The Web.config authentication currently looks like this:
<authentication mode="Forms">
<forms loginUrl="~/SignIn.aspx" protection="All" path="/" timeout="10080" />
</authentication>
<authorization>
<deny users="?" />
<allow users="*" />
</authorization>
<location path="SomePage.aspx">
<system.web>
<authorization>
<allow roles="Administrator, BasicUser" />
<deny users="*" />
</authorization>
</system.web>
</location>
I would however like to move the WebForms authentication away from Web.config and into an MVC filter that performs a check using a method call. I have been unable to find a single example of this. Is this even possible, and are there any undesired implications of doing it?
I am aware that .aspx files are not handled by the MVC hooks by default.
I am looking for a way to grab all .aspx files regardless of what their code-behind's base class is.
In Global.asax.cs,
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
// Since MVC does not handle authentication for .aspx pages, do this here.
AuthController.AuthenticateAspxPage(this.Context);
}
and in AuthController, for some type UserRole,
public const string ErrorRedirectUrl = "~/Auth/Error";
private static readonly IDictionary<string, UserRole[]> _aspxAccessRoles =
new Dictionary<string, UserRole[]>()
{
{ "~/SomePage.aspx", new UserRole[] { UserRole.Administrator,
UserRole.BasicUser } },
...
};
and then,
[NonAction]
public static void AuthenticateAspxPage(HttpContext context)
{
string ext = context.Request.CurrentExecutionFilePathExtension;
string aspxPage = context.Request.AppRelativeCurrentExecutionFilePath;
ClaimsPrincipal principal = context.User as ClaimsPrincipal;
if ((ext == ".aspx" || ext == ".ashx") && !HasAspxAccess(aspxPage, principal))
{
context.Response.Redirect(ErrorRedirectUrl);
}
}
[NonAction]
public static bool HasAspxAccess(string aspxPage, ClaimsPrincipal principal)
{
if (principal == null || principal.Claims == null)
{
return false;
}
string[] userRoles = principal.Claims
.Where(claim => claim.Type == ClaimTypes.Role)
.Select(claim => claim.Value)
.ToArray();
UserRole[] accessRoles;
return _aspxAccessRoles.TryGetValue(aspxPage, out accessRoles)
&& accessRoles.Any(role => userRoles.Contains(role.ToString()));
}

ASP.NET API Windows authentication w/o login

Intranet application that needs to use Windows AD for authorization. Anyone will have access to the site, but only members of a specific AD group will have access to certain pages and API calls. The group name is in the web.config as it will vary by environment. I want to be able to use attributes on API controllers and methods:
[MyAdminAttribute]
public class MyController : ApiController
Here is my custom attribute:
public class MyAdminAttribute: AuthorizeAttribute
{
protected override bool IsAuthorized(HttpActionContext actionContext)
{
var user = ((ApiController)actionContext.ControllerContext.Controller).User; // THIS USER IS ALWAYS NULL, WHY?
var adminGroup = WebConfigurationManager.AppSettings["MyAdminGroup"];
var isInAdminGroup = user.IsInRole(adminGroup);
var roles = ((ClaimsIdentity)user.Identity).Claims
.Where(c => c.Type == ClaimTypes.Role)
.Select(c => c.Value).ToList(); // Just checking here, 0 roles found.
return isInAdminGroup;
}
}
I also need to be able to check that the user is in the role within a Razor view:
#if (User.IsInRole(WebConfigurationManager.AppSettings["MyAdminGroup"])) {
Here are the relevent web.config settings:
<authentication mode="Windows"/>
<roleManager enabled="true" defaultProvider="AspNetWindowsTokenRoleProvider">
<providers>
<clear />
<add
name="AspNetWindowsTokenRoleProvider"
type="System.Web.Security.WindowsTokenRoleProvider"
applicationName="/" />
</providers>
</roleManager>
However, the User is always null and User.IsInRole() is always false. Why would this be occuring?

MembershipProvider change connection string in code

I am trying to implement Asp.net Forms Authentication in my site. Usually, you provide the name of your DB connection string in your web.config. However, as our database setup is a little more complicated, I was looking for a way to manually provide the MembershipProvider the connection string in code.
Thanks!
You don't have to use a connectionStringName for SqlMembershipProvider, instead you can supply the connection string directly. I.e. instead of:
<membership defaultProvider="SqlProvider" ...>
<providers>
<add name="SqlProvider" type="System.Web.Security.SqlMembershipProvider"
connectionStringName="MyConnectionStringName"
.../>
</providers>
</membership>
You can specify the connection string directly as:
<membership defaultProvider="SqlProvider" ...>
<providers>
<add name="SqlProvider" type="System.Web.Security.SqlMembershipProvider"
connectionString="data source=... "
.../>
</providers>
</membership>
Therefore you could also derive a custom provider from SqlMembershipProvider, and build the connection string dynamically as follows:
public class MySqlMembershipProvider : SqlMembershipProvider
{
public override void Initialize(string name, NameValueCollection config)
{
config["connectionString"] = BuildMyCustomConnectionString();
base.Initialize(name, config);
}
}
I came across this needing to do the same thing, set the connection string via code and not in the web.config, although I needed to change more than the name, I needed the actual value to be dynamically generated. If you want to change the actual connection string to be generated from code you can do the following:
web.config
...
<connectionStrings>
<add name="ConnectionPlaceHolder" connectionString="This is a place holder"/>
</connectionStrings>
...
<roleManager defaultProvider="SqlRoleProvider" enabled="true">
<providers>
<clear/>
<add name="SqlRoleProvider" type="MyClassLibraries.Web.Security.MyCustomSqlRoleProvider" connectionStringName="ConnectionPlaceHolder" applicationName="MyApplicationName"/>
</providers>
</roleManager>
Provider Class
public class MySqlRoleProvider : SqlRoleProvider
{
public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
{
try
{
config["connectionStringName"] = "ConnectionPlaceHolder";
base.Initialize(name, config);
FieldInfo connectionStringField = GetType().BaseType.GetField("_sqlConnectionString", BindingFlags.Instance | BindingFlags.NonPublic);
connectionStringField.SetValue(this, ApplicationConfiguration.RetrieveApplicationConfigurationValue(myConnectionString));
}
catch (Exception ex)
{
CurrentOperation.RaiseException(ex);
throw ex;
}
}
private string myConnectionString()
{
return "Server=MyServer;database=MyDB;uid=aspnetDBUser;pwd=myPassword"
}
}
When you call base.Initialize() the .NET class requires there be a name specified in the web.config which is why you have to put something, so I just used a placeholder since I knew I would be overriding it in the code.
Our team did this because we needed to dynamically build connection strings based on different environments and didn't want to have to worry about having multiple web.configs floating around.

How to configure ELMAH to show log just for Sitecore user

I added Elmah tool to my Sitecore Solution, actually I did it like here
http://newguid.net/sitecore/2011/using-elmah-for-error-logging-within-sitecore/
Just one rolls I have enabled is allowRemoteAccess property,
<elmah>
<security allowRemoteAccess="1" />
</elmah>
Now logs available for every one from any place, but I want to show it just for user who is login (authorized) to sitecore (sitecore users)
How I can mange it ?
Thanks.
Not sure what Elmah had built in now, but I used to do this:
<location path="elmah.axd">
<system.web>
<authorization>
<allow roles="SITECORE_USERS"/> <!---put your role here-->
<deny users="*"/>
</authorization>
</system.web>
</location>
Securing Elmah RSS Feeds in ASP.NET website
I found solution after research of elmah source code and this article http://dotnetslackers.com/articles/aspnet/Securing-ELMAH-with-Independent-HTTP-Authentication.aspx. User can implement IRequestAuthorizationHandler for custom IHttpModule
I did next:
public class SitecoreAuthModule :IHttpModule, IRequestAuthorizationHandler
{
public bool Authorize(HttpContext context)
{
return SC.Context.User.IsAdministrator;
}
public void Init(HttpApplication context)
{
context.AuthenticateRequest += new EventHandler(ContextAuthenticateRequest); ;
}
void ContextAuthenticateRequest(object sender, EventArgs e)
{
var context = sender as HttpApplication;
if (context.Request.Path.IndexOf("elmah.axd",
StringComparison.InvariantCultureIgnoreCase) < 0)
return;
}
public void Dispose()
{
}
}

.Net web application behind a WebSeal reverse proxy

We are currently designing a solution that will run as a .Net Web application behind a WebSeal reverse proxy.
I have seen some comments on the net where people have had various problems with this, for example rewriting of viewstate.
Question is: Has anyone implemented this combination of techologies and got it to work?
I made an ASP.NET application workin behind WEBSEAL. After lot of study and development and test it works.
I suggest some issues to help you:
IIS and ASP.NET are case insensitive
("...Login.aspx" and "...login.aspx" both lead to the same page); by default webseal is case sensitive. So you should set WEBSEAL junction to be case insensitive or check any single link (page, javascript, image)
Internal links, written as server relative URLs won't be served
WEBSEAL changes any link referring your application but doesn't change links to other applications.
Internal links, written as server relative URLs instead of application relative URLs won't be changed (WEBSEAL doesn't recognize it's the same application) and won't be served (WEBSEAL rejects links that are not modified).
First rule is to check any single link and make it an application relative URL .
Look at rendered HTML if you find <.. href=/ anything> : this i a server relative URL and it is bad.
Look in the Code Behind if you use "= ~/ anything" it is good. If you use "= / anything" OR ResolveUrl(..) it is bad.
But this is not enough: AJAX puts loads of javascript and code inside ScriptResource.axd and WebResource.axd and creates server relative URL to link it. This links are not controlled by programmers and there is no easy way to change them.
Easy solution (if possible): solve the problem setting WEBSEAL junction to be transparent.
Hard solution: write the following code (thanks to this answer)
protected void Page_Load(object sender, EventArgs e)
{
//Initialises my dirty hack to remove the leading slash from all web reference files.
Response.Filter = new WebResourceResponseFilter(Response.Filter);
}
public class WebResourceResponseFilter : Stream
{
private Stream baseStream;
public WebResourceResponseFilter(Stream responseStream)
{
if (responseStream == null)
throw new ArgumentNullException("ResponseStream");
baseStream = responseStream;
}
public override bool CanRead
{ get { return baseStream.CanRead; } }
public override bool CanSeek
{ get { return baseStream.CanSeek; } }
public override bool CanWrite
{ get { return baseStream.CanWrite; } }
public override void Flush()
{ baseStream.Flush(); }
public override long Length
{ get { return baseStream.Length; } }
public override long Position
{
get { return baseStream.Position; }
set { baseStream.Position = value; }
}
public override int Read(byte[] buffer, int offset, int count)
{ return baseStream.Read(buffer, offset, count); }
public override long Seek(long offset, System.IO.SeekOrigin origin)
{ return baseStream.Seek(offset, origin); }
public override void SetLength(long value)
{ baseStream.SetLength(value); }
public override void Write(byte[] buffer, int offset, int count)
{
//Get text from response stream.
string originalText = System.Text.Encoding.UTF8.GetString(buffer, offset, count);
//Alter the text.
originalText = originalText.Replace(HttpContext.Current.Request.ApplicationPath + "/WebResource.axd",
VirtualPathUtility.MakeRelative(HttpContext.Current.Request.Url.AbsolutePath, "~/WebResource.axd"));
originalText = originalText.Replace(HttpContext.Current.Request.ApplicationPath + "/ScriptResource.axd",
VirtualPathUtility.MakeRelative(HttpContext.Current.Request.Url.AbsolutePath, "~/ScriptResource.axd"));
//Write the altered text to the response stream.
buffer = System.Text.Encoding.UTF8.GetBytes(originalText);
this.baseStream.Write(buffer, 0, buffer.Length);
}
This intercepts the stream to the page and replaces all occurrences of "/WebResource.axd" or "ScriptResource.axd" with "../../WebResource.axd" and "../../ScriptResource.axd"
Develop code to get actual WEBSEAL user
WEBSEAL has been configured to put username inside HTTP_IV_USER. I created Webseal\Login.aspx form to read it programmatically.
Now, in order to make this user the CurrentUser I put an hidden asp.Login
<span style="visibility:hidden">
<asp:Login ID="Login1" runat="server" DestinationPageUrl="~/Default.aspx">..
and clicked the button programmatically
protected void Page_Load(object sender, EventArgs e)
{
string username = Request.ServerVariables["HTTP_IV_USER"];
(Login1.FindControl("Password") as TextBox).Text = MyCustomProvider.PswJump;
if (!string.IsNullOrEmpty(username))
{
(Login1.FindControl("UserName") as TextBox).Text = username;
Button btn = Login1.FindControl("LoginButton") as Button;
((IPostBackEventHandler)btn).RaisePostBackEvent(null);
}
else
{
lblError.Text = "Login error.";
}
}
When LoginButton fires, application reads UserName (set from WEBSEAL variable) and password (hard coded). So i implemented a custom membership provider that validates users and sets current Principal.
Changes in web.config
loginUrl is the URL for the login page that the FormsAuthentication class will redirect to. It has been set to WEBSEAL portal: not authenticated user and logout button will redirect to portal.
<authentication mode="Forms">
<forms loginUrl="https://my.webseal.portal/" defaultUrl="default.aspx"...."/>
</authentication>
Since Webseal/login.aspx is NOT default login page, authorization tag grants access to not authenticated users:
<location path="Webseal/login.aspx">
<system.web>
<authorization>
<allow users="*"/>
</authorization>
</system.web>
</location>
Application is set to use custom membership providers:
<membership defaultProvider="MyCustomMembershipProvider">
<providers>
<add name="MyCustomMembershipProvider" type="MyNamespace.MyCustomMembershipProvider" connectionStringName="LocalSqlServer"/>
</providers>
</membership>
<roleManager enabled="true" defaultProvider="MyCustomRoleProvider">
<providers>
<add name="MyCustomRoleProvider" type="MyNamespace.MyCustomRoleProvider" connectionStringName="LocalSqlServer"/>
</providers>
</roleManager>
Debug is set to off:
<compilation debug="false" targetFramework="4.0">
that's all folks!
I initially has some issues with the ASP.Net app when accessed through WebSeal. I was running the site on a development server. What worked for me was to deploy the application with debugging turned off in the config file.
<compilation debug="false" ...>
With debugging turned on, there were some AJAX calls that would work fine when I accessed the site directly but would fail when access through WebSeal. Once I turned the debugging off, everything work fine.
Also, because WebSeal requires anonymous authentication, we couldn't have used Windows Authentication.

Resources