Custom MembershipProvider in .NET 4.0 - asp.net

There are a few threads here at so about this matter but most of them are outdated and the reference links in them are even more outdated.
I got this website which I need to connect to an external sql server (mssql) with it's own table structure, using the default asp.net membership provider structure is not an option. The table layout is really simple and the usertable looks like this (it's called Individuals)
Individuals
- UserGuid (uniqueidentifier/guid, unique)
- Name (varchar)
- Password (varchar)
- HasAccess (tinyint/ 1 or 0)
- DateTime (datetime)
- Log (xml)
The required functionality is simply to log someone in, the rest is not necessary :)
I followed some guides but most of them are outdated and very complex. Unfortunately the msdn examples follows this pattern and the documentation is not very good.
So if anyone got some resources showing how to, or are willing to post codesamples or similar here I'd appreciate it.
Thanks!

It's very simple really:
Create a new Class file (if you're not using a multi-layered system, in your project's Models folder) let's called MyMembershipProvider.cs
Inherit that class from System.Web.Security.MembershipProvider
automatically create the needed methods (period + space in the inherit class)
Done!
All methods will have the NotImplementedException exception, all you need to do is edit each one and put your own code. For example, I define the GetUser as shown below:
public override MembershipUser GetUser(string username, bool userIsOnline)
{
return db.GetUser(username);
}
dbis my Database Repository that I added into the class as
MyServicesRepository db = new MyServicesRepository();
there, you will find the GetUser method as:
public MembershipUser GetUser(string username)
{
OS_Users user = this.FindUserByUsername(username);
if (user == null)
return
new MembershipUser(
providerName: "MyMembershipProvider",
name: "",
providerUserKey: null,
email: "",
passwordQuestion: "",
comment: "",
isApproved: false,
isLockedOut: true,
creationDate: DateTime.UtcNow,
lastLoginDate: DateTime.UtcNow,
lastActivityDate: DateTime.UtcNow,
lastPasswordChangedDate: DateTime.UtcNow,
lastLockoutDate: DateTime.UtcNow);
return
new MembershipUser(
providerName: "MyMembershipProvider",
name: user.username,
providerUserKey: null,
email: user.email,
passwordQuestion: "",
comment: "ANYTHING you would like to pass",
isApproved: true,
isLockedOut: user.lockout,
creationDate: user.create_date,
lastLoginDate: user.lastLoginDate,
lastActivityDate: user.lastActivityDate,
lastPasswordChangedDate: user.lastPasswordChangedDate,
lastLockoutDate: user.lastLockoutDate);
}
Do this for all the methods you use (debug the project and see which ones you need) - I only use some, not all as I don't really care about methods like ChangePasswordQuestionAndAnswer, DeleteUser, etc
just make sure that in your web.config you add the new Membership as:
<membership defaultProvider="MyMembershipProvider">
<providers>
<clear/>
<add name="MyMembershipProvider" type="Your.NameSpace.MyMembershipProvider" connectionStringName="OnlineServicesEntities"
enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false"
maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10"
applicationName="/" />
</providers>
</membership>
You have a nice Video Tutorial from Chris Pels (dated 2007 but still mostly valid) and code for this as well, though Video Tutorial is in VB, but let's you understand the steps...
http://www.asp.net/general/videos/how-do-i-create-a-custom-membership-provider
I did not only create my own Membership Provider but I created my Roles Provider as well, witch as you can see from above code, is as simple as the MemberShip and let's you, in your application use things like:
[Authorize(Roles = "Partner, Admin")]
public ActionResult MyAction()
{
}
and
#if (Roles.IsUserInRole(Context.User.Identity.Name, "Admin"))
{
<div>You're an ADMIN, Congrats!</div>
}
What is automagically create the needed methods (period + space in the inherit class)
You can either right-click, or have the cursor on the name and press Control + . and then space.

There are a few threads here at so
about this matter but most of them are
outdated and the reference links in
them are even more outdated.
Since the introduction of ASP.NET in Framework 1.0 the Page.User/CurrentSession.User/IPrincipal/IIdentity model is unchanged. In Framework 2.0 the Membership provider was added. Those "outdated" reference remain valid guidance. MSDN

Related

Restrict access to all asp.net pages

