is there any custom asp.net membership providers that someone has already created online. I want to use a custom provider because their role provider is very sluggish and I would like to use integers instead of guids for user IDs
There can be no alternative implementations of the standard membership provider API, given that it is based on non-generic interfaces.
However, in practice the whole membership API adds very little value and it is fairly easy to roll your own solution.
To help you get started here are a few of the helper classes that I typically use.
First, some code to deal with cookies and Forms authentication:
public interface ISecurityPersistenceProvider
{
void SetAuthCookie( int version, string name, DateTime expireDate, bool persistent, string cookieData );
HttpCookie GetAuthCookie();
string GetAuthCookieValue( out string name );
void RemoveAuthCookie();
}
public class FormsPersistenceProvider : ISecurityPersistenceProvider
{
public void SetAuthCookie( int version, string name, DateTime expireDate, bool persistent, string cookieData )
{
var ticket = new FormsAuthenticationTicket( version, name, DateTime.Now, expireDate, persistent, cookieData );
string secureTicket = FormsAuthentication.Encrypt( ticket );
var cookie = new HttpCookie( FormsAuthentication.FormsCookieName, secureTicket );
cookie.Expires = ticket.Expiration;
HttpContext.Current.Response.Cookies.Add( cookie );
}
public HttpCookie GetAuthCookie()
{
HttpCookie cookie = HttpContext.Current.Request.Cookies[ FormsAuthentication.FormsCookieName ];
return cookie;
}
public string GetAuthCookieValue( out string name )
{
HttpCookie cookie = GetAuthCookie();
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt( cookie.Value );
name = ticket.Name;
return ticket.UserData;
}
public void RemoveAuthCookie()
{
FormsAuthentication.SignOut();
}
}
Second, some code to validate users against a data store. You'll need to provide your own implementation for this interface (and/or adapt it to your needs).
public interface ISecurityUserProvider<T> where T : class
{
T AuthenticateUser( string customer, string userName, string passwordHash );
T GetUser( string customer, string userName );
string[] GetRoles( T user );
}
Third, a class to let us return additional information to callers (this will be used by the SecurityManager class below):
public class AuthenticationResult
{
#region Fields/Properties
public bool Success { get; internal set; }
public string Message { get; internal set; }
public IDictionary<string,string> Errors { get; internal set; }
#endregion
#region Constructors
public AuthenticationResult( bool success, string message ) : this( success, message, null )
{
}
public AuthenticationResult( bool success, string message, IDictionary<string, string> errors )
{
Success = success;
Message = message;
Errors = errors ?? new Dictionary<string, string>();
}
#endregion
}
I'm also using a custom principal class that allows me to associate a custom object with the basic IPrincipal implementation. This is just a quick extract to give you the idea:
public class CodeworksPrincipal<TUserData> : IPrincipal where TUserData : class
{
private string name;
private string[] roles;
private string allRoles;
private TUserData userData;
public CodeworksPrincipal( string name, string[] roles, TUserData userData )
{
// init fields, etc.
}
}
And finally, the SecurityManager class with the bulk of the logic:
public class SecurityManager<T,TPersistenceProvider,TUserProvider>
where T : class
where TPersistenceProvider : ISecurityPersistenceProvider
where TUserProvider : ISecurityUserProvider<T>
{
private readonly ISecurityPersistenceProvider persistenceProvider;
private readonly ISecurityUserProvider<T> userProvider;
// NOTE this constant is used to validate the validity of the cookie (see below)
private const int CookieParameterCount = 3;
public SecurityManager( ISecurityPersistenceProvider persistenceProvider, ISecurityUserProvider<T> userProvider )
{
this.persistenceProvider = persistenceProvider;
this.userProvider = userProvider;
}
#region Properties
protected ISecurityPersistenceProvider PersistenceProvider
{
get { return persistenceProvider; }
}
protected ISecurityUserProvider<T> UserProvider
{
get { return userProvider; }
}
public IIdentity CurrentIdentity
{
get { return Thread.CurrentPrincipal.Identity; }
}
public bool IsAuthenticated
{
get
{
IPrincipal principal = Thread.CurrentPrincipal;
return principal != null && principal.Identity != null && principal.Identity.IsAuthenticated;
}
}
public bool IsInRole( string roleName )
{
IPrincipal principal = Thread.CurrentPrincipal;
return IsAuthenticated && principal.IsInRole( roleName );
}
public string UserName
{
get { return IsAuthenticated ? CurrentIdentity.Name : ""; }
}
#endregion
#region Authentication
public AuthenticationResult Authenticate( string userName, string password, bool persistent, string visitorAddress )
{
return Authenticate( null, userName, password, persistent, visitorAddress );
}
public AuthenticationResult Authenticate( string customer, string userName, string password, bool persistent, string visitorAddress )
{
AuthenticationResult result = ValidateInput( userName, password );
if( ! result.Success )
return result;
string passwordHash = GetCryptographicHash( password );
T user = userProvider.AuthenticateUser( customer, userName, passwordHash );
if( user == null )
return new AuthenticationResult( false, "Unable to login using the specified credentials.", null );
if( ! IsAuthorizedVisitor( user, visitorAddress ) )
return new AuthenticationResult( false, "Credentials do not allow login from your current IP address.", null );
CurrentPrincipal = new CodeworksPrincipal<T>( userName, userProvider.GetRoles( user ), user );
// remember to change CookieParameterCount if you change parameter count here
string cookieData = String.Format( "{0}|{1}|{2}", CurrentIdentity.Name, customer, CurrentPrincipal.AllRoles );
persistenceProvider.SetAuthCookie( 1, userName, DateTime.Now.AddMonths( 1 ), persistent, cookieData );
// TODO create an audit log entry for the current request
return new AuthenticationResult( true, null, null );
}
private AuthenticationResult ValidateInput( string login, string password )
{
var result = new AuthenticationResult( true, "Invalid or missing credentials." );
if( String.IsNullOrEmpty( login ) )
{
result.Success = false;
result.Errors.Add( "login", "You must specify an alias or email address." );
}
if( String.IsNullOrEmpty( password ) )
{
result.Success = false;
result.Errors.Add( "password", "You must specify a password." );
}
// TODO add message to inform users of other requirements.
return result;
}
#endregion
#region Cookie Authentication
public void CookieAuthenticate( string visitorAddress )
{
HttpCookie cookie = persistenceProvider.GetAuthCookie();
if( cookie != null )
{
string userName;
string userData = persistenceProvider.GetAuthCookieValue( out userName );
string[] cookieData = userData.Split( '|' );
// extract data from cookie
bool isValid = cookieData.Length == CookieParameterCount &&
! string.IsNullOrEmpty( cookieData[ 0 ] ) &&
cookieData[ 0 ] == userName &&
IsAuthorizedVisitor( cookieData[ 1 ], cookieData[ 0 ], visitorAddress );
if( isValid )
{
string customer = cookieData[ 1 ];
string[] roles = cookieData[ 2 ].Split( ',' );
T user = userProvider.GetUser( customer, userName );
CurrentPrincipal = new CodeworksPrincipal<T>( userName, roles, user );
}
}
}
#endregion
#region Logout
public void Logout()
{
// logout the current user
if( HttpContext.Current.Request.IsAuthenticated || CurrentPrincipal != null )
{
long address = GetAddressFromString( HttpContext.Request.UserHostAddress );
// TODO add audit log entry
// sign out current user
persistenceProvider.RemoveAuthCookie();
CurrentPrincipal = null;
}
}
#endregion
#region VisitorAddress Checks
public long GetAddressFromString( string address )
{
IPAddress ipAddress;
if( IPAddress.TryParse( address, out ipAddress ) )
{
byte[] segments = ipAddress.GetAddressBytes();
long result = 0;
for( int i = 0; i < segments.Length; i++ )
{
result += segments[ i ] << (i * 8);
}
return result;
}
return long.MaxValue;
}
public bool IsAuthorizedVisitor( string customer, string userName, string visitorAddress )
{
return IsAuthorizedVisitor( customer, userName, GetAddressFromString( visitorAddress ) );
}
public bool IsAuthorizedVisitor( string customer, string userName, long visitorAddress )
{
T user = userProvider.GetUser( customer, userName );
return IsAuthorizedVisitor( user, visitorAddress );
}
public bool IsAuthorizedVisitor( T user, string visitorAddress )
{
return IsAuthorizedVisitor( user, GetAddressFromString( visitorAddress ) );
}
public bool IsAuthorizedVisitor( T user, long visitorAddress )
{
if( user == null || visitorAddress == 0 )
return false;
if( user.Hosts.Count == 0 )
return true;
foreach( Host host in user.Hosts )
{
if( IsAuthorizedVisitor( host.HostAddress, host.HostMask, visitorAddress ) )
return true;
}
return true;
}
public bool IsAuthorizedVisitor( long allowedAddress, long allowedMask, long visitorAddress )
{
long requireMask = allowedAddress & allowedMask;
return (visitorAddress & requireMask) == requireMask;
}
#endregion
#region Cryptographic Helpers
public static string GetCryptographicHash( string password )
{
SHA256 hash = SHA256.Create();
byte[] input = Encoding.UTF8.GetBytes( password );
byte[] output = hash.ComputeHash( input );
return Convert.ToBase64String( output );
}
#endregion
}
You can strip out the bits to check the visitors host address if you don't need it.
I hope this helps even if it isn't a membership provider per se ;-)