Introduction
My intention is to provide a kick start for the SharePoint developers to write a Custom Provider for their database. You should be able to:
- Use your existing user database with your SharePoint
- Upgrade your existing ASP.NET appliation to SharePoint
- Authenticate against a webservice etc.
If you search on internet, you will find much results for configuring forms authentication in SharePoint. As a beginner you will be still confuzed and the question still pending will be "how to use my own user database with SharePoint?".
Answer to the question is same - use forms authentication.
Membership Class
ASP.NET forms authentication uses .NET Membership class of System.web.security. Same we will also need to use. We need to make a mock class (I would like to call...) which derives from the Membershilp class.
Task List
Before going further, I would like you know what you are going to do.
- Build a Custom Membership .dll
- Add .dll to GAC - The Global Assembly Cache
- SharePoint Central Administration - Configure web.config
- SharePoint Central Administration - Administration
- SharePoint Website - Configure web.config
Prerequisites
The tools I used while writing this article are:
- Visual Studio 2008 (you can use any edition, even express edition willl do)
- Pre-installed Windows SharePoint Service 3.0
- Knowledge in SharePoint
- Knowledge in C#
Step 1: Build a Custom Membership .dll
Step 1.1: Database Setup
For the demonstration purpose, I use the following table structure.
CREATE TABLE users (
[id] [int] IDENTITY(1,1) NOT NULL,
[username] [varchar](50) NULL,
[password] [varchar](50) NULL,
[name] [varchar](50) NULL
)
[IMAGE db.jpg]
Step 1.2: Implement MembershipProvider .dll
Frankly the process is just same as the creation of a Membership provider for ordinary ASP.NET application.
You need to create a new class (a class libray project will do) which inherits the MembershipProvider of System.Web.Security.
public class pitMembership : MembershipProvider
{
}
You need to include all overriden methods in your memebership class. For this demonstration purpose, I use only those who are necessary for the working of SharePoint. Eg: GetUser(), GetAllUsers(), ValidateUser() etc.
Here goes the full source code:
using System;
using System.Web.Security;
using System.Data;
using System.Data.SqlClient;
/*
* Coded by Praveen (ninethsense@hotmail.com) for article/demonstration purpose
* Warning: I did not follow standards
*/
namespace pitCustomMembership
{
public class pitMembership : MembershipProvider
{
//Your SQL Server connection
private const string conStr = "Data Source=localhost;Initial Catalog=test;User Id=blah;Password=blah;";
// I would like call the appilcation name...
private string _AppName = "pitMembership";
// Dummy ValidateUserStart ******************************
public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
{
throw new NotImplementedException();
}
public override int GetNumberOfUsersOnline()
{
throw new NotImplementedException();
}
public override string GetUserNameByEmail(string email)
{
throw new NotImplementedException();
}
public override string ResetPassword(string username, string answer)
{
throw new Exception("The method or operation not implemented.");
}
public override bool UnlockUser(string userName)
{
throw new Exception("The method or operation not implemented.");
}
public override void UpdateUser(MembershipUser user)
{
throw new Exception("The method or operation not implemented.");
}
public override bool ChangePassword(string username, string oldPassword, string newPassword)
{
throw new Exception("The method or operation not implemented.");
}
public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)
{
throw new Exception("The method or operation not implemented.");
}
public override string GetPassword(string username, string answer)
{
throw new Exception("The method or operation not implemented.");
}
public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
{
throw new Exception("The method or operation not implemented.");
}
public override int MinRequiredPasswordLength
{
get { return 1; }
}
public override int MinRequiredNonAlphanumericCharacters
{
get { return 1; }
}
public override string PasswordStrengthRegularExpression
{
get { return ""; }
}
public override MembershipPasswordFormat PasswordFormat
{
get { return MembershipPasswordFormat.Clear; }
}
public override bool RequiresUniqueEmail
{
get { return false; }
}
public override bool DeleteUser(string username, bool deleteAllRelatedData)
{
throw new Exception("The method or operation not implemented.");
}
public override int PasswordAttemptWindow { get { return 1; } }
public override int MaxInvalidPasswordAttempts { get { return 5; } }
public override bool RequiresQuestionAndAnswer { get { return false; } }
public override bool EnablePasswordReset { get { return true; } }
public override bool EnablePasswordRetrieval { get { return true; } }
public override string ApplicationName
{
get { return _AppName; }
set { if (_AppName != value) { _AppName = value; } }
}
// Dummy End ******************************
public override bool ValidateUser(string username, string password)
{
SqlConnection con = new SqlConnection(conStr);
SqlCommand cmd = new SqlCommand("SELECT COUNT(1) FROM users WHERE username=@username AND password=@password");
cmd.Parameters.Add(new SqlParameter("@username", username));
cmd.Parameters.Add(new SqlParameter("@password", password));
cmd.Connection = con;
con.Open();
int count = Convert.ToInt16(cmd.ExecuteScalar());
con.Close();
return count != 0;
}
public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
{
MembershipUserCollection muc = new MembershipUserCollection();
try
{
SqlDataAdapter da = new SqlDataAdapter("SELECT id, username, password, name FROM users",new SqlConnection(conStr));
DataSet ds = new DataSet();
da.Fill(ds);
foreach (DataRow dr in ds.Tables[0].Rows)
{
muc.Add(new MembershipUser(
"pitMembership",
dr["username"].ToString(),
dr["id"],
"ninethsense@hotmail.com",
"",
"",
true,
false,
DateTime.Now,
DateTime.Now,
DateTime.Now,
DateTime.Now,
DateTime.MinValue
));
}
totalRecords = ds.Tables[0].Rows.Count;
}
catch (Exception e)
{
throw new Exception("Exception in GetAllUsers():" + e.Message);
}
return muc;
}
public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
{
MembershipUserCollection muc = new MembershipUserCollection();
try
{
// Beware of code injection :-)
SqlDataAdapter da = new SqlDataAdapter("SELECT id, username, password, name FROM users WHERE username='"+usernameToMatch+"'", new SqlConnection(conStr));
DataSet ds = new DataSet();
da.Fill(ds);
foreach (DataRow dr in ds.Tables[0].Rows)
{
muc.Add(new MembershipUser(
"pitMembership",
dr["username"].ToString(),
dr["id"],
"ninethsense@hotmail.com",
"",
"",
true,
false,
DateTime.Now,
DateTime.Now,
DateTime.Now,
DateTime.Now,
DateTime.MinValue
));
}
totalRecords = ds.Tables[0].Rows.Count;
}
catch (Exception e)
{
throw new Exception("Exception in GetAllUsers():" + e.Message);
}
return muc;
}
public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
{
MembershipUser mu = null;
try
{
// Beware of code injection :-)
SqlDataAdapter da = new SqlDataAdapter("SELECT id, username, password, name FROM users WHERE id=" + providerUserKey.ToString(), new SqlConnection(conStr));
DataSet ds = new DataSet();
da.Fill(ds);
if (ds.Tables[0].Rows.Count > 0)
{
DataRow dr = ds.Tables[0].Rows[0];
mu = new MembershipUser(
"pitMembership",
dr["username"].ToString(),
dr["id"],
"ninethsense@hotmail.com",
"",
"",
true,
false,
DateTime.Now,
DateTime.Now,
DateTime.Now,
DateTime.Now,
DateTime.MinValue
);
}
}
catch (Exception e)
{
throw new Exception("Exception in GetUser()1:" + e.Message);
}
return mu;
}
public override MembershipUser GetUser(string username, bool userIsOnline)
{
MembershipUser mu = null;
try
{
// Beware of code injection :-)
SqlDataAdapter da = new SqlDataAdapter("SELECT id, username, password, name FROM users WHERE username='" + username + "'", new SqlConnection(conStr));
DataSet ds = new DataSet();
da.Fill(ds);
if (ds.Tables[0].Rows.Count > 0)
{
DataRow dr = ds.Tables[0].Rows[0];
mu = new MembershipUser(
"pitMembership",
dr["username"].ToString(),
dr["id"],
"ninethsense@hotmail.com",
"",
"",
true,
false,
DateTime.Now,
DateTime.Now,
DateTime.Now,
DateTime.Now,
DateTime.MinValue
);
}
}
catch (Exception e)
{
throw new Exception("Exception in GetUser()2:" + e.Message);
}
return mu;
}
}
}
For your easy understanding I used plain SQL statements inside the code. You can see for all dummy overriden methods, I thrown exception. Let me tell you about the used methods:
- ValidateUser() - This method verifies whether the supplied username and password matches any record in your database. This is used when you try to login to sharepoint.
- GetAllUsers() - Returns a list of all members available in database
- FindUsersByName() - Returns a list of members which matches the supplied username
- GetUser(providerUserKey,bool) - Get the user information based on supplied user key
- GetUser(username, bool) - Get the user information based on the supplied username
Step 1.3: Sign the .dll
In order to add your dll (assembly) to GAC, you need to sign your dll with a strong name. An easy method will be to do the following:
- Take Project Properties (right click on the project node on Solution Explorer -> Properties)
- Signing Tab
- Click the checkbox of Sign the assembly
- Select <New...> from the dropdown
- Add a new keyname (and provide password if you wish or just uncheck the check box)
Step 1.4: Build
Now build the appliation so that you have the .dll file ready. I have a dll with name pitCustomMembership.dll.
Step 2: Add .dll to GAC
I will use the gacutil command line to do this. Follow these steps if you do not know.
- Start -> Programs -> Microsoft Visual Studio 2008 -> Visual Studio 2008 Tools -> Visual Studio 2008 Command Prompt
- type gacutil /i your dll.
Here is what I saw:
C:\SharePoint custom provider\pitCustomMembership\bin>gacutil /i pitCustomMembership.dll
Microsoft (R) .NET Global Assembly Cache Utility. Version 3.5.30729.1
Copyright (c) Microsoft Corporation. All rights reserved.
Assembly successfully added to the cache
Now let us assume the dll is in GAC.
Step 3: SharePoint Central Administration - Configure web.config
Take the web.config file of your Central Adminstration website. You can find the path from IIS Manager - Start -> Settings ->Control Panel -> Administrative Tools -> Internet Information Services (IIS) Manager
Add the below code inside the <system.web> node:
<membership defaultProvider="pitMembership">
<providers>
<clear />
<add name="pitMembership" type="pitCustomMembership.pitMembership" />
</providers>
</membership>
type must be of form namespace.classname
Now add the below code inside <assemblies> node:
<add assembly="pitCustomMembership, Version=1.0.0.0, Culture=neutral, PublicKeyToken=a9b23b4df7c7dbf0" />
Wonder where this assembly, Version etc. comes from? Here is one way to get those details:
- On windows explorer, point to C:\Windows\assembly
- Locate your dll
- Right click -> Properties
Step 4: SharePoint Central Administration - Administration
[IMAGE auth1.jpg]
- Login to your Central Administration Website
- Application Management -> Application Security:Authentication Provider
- Select Default Zone
- Choose Authentication Type = Forms
- Typ3 Membership provider name
[IMAGE auth2.jpg]
Now go back to Application Management again then
- Application Management -> SharePoint Site Management:Site Collection Administrators
- Type a username from your database and click check names icon.
You should see your username underlined now. This is a good sign that your authentication provider started working.
Step 5: SharePoint Website - Configure web.config
Now paste the same <membership> tag and <assemblies> of your central administration website's web.config (Step 3) in your SharePoint website.
Everything Finished!
[IMAGE login.jpg]
Now you should be able to login to the SharePoint website with the username and password in your database.
About the Author
Praveen.V.Nair aka NinethSense, a technology enthusiast and Microsoft MVP Awardee. You can find more about him on his website http://www.ninethsense.com and his blog http://blog.ninethsense.com.