I am mainlining one asp.net Project, this project is configured in IIS. The website is open for everyone, when i review the code in asp.net page, its checking window login "enterprise id" and allowing all users to view the all the aspx pages.
Now, my management team requested us to restrict those who are under junior level employees.(Junior engg, Developer, software engg).
I have written the query, passing enterprise id and validate grade, if its junior level , returning "0" values,else returning "1" values.
My questions is, I do not want go and edit each page and check this query and restrict each page.
can you please suggest , how can i implement simplest and best way to restric the users.
Thanks,
--------------------------------------- Update on 09/24/2015
Index.aspx
protected void Page_Load(object sender, EventArgs e)
{
string UserStatus = UtilFunctions.ValidateUser();
Response.Write(UserStatus);
if (UserStatus == "0")
{
Response.Write("<div><font color=red><h1>You are not authorized to view this page</h1></font></div>");
Response.End();
}
}
Utilifunctions.cs
public static String ValidateUser()
{
string CurrentUser = getLoggedOnUser();
using (System.Data.SqlClient.SqlConnection myConnection = new SqlConnection(ConfigurationManager.ConnectionStrings["TestDB"].ConnectionString))
{
using (SqlCommand myCommand = myConnection.CreateCommand())
{
myConnection.Open();//Opens the Connection
myCommand.CommandText = "Select Permission From Temp_Validate Where EnterpriseId='" + CurrentUser + "'";
SqlDataReader IDReader = myCommand.ExecuteReader(); //Gets the ID
IDReader.Read();
string UserStatus = IDReader["Permission"].ToString();
IDReader.Close();
return UserStatus;
}
}
I implemented the above functionalite in my index.aspx page, if the userstatus equal to "0" , it will display the "You are not authrized to view this message" and it will end.
I have around 30 aspx page,its currently running in Production. I do not want go include the same code (index.aspx) in every page load to stop the user validation.
could you please suggest how can i implement without editing all pages.
Updated on 09/28 : Utilifunction.cs
public static String getLoggedOnUser()
{
String user = HttpContext.Current.User.Identity.Name.Substring(HttpContext.Current.User.Identity.Name.IndexOf("\\") + 1);
if (user == "") user = "anonymous";
string UserStatus = IsValidUser(user);
if (UserStatus == "0")
{
HttpContext.Current.Response.Redirect("PSF_Error.aspx", true);
}
return user;
}
public static String IsValidUser(string currentUser)
{
using (System.Data.SqlClient.SqlConnection myConnection = new SqlConnection(ConfigurationManager.ConnectionStrings["Test"].ConnectionString))
{
using (SqlCommand myCommand = myConnection.CreateCommand())
{
//Gets email of the creator of current user
myConnection.Open();//Opens the Connection
myCommand.CommandText = "Select Permission From Temp_Validate Where EnterpriseId='" + currentUser + "'";
SqlDataReader IDReader = myCommand.ExecuteReader(); //Gets the ID
IDReader.Read();
string UserStatus = IDReader["Permission"].ToString();
IDReader.Close();
return UserStatus;
}
}
}
Index.aspx
Page_load
{
string CurrentUser = UtilFunctions.getLoggedOnUser();
}
You have a few options, here:
1) Set up role-based access with Owin or AspNet.Identity. This is probably your best option, but I couldn't find a good tutorial for you. Those packages are well-documented, however, and I'm sure you can figure them out with some effort.
2) Build a Roles table, and customize access yourself. The best example I found was here: http://www.codeproject.com/Articles/875547/Custom-Roles-Based-Access-Control-RBAC-in-ASP-NET
3) Redirect unauthorized users without the use of roles. So something like:
public ActionResult SecurePage(User u)
{
if(u.level == "junior"){
return RedirectToAction("CustomErrorPage");
} else {
return View();
}
}
I'm not sure that that option is terribly secure, but it should work.
Hope that helps!
after setting up roles you can use a web.config file in every directory specifying authorization and/or use the 'location' element in the web.config file.
First off, sorry about the confusing code. I've been using MVC, and you've clearly posted your code behind.
I don't think that you can achieve what you are trying to do, without adding your code to each page, or learning about roles. You could reduce some code duplication in a number of clever ways, but I can't think of anything that doesn't seem like a total hack.
If you want to, say, put all of your secure pages in the same directory, and restrict low-level access to that directory, you are going to have to filter by specific users or, if you can implement them, roles. As I understand it, the deny and allow nodes in your web.config file are setting server side (so IIS, probably) authorization rules, so the keywords and rules you can use are limited. Check this page out, for some basics:
http://weblogs.asp.net/gurusarkar/setting-authorization-rules-for-a-particular-page-or-folder-in-web-config
While it is likely POSSIBLE to build a rule based on values in your DB, doing so would probably be far more work than it would be worth.
Sorry that I can't offer a more satisfactory answer, but I would recommend: 1) Get to work, and add a check to the code behind for each page, or 2) (and I highly suggest this option) close this question, and post another, about implementing roles in .net, and assigning roles to users, in code. If, say, you can use your login page to assign every junior-level user the custom role of Junior, and place all of your secure pages in a directory named SecurePages you could add the following code to your web.config, and achieve exactly what you are trying to do:
<location path="SecurePages">
<system.web>
<authorization>
<deny roles="Junior">
<deny users="*">
</authorization></system.web></location>
Good luck!

