FSCAuth Porting to PHP

Hello, I'll be honest with you all. FSCAuth was kind of a failure. It's awesome and all, but for some reason people just aren't interested in it.

I evaluated the .Net market and saw that quite a few people are happy with the (more difficult) Forms Authentication... But, there is one market that I was into years ago that had no official form of authentication, PHP. So, I looked into the existing PHP authentication solutions and saw, well a big lack of libraries out there.

So, I've began working on a port to FSCAuth to PHP. I aim to support all of the functions supported in the .Net version, with a few notable syntax-related changes that comes with porting to a different language. But basically, it should be capable of all of the same things. To list them again:

  1. Keeping track of salts(therefore allowing for Blowfish and similar algorithm support)
  2. Simple 4 function user store
  3. Easy to configure, just change a few configuration options, hook up the userstore to your database, and put the login calls in needed places in your application.

So, what's it going to look like? It'll basically look like the .Net version, a big static class.

Here is my test.php class:

<?php
  require("fscauth.php");
?>
...HTML code... 
<?php
  if(array_key_exists("test", $_GET) && $_GET["test"]==="1"){
    //try login
    FSCAuth::Login("foobar","password",false);
    echo '<br /> username: '.FSCAuth::GetCurrentUser()->Username;
  }else{
    //list user
    $user=FSCAuth::GetCurrentUser();
    if($user!=null){
      echo 'username: '.$user->Username.'<br />';
    }else{
      echo 'no one is logged in <br />';
    }

  }
?>

As you can tell, some of the syntax is a bit less.. intuitive, but this is a limitation of PHP.

But anyway, if you're a PHP developer and would like to beta test something like this, just drop me a line at earlz -at- this domain(lastyearswishes.com)

Also, in case you're wondering, the cookies generated WILL be compatible across the .Net and PHP version. So, if you have two applications that share login cookies, this is a very ideal use-case.

Posted: 7/3/2012 5:43:33 AM

But how does it work?!

I was going to put a rant in here about PHP sucking, but instead I'm going to do something useful and informative. Describe how this blog works.

When I started this blog, I had only used ASP.Net with WebForms. Even though I didn't know anyway to do it better, I could see the shortcomings of pretending HTTP is a stateful protocol. This is because before ASP.Net, I used PHP, which actually taught me quite a bit about the HTTP protocol. I've since abandoned the PHP ship due to stuff like this, but that's another story.

I believe at the time ASP.Net MVC was still pre-release or still filled with bugs, especially for Mono. I was set on using Linode for my hosting because I've never had any problems with them, and I prefer managing Linux servers to Windows... So I had a choice, Use WebForms, or use a different framework. I really like C# though, so I instead created a very minimalistic framework.

This framework eventually got broken up into 3 different pieces of software.

  1. EView -- a powerful, yet simple, static view generator written completely in T4
  2. EFramework -- a routing engine and basic replacement for using ASP.Net MVC or WebForms
  3. FSCAuth --a very easy to use Authentication library

At first, these were all one framework named EFramework, but I since broke them up since they are all useful by themselves as well.

So first up, I'll explain how EView generates everything you see here.

EView is just a single T4 file. I would've broken it up into separate files but at the time Mono had a bug that prevented me from doing it easily. How it works basically is you place EView into a directory, and when you run the T4 file template, it'll read all the files from the current directory and generate a C# file.

So, let's do a simple example. You have a folder named Templates and 3 files, EView.tt (the T4 file), IndexView.html, EntryView.html

So, you run EView.tt. Visual Studio (or MonoDevelop or whatever) runs the T4 template during design time. EView will scan the folder it's placed in. It finds IndexView.html and EntryView.html

Let's say IndexView.html contains this:

<!DOCTYPE HTML>
{@
Entry as IEView;
@}
<html>
<head>
    <title>Test Index</title>
</head>
<body>
  <div class="entry">
    {=Entry=}
  </div>
</body>
</html>