SqlException when creating a new item in Asp.Net MVC 3 app

In my "web store" mvc app I want to add items to database. Items table has CreatedBy field and it is a foreign key from User table UserId field. Everything was working before I put the database into the App_Data folder. Now I get the SqlException when trying to create a new Item:
The INSERT statement conflicted with the FOREIGN KEY constraint "FK_Item_contains_User". The conflict occurred in database "C:\USERS\ALEKSEY\REPOS\2\WEBSTORE\WEBSTORE\APP_DATA\WEBSTORE.MDF", table "dbo.Users", column 'UserId'.
Here is the Create method of ItemRepository class:
public Item CreateItem(int productId, Guid userId)
{
var item = new Item
{
ProductId = productId,
CreatedBy = userId,
};
_dataContext.Items.InsertOnSubmit(item);
_dataContext.SubmitChanges(); // in this line the exception occures !
return item;
}
Here is the controller method Create:
[HttpGet]
public ViewResult Create()
{
var p = _productRepository.CreateProduct("", "", 0, "", "", "");
var userId = (Guid)Membership.GetUser().ProviderUserKey;
var item = _itemsRepository.CreateItem(p.ProductId, userId);
// some code
return View(model);
}
Besides, I use Linq to Sql model drag an' drop approach.
Here is the changed web.config connection string part:
<connectionStrings>
<add name="WebStoreConnectionString" connectionString="Data Source=(LocalDB)\v11.0;
AttachDbFilename=|DataDirectory|\WebStore.mdf;Integrated Security=True;Connect Timeout=30"
providerName="System.Data.SqlClient" />
<add name="DefaultConnection" connectionString="Data Source=|DataDirectory|\aspnet.sdf"
providerName="System.Data.SqlServerCe.4.0" />
As I said everything was working before I moved the database to App_Data file. I also tried to remove the dependency between Items and Users tables - the exact same exception.
Any help would be appropriate. Thanks in advance!
Edits:
Ok, now I really broke the dependency between Items and Users tables and no exception occures. But! I have to somehow know who has created each product, so breaking the dependency is not an option. I also tried to remove all code that initializes the CreatedBy field.
Any ideas??
Edits (part 2):
The second comment below gives a great advise! I found that all users that are created are stored now in the aspnet.sdf database!!!
But if I remove the connection string "DeafaultConnection":
<add name="DefaultConnection" connectionString="Data Source=|DataDirectory|\aspnet.sdf"
providerName="System.Data.SqlServerCe.4.0" />
I will get ConfigurationErrorsException:
"The connection name 'DefaultConnection' was not found in the applications
configuration or the connection string is empty."
in the folowing line:
var userId = (Guid)Membership.GetUser().ProviderUserKey;
Ok, as I guessed the issue was in the configuration. Each provider (for some reason) in the connection string had "DefaultConnection". I changed it to "WebStoreConnectionString". And now everything works!
p.s. thanks #w0lf, he pushed the thoughts in the right direction)

Maintain ASP.Net membership passwords during machine key change

Is there an utility or code sample that can decrypt with the old key, and then encrypt passwords with a new key for ASP.Net membership users?
None of the workarounds mentioned worked for me.
My solution is below. It involves first storing passwords in clear text and then reencrypting them again with new MachineKey.
Machine Key Change
This is my best guess at a solution, but I haven't had a chance to test it. It relies on the following settings for your current provider:
enablePasswordRetrieval="true" requiresQuestionAndAnswer="false" passwordFormat="Encrypted"
It also assumes that the new machinekey is already in the config file.
Create the following class (thanks to mootinator for the jumpstart on this)
using System.Reflection;
using System.Web.Configuration;
using System.Web.Security;
namespace MyNamespace
{
public class MySqlMembershipProvider : SqlMembershipProvider
{
protected override byte[] DecryptPassword(byte[] encodedPassword)
{
MachineKeySection section = (MachineKeySection)WebConfigurationManager.GetSection("system.web/machineKey");
section.DecryptionKey = "oldkey"; // TODO: Set your old key here
MethodInfo method = typeof(MachineKeySection).GetMethod("EncryptOrDecryptData", BindingFlags.Instance | BindingFlags.NonPublic);
return (byte[])method.Invoke(section, new object[] { encodedPassword, null, 0, encodedPassword.Length, 0, false, false });
}
}
}
In your web.config:
<membership defaultProvider="DefaultSqlMembershipProvider">
<providers>
<clear/>
<add name="DefaultSqlMembershipProvider" connectionStringName="MembershipConnectionString" enablePasswordRetrieval="true" requiresQuestionAndAnswer="false" applicationName="TODO" passwordFormat="Encrypted" type="System.Web.Security.SqlMembershipProvider"/>
<add name="MySqlMembershipProvider" connectionStringName="MembershipConnectionString" enablePasswordRetrieval="true" requiresQuestionAndAnswer="false" applicationName="TODO" passwordFormat="Encrypted" type="MyNamespace.MySqlMembershipProvider"/>
</providers>
</membership>
Change the passwords with the following code:
MembershipProvider retrievePasswordProvider = Membership.Providers["MySqlMembershipProvider"];
foreach (MembershipUser user in Membership.GetAllUsers())
{
MembershipUser retrievePassworedUser = retrievePasswordProvider.GetUser(user.UserName, false);
string password = retrievePassworedUser.GetPassword(); // get password using old key
user.ChangePassword(password, password); // change password to same password using new key
}
Let me know if that works for you.
I think you could do this by setting the key on the fly:
You might have to extend the SqlMembershipProvider (or whatever you use) to get access to the protected DecryptPassword method.
MachineKeySection section = (MachineKeySection)WebConfigurationManager.GetSection("system.web/machineKey");
section.DecryptionKey = "old";
// Read old password
section.DecryptionKey = "new";
// Store new password

Create Password and Passwordsalt