Now let's explain. What's that weird {@ thing at line 2? This is an EView directive. EView has blocks like { } The symbol after the { and before the } indicates what the directive is. The @ directive is for variable declarations. So basically, in the view class it generates, it'll add the public variable Entry of type IEView to it. IEView is the interface that all EView views implement. After that it looks like plain HTML until {=Entry=}. The {= directive tells EView "put this variable's value here. So it wants to put theEntryvariable there. EView is smart enough and knows that Entry is a View, so it callsEViewRender at that point.

So basically, this all generates a view class like this: (more complicated than this, but to be easy to understand, this example is simpler)

  public class IndexView : IEview
  {
    public IEView Entry;
    public string EViewRender(){
      StringBuilder sb=new StringBuilder();
      sb.Append(@"""<!DOCTYPE HTML>
      <html>
    <head>
        <title>Test Index</title>
    </head>
    <body>
      <div class=""entry"">
     """);
     //insert {=Entry=}
     sb.Append(Entry.EViewRender); //a lot more logic goes into this, but just for example sake
     sb.Append(@"""
      </div>
    </body>
      </html>
      ");
      return sb.ToString();
    }
  }

And that's basically how it works. So when you want to use IndexView, you just do this:

var index=new IndexView();
index.Entry=MyEntryView; 
string html=index.EViewRender();

So as you can see, it's very powerful really, while views are really simple to create. No weird XML tags to use, or other crao. You just write your HTML and use a { } blocks to describe how the view should be dynamic. It supports many more operations than shown here such as if statements, foreach statements, and a special Layout directive that works similar to ASP.Net's MasterPages.

So now you understand a bit of what my views look like and how they're rendered, we can move on to EFramework.

EFramework was really an experiment to see if what I was trying to do was even possible.. and then I thought it worked really well, so I kept at it.

What exactly does EFramework provides? It provides 2 things:

  1. A powerful routing engine
  2. An HttpHandler class that functions similar to ASP.Net's .ashx Handlers.

So let's start with the routing engine. The routing engine has 3 pattern matching engines

  1. Regex for regular expressions
  2. Plain for exact plain text matches
  3. Simple, used for my own pattern matching that resembles ASP.Net MVC's routing URLs

I'll only cover the Simple type of pattern matching.

Here is the Routing record for the pages of my Blog:

Routing.AddRoute("paged_index",PatternTypes.Simple,"/blog/{page}",()=>{return new BlogHandler();});
  1. "paged_index" is the name of the route, which is passed to the HttpHandler that handles the route.
  2. PatternTypes.Simple is which type of pattern matching
  3. "/blog/{page}" is the pattern to match. It basically says match anything that looks like /blog/* where * is anything, and put the wildcard value into the page route variable
  4. This is a lambda for creating a new HttpHandler. So basically, when a route matches, it will call on this Lambda and expects for the lambda to return an HttpHandler of some sort

The Simple pattern matching is also quite powerful and can do more than simple wildcards. It's aware of how / affects URLs and can do the following:

  1. "/blog/{entryid}/comment/add" This will match anything like /blog/*/comment/add where * can be basically anything but / and when it reaches a / it knows the wildcard is over
  2. "/test3/{id}/{action=[view,edit]}/{*}" This one is a bit more complex. The first {id} of course matches anything up til the next /. {action=[view,edit]} indicates that this wildcard can only be view or edit. If it is not one of those two, the route doesn't match to the URL. And finally, the {*} adds support for "descriptive" URLs, such as from StackOverflow: http://stackoverflow.com/questions/2185781/why-cant-c-sharp-compiler-follow-all-code-paths-through-a-switch-statement

That's about as complex as it gets right now. I'm sure there are some URLs it can't accept, but it works for most URLs I've seen.

Now then, how does an HttpHandler work exactly?

public class BlogHandler : HttpHandler
{
  public override void Get ()
  {
    if(RouteID=="page"){
      var v=new IndexView();
      v.Entry=GetEntry(RouteParams["id"]);
      Write(v.EViewRender());
      return;   
  }
}

Pretty simple. The Get function is called when the HTTP method is GET, Post for POST and etc. RouteParams is a dictionary with all of the route variables in it. So in this case, the route probably looks like

Routing.AddRoute("page",PatternTypes.Simple,"/blog/{id}",()=>{return new BlogHandler();});

And that's for the most part, what makes EFramework good enough for me.

And then there is FSCAuth, what prevents all of you from posting articles on my blog.

FSCAuth was made because I think ASP.Net's authentication mechanisms are a bit too complex for simple authentication scenarios, such as this blog. And FSCAuth is so easy to use and so secure(by default) that I decided to try to sell it. So how does FSCAuth tie into this site?

Well, I use MongoDB for my database (also mainly for learning something new :) ). FSCAuth works by having a UserStore class and a few configuration options set in Global.asax

First the easy part. My configuration looks like this:

        Authentication.UserStore=new MongoUserStore(); //set the UserStore to be used
        Authentication.SiteName="Last Year's Wishes"; //put into login cookies
        Authentication.LoginPage="/login"; //where to redirect to when authentication is required
        Authentication.UniqueHash=Config.UniqueHash; //our secret hash
        Authentication.CookieHttpOnly=true; //Set HttpOnly property on cookies
        Authentication.CookieSecure=false; //set to choose to only transport cookies over HTTPS 
        Authentication.CookieUseBase=true; //hash the physical path of the application into cookies. 
        Authentication.CookieUseBrowserInfo=false; //hash in client's browser information to the cookies. 
        Authentication.CookieUseIP=true; //Hash in the client's IP address to the cookie. Ties the cookie to the client's IP 

Now the last configuration thing to do is create our UserStore. This is a direct rip of mine (to show how easy it is to create one)

public class MongoUserData : UserData{
    [BsonId]
    public ObjectId ID{get;set;}
}

public class MongoUserStore : IUserStore
{
    public MongoUserStore ()
    {
    }
    public UserData GetUserByName (string username)
    {
        var db=Config.GetDB(); //gets our MongoDB database instance
        var u=db.GetCollection<MongoUserData>("users").FindOneAs<MongoUserData>(Query.EQ("Username",username));
        return u;
    }

    public bool UpdateUserByID(UserData user)
    {
        var userdata=(MongoUserData)user;
        var db=Config.GetDB();
        var u=db.GetCollection<MongoUserData>("users");
        if(u.Save<MongoUserData>(userdata)==null){
            return false;
        }else{
            return true;
        }
    }

    public bool AddUser (UserData user)
    {
        var userdata=(MongoUserData)user;
        var db=Config.GetDB();
        var u=db.GetCollection<MongoUserData>("users");
        if(u.Find(Query.EQ("Username",userdata.Username)).Count()!=0){
            return false;
        }
        u.Insert<MongoUserData>(userdata);
        userdata.UniqueID=userdata.ID.ToString();
        u.Save<MongoUserData>(userdata);
        return userdata!=null;
    }
    public bool DeleteUserByID(UserData user){
        var userdata=(MongoUserData)user;
        var db=Config.GetDB();
        var u=db.GetCollection<MongoUserData>("users");
        u.Remove(Query.EQ("_id",userdata.ID));
        return true;
    }
}

As you can see, it's not too complex to create... unlike a similar thing for tying ASP.Net's Forms Authentication to MongoDB.. I know it can't be done in only 58 lines of code anyway.

So then, how to actually make use of FSCAuth, all I do is call Authentication.RequiresLogin() or one of the other Authentication functions.

This is my actual Post method for my BlogHandler:

    public override void Post(){
        if(RouteID=="new"){
            Authentication.RequiresLogin();
            var c=Config.GetDB().GetCollection<BlogEntryData>("entries");
            var entry=new BlogEntryData{
                Title=Form["Title"],
                Text=Form["Text"],
                Publish=Form["Publish"]!=null,
                Posted=DateTime.Now,
                Tags=new List<string>(Form["Tags"].Split(new char[]{' '},StringSplitOptions.RemoveEmptyEntries))
            };
            c.Save<BlogEntryData>(entry);
            Response.Redirect("/blog/view/"+entry.ID.ToString());
        }
        if(RouteID=="edit"){
            Authentication.RequiresLogin();
            var c=Config.GetDB().GetCollection<BlogEntryData>("entries");
            var entry=c.FindOneByIdAs<BlogEntryData>(new ObjectId(RouteParams["id"]));
            entry.Title=Form["Title"];
            entry.Text=Form["Text"];
            bool p=Form["Publish"]!=null;
            if(!entry.Publish && p){ //if not previously published, update the time so it's "bumped"
                entry.Posted=DateTime.Now;
            }
            entry.Publish=p;
            entry.Edited=DateTime.Now;
            entry.Tags=new List<string>(Form["Tags"].Split(new char[]{' '},StringSplitOptions.RemoveEmptyEntries));
            c.Save<BlogEntryData>(entry);
            Response.Redirect("/blog/view/"+entry.ID.ToString());
        }
    }

And that's all I have to do. Super simple.

Even though I created this framework as a workaround because MVC at the time was very experimental with Mono, I found that I rather like my framework. I can see quite a few limitations, but it works wonderfully for small scale websites. I also have not measured the performance of any of this except for FSCAuth, so I'm sure it's not enterprise ready, but for this low-traffic blog, it works really well. And I learned a lot about ASP.Net in building it.

Anyway, hope you enjoyed reading how this site works and maybe are curious about one of my 3 projects. In case you are, here are the links to the projects again:

  1. EView -- a powerful, yet simple, static view generator written completely in T4
  2. EFramework -- a routing engine and basic replacement for using ASP.Net MVC or WebForms
  3. FSCAuth --a very easy to use Authentication library
Posted: 5/3/2012 8:16:16 PM

New FSCAuth Licensing

Because of lack of demand for my beta testing of FSCAuth I've decided that I probably need to go a different approach. No one wants to blindly trust an authentication library, so I'm going to dual license it under GPL and commercial. This way everyone can see the source code and know that it's trust worthy, but also can pay for a (pretty cheap) commercial license to prevent people from having to open source their entire project.

I'll have source code uploaded in the next few days with the GPL license attached, so be on the look out for it.

Posted: 7/29/2011 4:11:55 AM

FSCAuth Beta is now open!

I uploaded the doxygen documentation for FSCAuth 1.1-beta and now I'm saying I'm open for beta testing. The new documentation is here.

If you are interested in beta testing a new and intuitive authentication library for ASP.Net, please fill out this form detailing your beta request. The beta will end sometime in September 2011 (updated). All beta assemblies will work until December 1st, 2011. If you do get selected to beta test, then there are benefits in it for you! All beta testers that give me back feedback will receive a free 2 server license of the released version of FSCAuth. Note that all paid licenses include source code.

For more information about FSCAuth, please see these links:

Posted: 7/25/2011 2:48:37 AM

FSCAuth 1.1 FAQ

FAQ

Q: I have to use a legacy database.Can I still take advantage of FSCAuth?

A: YES! FSCAuth was designed to work just as well with a database not explicitly designed for it. The only constraint is that there must be a UniqueID that will fit into a string for each user. Because you have control over each field in UserData you can also override things and make it so that FSCAuth will work across a plain text database. However, I don't recommend it and instead recommend you just reset all of your passwords and add a Salt column to your database if one doesn't exist yet.

Q: What if a user needs to recover their password?

A: The only way to recover a password is to store it in plain text or encrypted. As such, this is not supported for this library. I recommend instead generating a random password and sending this to the user instead so that they can reset their password to what they wish.

Q: How do I change the Hashing Algorithm? Why do I need a delegate?

A: To give you full control over how hashes are created and to accomodate "tracked" salts as used in BCrypt, you must create a new function and assign it to HasherInvoker. This is the default hasher:

        static HashWithSalt DefaultHasher(string plain, string salt)

    {
        var v=new HashWithSalt();
        if(salt==null){
            v.Salt=HashHelper.GetSalt(SaltLength);
        }else{
            v.Salt=salt;
        }
        HashAlgorithm hash;
        if(SupportsUnmanagedCrypto){
            hash=new SHA256CryptoServiceProvider();
        }else{
            hash=new SHA256Managed();
        }
        v.Text=HashHelper.FromBytes(hash.ComputeHash(HashHelper.ToBytes(plain+v.Salt)));
        return v;
    }

With this, it should be simple to implement any hashing algorithm.

Q: What if I want the UniqueHash stored in my web.config?

A: Don't fill in Authentication.UniqueHash in the code, and the FSCAuth library will look in your web.config under appSettings. For instance, if this is in web.config, it will use myhash as the value of UniqueHash:

<appSettings>
  <add name="FSCAuth_UniqueHash" value="myhash" />
</appSettings>

Note: This doesn't work under most Medium Trust installations, so for medium trust you must populate it in code.

Q: How do I use HTTP Basic Authentication?

A: If you only want it for one page and not every page, then in the Page_Load(or similar) just use Authentication.RequiresLogin(true);. The true option means to use Basic Auth. If no one is logged in, then at this line it will send the HTTP 401 Authentication Required erorr code.

If, however, you prefer to use HTTP Basic Auth "by-default", then use Authentication.UseBasicAuthByDefault=true;. This will make it so Authentication.RequiresLogin(); will use Basic Auth. To use cookie based authentication instead somewhere else, you can use Authentication.RequiresLogin(false);

Q: Can I have more than the fields you provide for UserData or GroupData?

A: Yes! All that must be done is descend from the class and use your new class in the UserStore. Note: Not every UserStore will require modifications to persist the extra fields, however, the provided SQL Server UserStore will require modifications to persist the extra fields.

Q: How can I protect static files?

A: Right now, it's not the easiest thing in the world. I plan to make this easier in the next release, but here is how to work around it: Assuming IIS 7 or Apache(or some other server which always calls Global.asax even for static file requests), you must match the requested path against a regular expression or similar:

protected virtual void Application_BeginRequest (Object sender, EventArgs e){
  if (Context.Request.Path.ToLower() == "/private")
  {
    Authentication.RequiresLogin();
  }
}

Q: What if I want to change my hashing algorithm or UniqueHash?

A: Currently, it's not possible without resetting every user's password. However, there is a small bit of a workaround. You can modify things so that when a user logs in, since you know their password because it solved the old hash, you can set the new hash using the password they just logged in with. FSCAuth doesn't have this functionality built in however.

Q: Can I use bcrypt as my hashing algorithm?

A: Yes! Just follow these instructions

Q: Why don't you provide more UserStores?

A: I don't plan on providing any more past Memory, SQL Server, and MongoDB. The reason is because UserStores usually have to be implemented from scratch regardless to fit with how your website is designed. I don't want to provide code that has no use.

Q: Why are people logged into my site using Basic Authentication?

A: Basically, whenever a user logs in initially through Basic Authentication, the web browser sends the user credentials on every page requested. If you have HttpRealm set, then this enables code that will check the credentials the browser sends and log in the user if it's the correct login information. It is not possible to limit some pages to only cookies and some pages to only Basic authentication within the same website. (note: if you send RequiresLogin(false) then you won't get an HTTP Basic login prompt, but if you were to force your web browser to send the Basic Authentication headers, they'd log you in)

Q: Why can't I log out a user that logged in using Basic Authentication?

A: This is by fault of the HTTP standard. Basically, there is no way for your server to send a message to the user's web browser that it should stop sending credentials. You can see this Stackoverflow question for a work around though.

Q: Can I limit how many times a user can try to login?

A: Not at the moment. I've been working on an extension which will only let users attempt to login a set amount of times with it getting exponentially slower with each failed attempt, but currently it's not ready for production use. I plan on putting it in with the next release however.

Q: Do I need to transport everything over HTTPS?

A: Short answer; Probably. Unless you are using throw away credentials that don't actually matter, then use HTTPS at least when you POST back to login. If you use HTTP Basic authentication, you NEED to use HTTPS for everything because the username and password is sent over in clear text for every single request. It's not technically required, but it's defeating all of your security if you don't use HTTPS where user credentials are sent.

Q: Where format does the UniqueHash property have to be in?

A: It doesn't matter. Just make sure it's long and no one can guess it. I personally prefer to use Random.org to generate me a random string.

Q: Can I cache items in the UserStore?

A: You must be careful about it, but you can. The only concern you should have is that if you delete a user or change their information(especially password hash and/or salt), then you must be sure to flush the cache for that user. This is extremely simple to do in a single server setup, but more care must be taken in a multi-server setup.

Q: I have an odd condition where I need to put in a user not authorized message. How do I do that?

A: Just do throw new HttpException(403, "Forbidden"); For instance, to block a certain user from a page

if(CurrentUser.Username=="Bobby Drop Tables"){
  throw new HttpException(403, "Forbidden");
}

Note: If you have a "catch-all" type error page, be sure that you set it up both as the 500(catch all), 403, and 401 error pages in the web.config, otherwise, you'll have the problem described below.

Q: What does the CustomErrorsFixer class do?

A: This class will fix a bug in some configurations of ASP.Net/mono in which instead of showing the current error page with the correct HTTP status code, instead it will do an HTTP redirect to the error page. This causes a massive amount of SEO problems including search engines indexing your login page and other pages that shouldn't be indexed. Pages which aren't anonymously accessible will be indexed in the search engine as either your login page or your 403 Forbidden page. Due to problems in medium trust, this class won't work within a medium trust environment. You should setup a robots.txt file to mitigate this problem in medium trust.

Posted: 7/25/2011 2:43:57 AM

FSCAuth 1.1 Introduction

What is FSCAuth?

FSCAuth is short of Fast, Secure, and Concise Authentication. It's designed to be a flexible replacement for ASP.Net Forms Authentication. It is designed around a very minimalistic interface to your database, IUserStore.

Why use FSCAuth?

The main reason I use FSCAuth is that it saves me time and I don't have to use as much code to describe how I want it to react. I created it initially because ASP.Net Forms Authentication required too much work for a trivial login system. Everyone has basically said there are only two options for authentication, ASP.Net Forms Auth or roll your own. Well, I've rolled my own so that people have a third option now.

How does it save time?

FSCAuth is very straight forward to use. Just glancing over the Intellisense documentation is generally enough to get started. For setup only 2 fields must be populated in Global.asax and a UserStore must be implemented(which is only 4 easy functions). After that, you're ready to show off awesome code like this:

protected void Page_Load(object sender, EventArgs e){ //the load event for my secret page
  //Some secret stuff you don't want to show to people
  Authentication.RequiresInGroup("secret"); //will throw an HTTP 403 error if they are not in the group and redirect them to your 403 error page.
}

or even

protected void Page_Load(object sender, EventArgs e){
  if(Authentication.IsAuthenticated){
    AuthenticatedPanel.Visible=false;
  }else{
    AuthenticatedPanel.Visible=true;
  }
}

On top of this easy, but fine grained authorization, you also NEVER have to worry about handling cookies or HTTP Basic Auth yourself. The only thing that developers using FSCAuth have to worry about is the UserStore.

Is it secure?

Right from the beginning Fast, Secure, and Concise Authentication was designed to be fool proof for security. I never make you implement any low level details of the authentication. This makes it so that there is much less risk in extending your authentication system. It was designed to be secure enough that even if a dump of the database behind it got leaked, your user's credentials would be safe, and hackers would still not be capable of logging in. All passwords are hashed and salted. All login cookies are practically impossible to forge with today's hardware.

Don't take my word for it though; check out the source code. With every paid license full source code is included. The source code is not overly complex and at the core is only a few hundred lines including comments. If you look at it and think I did a horrible job, then return it. Binpress offers a 14 day money back guarantee.

Is it fast?

Speed is the wrong word to use for an authentication framework. I prefer efficiency. One of FSCAuth's best points is that only 1 database hit is required for everything except for creating a user. It can actually be made to not require a database hit depending on how the UserStore is implemented. FSCAuth plays nice with caching.

By default, FSCAuth uses SHA256 for hashing, which is the most common hashing algorithm for passwords right now. If you prefer a slower hashing method(for security) you can either change algorithms to any hash algorithm that implements System.Security.Cryptography.HashAlgorithm, or you can change the number of iterations the hash algorithm is used (default is 1).

Also, there is no need for a persistence of session state. So no extra memory used on your servers, nor messy tables in your database. This is a "stateless" authentication system.

What's capable?

This library is capable, of course, of adding and authenticating users. It also includes simple one-line checks for operations such as checking if a user is logged in, and checking if they are enrolled in a group. Also included in the latest release is the ability to use HTTP Basic Authentication just as simply as you'd use cookie based authentication.

Limitations

Well, I have to tell you, FSCAuth isn't perfect, but it's pretty close to the needs I've seen. Currently, FSCAuth lacks quite a few features supplied by ASP.Net Forms Authentication. Some of this is by design and some of it will be implemented in a later release. Anyway, FSCAuth doesn't implement any of the following: Emailing a user their password, a ready-made user registration wizard, controlling authorization with attributes on functions and classes, password strength requirements, Windows/Passport authentication, Role/Task/Group multilevel support (there is only groups), and probably quite a bit more. Most of the lacking features are by design. I've never seen the built-in registration wizard used on an ASP.Net site in the wild; so I won't impelment something that most people want to create themselves anyway. Rather, this project is designed to be used where Forms Authentication doesn't work well. This means that using something other than GUIDs is easy, Implementing a custom user database(or using an existing database) is straight forward, and tying it to your database can be done in less than 200 lines of code in most cases(SQL Server UserStore is 171 lines). Keep in mind also though, that FSCAuth can be used as a base for creating your own custom authentication system. The source code is provided with every paid license.

What's included?

  1. The main authentication module(source code and assembly)
  2. Generic in-memory list UserStore implementation
  3. SQL Server UserStore
  4. ASP.Net Login custom control
  5. ASP.Net Logout custom control
  6. ASP.Net example web application

Note: In the demo version, source code for FSCAuth and FSCAuth.Extensions is not included. FSCAuth.Example source code is provided.

Compatability

  • Framework versions: Mono 2.0 or greater(possibly works with earlier verions), .Net 2.0 and greater(below 2.0 must degrade to Managed SHA256)
  • Windows OS support: Windows XP(1), Server 2003, Server 2008, Vista, and 7. (32 and 64 bit)
  • *nix OS support: Linux, OpenBSD (should work in other OSs as well with mono)
  • Servers: mono-xsp, Cassini, IIS6(2), IIS7, IIS7.5, Apache with mod_mono
  • Comes with example UserStores for SQL Server and MongoDB. They are easy to adapt to custom needs.
  • Runs within Medium Trust(3)
  • Works equally well for both Webforms and ASP.Net MVC
  • Runs without modifications in a web cluster(no secret caching is done behind the scenes)

Notes: 1. Using Windows XP, you must degrade to the Managed SHA256 implementation due to lack of OS support 2. Using IIS6, I have not yet found a way to protect static files 3. In medium trust, CustomErrorsFixer does not work, which fixes error pages to return the proper HTTP status code. AuthPage must be populated with the 401 error page if using HTTP Basic Auth in Medium Trust.

More documentation:

You can also stay up to date by following the FSCAuth tag on my blog

Support

If you need to report a bug, feature request, or any other support related to FSCAuth, please use This Form.

Posted: 7/25/2011 2:43:16 AM

BCrypt Support in FSCAuth

I've had to revamp quite a few things in this next release of FSCAuth to accommodate BCrypt, but overall, I think it makes things cleaner and flexible anyway.

So if you want to use BCrypt, it's pretty easy to do now. First off, a new change is that HasherInvoker is now a delegate used for computing hashes and it is now capable of "keeping track" of salts, such as is required for BCrypt. You could do it probably without converting BCrypt to a HashAlgorithm, but it's how I did it... so..

First, get the HashAlogrithm interface for BCrypt.Net: GalacticJello at StackOverflow kindly provided it for me.

Next, you just need a new HasherInvoker function for FSCAuth:

    static HashWithSalt BCryptHashHander(string plain, string salt)
    {
        var v=new HashWithSalt();
        BCryptHasher hash=new BCryptHasher();
        if(salt==null){
            v.Text=HashHelper.FromBytes(hash.ComputeHash(HashHelper.ToBytes(plain)));
            v.Salt=hash.Salt;
        }else{
            hash.Salt=salt;
            v.Text=HashHelper.FromBytes(hash.ComputeHash(HashHelper.ToBytes(plain)));
        }
        return v;
    }

Fairly simple at least. And then just assign it to Authentication:

Authentication.HasherInvoker = BCryptHashHander;

You could extend on this to put in a WorkFactor and other such things pretty easily as well. The only possible thing in FSCAuth to break it might be HashIterations being more than one. But for BCrypt, you should really only have this at 1 and increase the WorkFactor if you need it slower

BSD Licensed code :)

Copyright (c) 2011 Jordan "Earlz/hckr83" Earls http://lastyearswishes.com All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
  3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Posted: 7/24/2011 7:50:53 PM

SQL Server User Store for FSCAuth

Well, I finally got around to writing a UserStore utilizing SQL Server. I did it without an ORM.. which is annoying at best, but regardless, I'd say it gets the job done and is sorta easy to extend for your own UserData class. SQL doesn't play well with inheritance though. It's quite difficult to write an easy to extend data class without using an ORM

Anyway, here is the code:

namespace Earlz.FSCAuth.Extensions
{
    public class SqlServerUserStore : IUserStore
    {
        private static SqlServerUserStore instance;
        private SqlServerUserStore() {}

        public static SqlServerUserStore Instance
        {
          get 
          {
             if (instance == null)
             {
                instance = new SqlServerUserStore();
             }
             return instance;
          }
        }
        /// <summary>
        /// Will get a user by username or email address
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        virtual public UserData GetUserByName(string name)
        {
            using (var con=GetConnection())
            {
                con.Open();
                var cmd = new SqlCommand();
                cmd.CommandText="select * from users left join Groups on"+
                    " groups.UserID=users.ID where username=@name or "+
                    " emailaddress=@name";
                cmd.Connection = con;
                cmd.Parameters.Add(new SqlParameter("@name", name));
                return ReaderToUserData(cmd.ExecuteReader());
            }
        }

        virtual public bool UpdateUserByID(UserData user)
        {
            var old = GetUserByName(user.Username);
            if (old == null)
            {
                return false;
            }
            using (var con = GetConnection())
            {
                con.Open();
                var cmd = new SqlCommand();
                cmd.Connection = con;
                cmd.CommandText="";
                if (old.Groups.TrueForAll(x => user.Groups.Contains(x)))
                {
                    //update groups as well.
                    //we're just going to do it messy and drop all groups
                    //and add them back in.
                    cmd.CommandText = "delete from groups where userid=@userid;";
                    foreach (var g in user.Groups)
                    {
                        cmd.CommandText += "insert into groups (name,userid)"+
                        " values('" + g.Replace("'", "''") + "',@userid);";
                    }
                }
                cmd.CommandText += "update users set emailaddress=@email, username=@username,"+
                " salt=@salt, passwordhash=@password ";
                cmd.CommandText += "where id=@userid;";
                cmd.Parameters.Add(new SqlParameter("@userid", user.UniqueID));
                cmd.Parameters.Add(new SqlParameter("@email",user.EmailAddress ?? ""));
                cmd.Parameters.Add(new SqlParameter("@username",user.Username));
                cmd.Parameters.Add(new SqlParameter("@salt",user.Salt));
                cmd.Parameters.Add(new SqlParameter("@password",user.PasswordHash));
                if (cmd.ExecuteNonQuery() > 0)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }

        }

        virtual public bool AddUser(UserData user)
        {
            using(var con=GetConnection())
            {
                con.Open();
                var cmd = new SqlCommand();
                cmd.Connection = con;
                cmd.CommandText = "declare @userid as integer;";
                cmd.CommandText += "insert into users (username,emailaddress,passwordhash,salt)"+
                " values (@username,@email,@password,@salt);";
                cmd.CommandText += "set @userid=scope_identity();";
                foreach (var g in user.Groups)
                {
                    cmd.CommandText += "insert into groups (name,userid) values "+
                    "('" + g.Replace("'", "''") + "',@userid);";
                }
                cmd.CommandText += "select @userid;";
                cmd.Parameters.Add(new SqlParameter("@email",user.EmailAddress ?? ""));
                cmd.Parameters.Add(new SqlParameter("@username",user.Username));
                cmd.Parameters.Add(new SqlParameter("@salt",user.Salt ?? ""));
                cmd.Parameters.Add(new SqlParameter("@password", user.PasswordHash ?? ""));
                try
                {
                    user.UniqueID = ((int)cmd.ExecuteScalar()).ToString();
                    return true;
                }
                catch
                {
                    return false;
                }
            }
        }

        virtual public bool DeleteUserByID(UserData user)
        {
            using (var con = GetConnection())
            {
                con.Open();
                var cmd = new SqlCommand();
                cmd.CommandText = "delete from groups where userid=@userid; delete from users where id=@userid";
                cmd.Connection = con;
                cmd.Parameters.Add(new SqlParameter("@userid", int.Parse(user.UniqueID)));
                if (cmd.ExecuteNonQuery() > 0)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
        }
        virtual protected UserData ReaderToUserData(IDataReader rdr)
        {
            if (!rdr.Read())
            {
                return null;
            }
            var u = new UserData();
            u.EmailAddress = rdr["EmailAddress"] as string;
            u.PasswordHash = rdr["PasswordHash"] as string;
            u.Salt = rdr["Salt"] as string;
            u.UniqueID = ((int)rdr[0]).ToString(); //user.id
            u.Username = rdr["Username"] as string;
            u.Groups=new List<string>();
            if (rdr["Name"] != null && !(rdr["Name"] is DBNull))
            {
                u.Groups.Add(rdr["Name"] as string);
                while(rdr.Read()){
                    u.Groups.Add(rdr["Name"] as string);
                }
            }
            rdr.Close();
            return u;

        }
        protected virtual SqlConnection GetConnection()
        {
            string s=ConfigurationManager.ConnectionStrings["fscauth"].ConnectionString;
            var con = new SqlConnection(s);
            return con;
        }
    }
}

Also, BSD licensed again:

Copyright (c) 2011 Jordan "Earlz/hckr83" Earls http://lastyearswishes.com All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
  3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Posted: 6/12/2011 9:57:37 PM

Woo Academic Licenses

So because I'm sort of a student, I get free academic licenses for a shit ton of Microsoft products. For instance, thanks to academia, I can easily test FSCAuth against IIS 6 and 7. So yea, I'm doing quite a bit more testing with FSCAuth than the last release. That's why it's coming out a bit slow.

Anyway, I have to get back to installing Windows Server 2008 Enterprise edition(x86)

Posted: 6/11/2011 11:32:58 PM

FSCAuth Changes

Ok so here is basically a change list for FSCAuth 1.1

  • A random salt was added to each UserData
  • That salt is used in hashing passwords and login cookies
  • Added support for HTTP Basic Authentication. It was actually slightly redesigned to work with Basic and Forms authentication at the same time
  • Added password reset function
  • Added short cut methods to UserData. So now there is Update(), SaveNew(), Delete(), and Reset Password().
  • Renamed all methods in IUserStore. Now have UpdateById and GetUserByName.
  • Added DeleteUserId to IUserStore
  • Revamping to make Authentication.Authenticate useless now. All user data is now loaded in a lazy fashion. When you make a reference to CurrentUser it will load it.

Basically, these changes are mostly features and refactoring to make it more useful to it's audience.

I'll also be adding an external git repository to hold example websites and example UserStores, all BSD licensed.

I plan to push out 1.1 in the first week of June. I will probably try to market this out to people and get some information about it out there. This is because I wasn't for sure how 1.0 would be received. So I've added features and made it somehow even easier to code against. I've also added the HTTP Basic Authentication method with web services in mind who think that WCF is too complicated for such a simple task.

Posted: 5/28/2011 5:39:50 AM