I have an existing table that has 100 users and passwords. The data type is a varchar.
I just created an asp.net mvc application and I want to convert the password to aspnet_membership table.
How do I convert varchar password on SQL level as "Password" and "Passwordsalt" in aspnet_membership table?
Password & PasswordSalt part are not processed and created at "SQL Level"
If you look closely to the asp.net membership database - tables / stored procedures / other objects. Then you will find that there are two stored procedures (sp for short) to create User in asp.net membership database tables.
aspnet_Membership_CreateUser
aspnet_Users_CreateUser
These sps will create user entry in aspnet_Membership & aspnet_Users table respectively.
ASP.Net membership works on the web.config file settings that you setup.
An example default webconfig entry will something like this:
<authentication mode="Forms"> // If you are using Form authentication
<forms loginUrl="~/Account/Login.aspx" timeout="2880" />
</authentication>
<membership>
<providers>
<clear/>
<add name="AspNetSqlMembershipProvider" type="System.Web.Security.SqlMembershipProvider" connectionStringName="ApplicationServices"
enablePasswordRetrieval="false" passwordFormat="Encrypted" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false"
maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10"
applicationName="/" />
</providers>
</membership>
In this settings section the attribute "passwordFormat" sets the way your user password is stored.
Options are - Clear (0), Hashed (1), Encrypted (2)
By default it will be having hashed value - or if u have not specified passwordFormat.
In clear text the password will be saved as - Text clear - readable.
With the Hashed option the password will not be (Encrypted), only encoded using a Hashing alogorithm
With the Encrypted option the password will be Encrypted and then encoded.
Encrypted option u specifies a non-auto generated "machine key"
To get one see: Get a non-autogenerated machine key
Password salt is a randomly generated string which is used to Encrypt and encode the password along with the Validation & Decryption Key.
If you want to overide the encryption method of asp.net membership provider and encode youself, (if using custome membership provider), you can do something like this:
private string EncodePassword(byte passFormat, string passtext, string passwordSalt)
{
if(passFormat.Equals(0)) // passwordFormat="Clear" (0)
return passtext;
else{
byte[] bytePASS = Encoding.Unicode.GetBytes(passtext);
byte[] byteSALT = Convert.FromBase64String(passwordSalt);
byte[] byteRESULT = new byte[byteSALT.Length + bytePASS.Length + 1];
System.Buffer.BlockCopy(byteSALT, 0, byteRESULT, 0, byteSALT.Length);
System.Buffer.BlockCopy(bytePASS, 0, byteRESULT, byteSALT.Length, bytePASS.Length);
if(passFormat.Equals(1)) // passwordFormat="Hashed" (1)
{
HashAlgorithm ha = HashAlgorithm.Create(Membership.HashAlgorithmType);
return (Convert.ToBase64String(ha.ComputeHash(byteRESULT)));
}
else // passwordFormat="Encrypted" (2)
{
MyCustomMembership myObj = new MyCustomMembership();
return(Convert.ToBase64String(myObj.EncryptPassword(byteRESULT)));
}
}
}
Example usage:
string passSalt = // Either generate a random salt for that user, or retrieve the salt from database if the user is in edit and has a password salt
EncodePassword(/* 0 or 1 or 2 */, passwordText, passSalt);
I hope this helps.
Its not possible at a SQL level, but with some C# code there are 2 posible techniques.
Simplest is to write a process to read through your existing table, and call Membership.CreateUser for each of the users, and the membership provider will create the user records for you, including the password & salt.
Alternatively, create yourself a dummy user, then wrote a process to change the password of the dummy user to the value from your existing users, and read the value from the aspnet_membership table. I have code that does this if you're interested.
HashAlgorithm ha = HashAlgorithm.Create(Membership.HashAlgorithmType);
How to check the ha is null or not if null means and how to throw the exception

How do I setup Linq to SQL and WCF

So I'm venturing out into the world of Linq and WCF web services and I can't seem to make the magic happen. I have a VERY basic WCF web service going and I can get my old SqlConnection calls to work and return a DataSet. But I can't/don't know how to get the Linq to SQL queries to work. I'm guessing it might be a permissions problem since I need to connect to the SQL Database with a specific set of credentials but I don't know how I can test if that is the issue. I've tried using both of these connection strings and neither seem to give me a different result.
<add name="GeoDataConnectionString" connectionString="Data Source=SQLSERVER;Initial Catalog=GeoData;Integrated Security=True"
providerName="System.Data.SqlClient" />
<add name="GeoDataConnectionString" connectionString="Data Source=SQLSERVER;Initial Catalog=GeoData;User ID=domain\userName; Password=blahblah; Trusted_Connection=true"
providerName="System.Data.SqlClient" />
Here is the function in my service that does the query and I have the interface add the [OperationContract]
public string GetCity(int cityId)
{
GeoDataContext db = new GeoDataContext();
var city = from c in db.Cities
where c.CITY_ID == 30429
select c.DESCRIPTION;
return city.ToString();
}
The GeoData.dbml only has one simple table in it with a list of city id's and city names. I have also changed the "Serialization Mode" on the DataContext to "Unidirectional" which from what I've read needs to be done for WCF.
When I run the service I get this as the return: SELECT [t0].[DESCRIPTION] FROM [dbo].[Cities] AS [t0] WHERE [t0].[CITY_ID] = #p0
Dang, so as I'm writing this I realize that maybe my query is all messed up?
Try this:
public string GetCity(int cityId)
{
GeoDataContext db = new GeoDataContext();
var city = db.Cities.SingleOrDefault(c => c.CITY_ID == 30429);
return city.DESCRIPTION;
}
The problem with your query is that it's not returning a string in the var, it's returning an IQueryable. So when you ToString() the IQueryable, it must be returning a string representation of the SQL query represented by the IQueryable.

Resources