Brute-force Protection... hmmm

Well, I'm designing how to implement brute-force protection in FSCAuth, and it's proven basically impossible to do in a stateless manner. So, I'm thinking of using the Cache to keep track of IPs and keep a count of auth attempts.

Regardless, this isn't exactly the fun thing I wanted to implement, but it has to be done, so might as well do it before releasing v1.1.

Curse you, internet.

Tags: fscauth
Posted: 5/12/2011 4:24:12 PM

FSCAuth progress

I've been working on FSCAuth some recently. Two new things is password reset. Now, a password can be reset simply by doing

string new_password=Authentication.ResetPassword("username");

Then, you can mail their new password to them, write it on a sticky note, or attempt to pronounce it. I leave the choice up to you!

Now, the much more awesome thing is I implemented an HTTP Basic Authentication option. So now, instead of requiring your programs communicating with your web-service to parse and populate some login form.. or use some other obscure GET form of authentication(shudder)... you can now use Basic Authentication! It's extremely easy to enable and it's possible(even designed) to use both standard form/cookie authentication and Basic authentication. An example from my newly updated example site:

        //in a .ashx handler:
        Authentication.RequiresLogin(true);
        context.Response.ContentType="text/plain";
        context.Response.Write("successfully logged in as "+Authentication.CurrentUser.Username+" with ashx handler");

Simple as pie. And then, in the configuration section you just have to set what you want your authentication realm to be. So basically, if you use RequiresLogin(false) it won't send the WWW-Authenticate headers causing the login popup to appear. If you use true instead, it will, and you'll login instead via Basic authentication.

What if you like Basic Authentication, even though it has mostly vanished from the dot-com jungles? Well, I support this behavior as well. I've added a new configuration option: UseBasicAuthByDefault. Set to true, and all naked(no argument) RequiresLogin() will basically act like RequiresLogin(true). Simple right?

I'm planning to release 1.1 in a few more weeks. Hopefully I'll get around to implementing more of those "must have" features in my FogBugz sooner than later.

Posted: 5/12/2011 2:21:34 AM

FSCAuth Released!

Well, I finally got FSCAuth out the door. The licensing options are pretty fair with licensing starting at $30 and with a free demo version which is only capable of connecting from localhost.

You can checkout more and buy it at Binpress

Tags: fscauth yay
Posted: 4/26/2011 12:40:43 AM

Tags are a go

Tags finally work. Well, I mean the /tags/{tag}/{page} mechanism. The tag index is still blank, but you can now click the tags below this post and see everything tagged meta and what not.

Also, updated to using FSCAuth as the authentication here. It was using it before, but it use to be part of "EFramework". As such, that version was a bit cumbersome in some areas, though the core authentication algorithms haven't changed since I first wrote it.

Also, you may see "work in progress" posts. I'll work on an extra option to hide those, but basically you'll be able to see both blog entries and pages by looking through tags

Posted: 4/23/2011 4:54:32 PM

An Example UserStore

This is the real-life example of an implementation of FSCAuth. This is the MongoDB UserStore used by this blog.

Basically, a fully functional UserStore can be created in about 50 lines. This particular example uses MongoDB. It also overrides UserData to add the MongoDB BsonId attribute. This isn't really required, but it makes things a lot easier.

And here is the code:

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

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

    public bool UpdateUser (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 true;
    }
    public bool DeleteUser(UserData user){
        var userdata=(MongoUserData)user;
        var db=Config.GetDB();
        var u=db.GetCollection<MongoUserData>("users");
        return u.Remove(Query.EQ("ID",userdata.ID)).Ok;
    }
}

This code is BSD licensed:

Copyright (c) 2010 - 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: 4/23/2011 4:34:08 AM

Semi-Formal specification of FSCAuth

Here I'm going to describe in a semi-formal manner of how FSCAuth works. Hopefully, emphasizing the algorithm, rather than the implementation.

A few definitions:

  • Hash(x): The Hasher function. By default this creates SHA256 hash in FSCAuth
  • UserID: The UserData.UniqueID string
  • Username: The user name of the user
  • PasswordHash: The created password hash
  • Password: the plaintext password
  • UniqueHash: The UniqueHash value for the entire site
  • Base: the base path of the website. If CookieUseBase is false, then this is an empty string.
  • IP: The IP address of the client. If CookieUseIP is false, then this is an empty string.
  • UserAgent: The user agent string from the client's browser. If CookieUseBrowserInfo is false, then this is an empty string.
  • Now(): The current time as a 32bit UTC Unix timestamp
  • + is for string concatenation. "abc"+"xyz" is equal to "abcxzc"

To create a new user, the only thing we must create the password hash. A password hash is constructed as follows

PasswordHash := Hash("fscauth" + Password + Username + UniqueID + UniqueHash);

Then, when creating a new authentication cookie, we populate two fields. Expires, Secret, and Name. The cookie name itself is created by Hash(SiteName) + "_login" or SiteName+"_login" depending upon the HashCookieName variable. The Name variable is just the plaintext username of the user to be authenticated. The Expires variable is a UTC Unix timestamp of when the cookie should expire.

Secret :=  Hash(PasswordHash + IP + Base + UserAgent + Expires + UniqueHash + SiteName)

By incorporating UniqueHash into the cookies and password hashes, this makes a site extremely secure, as long as UniqueHash is kept secret. The contents of this variable can be kept either in the Web.config or the C# source code.

As well as this, a unique per-user Salt is used for each hash(user determined as to how the salt is put into the hash)

Tags: fscauth
Posted: 4/23/2011 4:28:58 AM

Demonstration of FSCAuth

Hello, here I'm going to show you how simple FSCAuth is to use. I'm going to basically walk through my demo over at http://fscauth-demo.lastyearswishes.com

What was my first step? Well, first we need to get our UserStore up and running. I used the built in MemoryUserStore. It's not hard to implement your own even if it wasn't built in. The simple source code is this:

/// <summary>
/// This is a user store utilizing a simple in memory list of users. Note, this is a singleton
/// </summary>
public class MemoryUserStore : IUserStore
{
    private static MemoryUserStore instance;
    private MemoryUserStore() {}

    public static MemoryUserStore Instance
    {
      get 
      {
         if (instance == null)
         {
            instance = new MemoryUserStore();
         }
         return instance;
      }
    }

    List<UserData> users;
    public List<UserData> Users{
        get{
            if(users==null){
                users=new List<UserData>();
            }
            return users;
        }protected set{
            users=value;
        }
    }

    public bool AddUser (UserData user)
    {
        foreach(var u in Users){
            if(u.Username==user.Username){
                throw new UserExistsException();
            }
        }
        user.UniqueID=Users.Count.ToString();
        Users.Add(user);
        return true;
    }
    public UserData GetUser (string username)
    {
        foreach(var u in Users){
            if(u.Username==username){
                return u;
            }
        }
        return null;
    }
    public bool UpdateUser (UserData user)
    {
        foreach(var u in Users){
            if(u.UniqueID==user.UniqueID){
                int index=Users.IndexOf(u); //just replace it in the list.
                Users.RemoveAt(index);
                Users.Insert(index,user);
                return true;
            }
        }
        return false;
    }
}

Pretty simple, eh?

If you want some "stub" class to get started with, you can use this:

/// <summary>
/// Insert your summary here
/// </summary>
public class MyUserStore : IUserStore
{
    private static MyUserStore instance;
    private MyUserStore() {}

    public static MyUserStore Instance
    {
      get 
      {
         if (instance == null)
         {
            instance = new MyUserStore();
         }
         return instance;
      }
    }

    public bool AddUser (UserData user)
    {
        throw new NotImplementedException();
    }
    public UserData GetUser (string username)
    {
        throw new NotImplementedException();
    }
    public bool UpdateUser (UserData user)
    {
        throw new NotImplementedException();
    }
}

I pride myself in the minimal database interface required. I also don't deal with some of the things you may want too though. A big thing purposely missing is email verification and general user registration. I've found that hardly anyone can settle on a generic user registration page. So I'd much rather implement features that will actually be used.

Ok, so we got our UserStore. Now we just need to add some lines to Global.asax

    static MemoryUserStore Store=MemoryUserStore.Instance;
    protected virtual void Application_Start (Object sender, EventArgs e)
    {
        Authentication.SiteName="My Example Site";
        Authentication.UniqueHash="JziVXDeIdjQk5dCPcKZG"; //DO NOT COPY AND PASTE. Generate your own random string!
        Authentication.LoginPage="/Default.aspx";
        Authentication.UserStore=Store;
    }
    protected virtual void Application_BeginRequest (Object sender, EventArgs e)
    {
        Authentication.Authenticate();
    }
    protected virtual void Application_Error (Object sender, EventArgs e)
    {
        CustomErrorsFixer.HandleErrors(Context);
    }

Ok. What's all this do?

  • Authentication.SiteName is the SiteName. Just put in your website name. This is used in the naming of cookies.
  • Authentication.UniqueHash This is the super secret UniqueHash. Go to random.org and get your own unique string. This is used in generating password hashes and authentication hashes.
  • Authentication.LoginPage is where we want to redirect when a call to Authentication.RequiresLogin() fails. If you have this set to null, It will just give a generic 401 error page
  • Authentication.UserStore is where the Authentication module goes to search for user credentials
  • Authentication.Authenticate(); this is called right in the beginning of every single HTTP request. This means that user credentials are available at any time in the page lifecycle.
  • CustomErrorsFixer.HandleErrors(Context); Ok, now have no idea what this one does? This is a very neat helper class I wrote so that when an HttpException is thrown, instead of getting 302 redirects, a server-side transfer is done for the error page and the client gets the proper HTTP error code. Unless you have a special reason, or somehow have problems(I can't imagine how), I recommend always including this in your project. Note, this recognizes the normal CustomErrors section of web.config, so no additional configuration is necessary outside of standard ASP.Net configuration. Note: This also fixes an HttpException bug in Mono which makes every error appear to be a 500 error.

Ok. Now that's it! You're now authenticating users securely.

What now? Well, I think we need some way to create users. Here is what I used in my demo page. The markup is horrible, but it works and that's what counts

<%@ Page Language="C#" Inherits="Earlz.FSCAuth.Example.Register" EnableViewState="false" ValidateRequest="false" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head runat="server">
    <title>Register New Account</title>
</head>
<body>
    <form id="form1" runat="server">
    <asp:Panel runat="server" ID="RegisterPanel">
        <asp:Label runat="server" ID="BadConfirm" Visible="false" Text="Password and Password Confirm does not match" /> <br />
        <asp:Label runat="server" ID="BadUsername" Visible="false" Text="Username must be less than 50 characters" /> <br />
        Username: <asp:TextBox runat="server" ID="Username"></asp:TextBox> <br />
        Password: <asp:TextBox runat="server" ID="Password" TextMode="Password"></asp:TextBox> <br />
        Password(confirm): <asp:TextBox runat="server" ID="PasswordConfirm" TextMode="Password"></asp:TextBox> <br />
        <asp:CheckBox runat="server" ID="SuperSecret" Text="Subscribe to Super Secret group?" />
        <asp:Button runat="server" ID="Submit" Text="Register" OnClick="SubmitClick"></asp:Button>
    </asp:Panel>
    <asp:Panel runat="server" ID="InfoPanel" Visible="false">
    Username: <asp:Label runat="server" ID="UsernameLabel" /> <br />
    Password hash: <asp:Label runat="server" ID="PasswordLabel" /> <br />
    <a href="Default.aspx">Back to index</a>
    </asp:Panel>
    </form>
</body>
</html>

And then in the code behind, things stayed relatively simple:

public partial class Register : System.Web.UI.Page
{


    protected void SubmitClick(object sender,EventArgs e)
    {
        if(MemoryUserStore.Instance.Users.Count>100){
            MemoryUserStore.Instance.Users.Clear(); //clear them after creating 100 users. (for demo purposes)
        }
        if(Password.Text!=PasswordConfirm.Text){
            BadConfirm.Visible=true; 
            return;
        }
        var user=new UserData(); //create a new UserData object
        if(Username.Text.Length>50){
            BadUsername.Visible=true;
            return;
        }
        user.Username=Username.Text;
        if(SuperSecret.Checked){
            user.Groups.Add("super secret"); //subscribe to super secret group
        }
        bool res;

        try
        {
            res=Authentication.AddUser(user,Password.Text);
        }
        catch(UserExistsException)
        {
            BadUsername.Text="This username already exists in the database";
            BadUsername.Visible=true;
            return;
        }

        if(!res){
            throw new ApplicationException("Couldn't create new user for unknown reason"); 
            //not sure why it failed as it didn't throw a useful exception(this could be for database errors and such)
        }
        Authentication.Login(Username.Text,Password.Text,false); //log them in after creating the user
        UsernameLabel.Text=Server.HtmlEncode(user.Username);
        PasswordLabel.Text=user.PasswordHash;
        InfoPanel.Visible=true;
        RegisterPanel.Visible=false;

    }

}

Pretty simple code. Just does some basic validation checks and makes the user. Note that we don't fill in UserData.PasswordHash ourselves. This is done internally when Authentication.AddUser is called.

Now we just need a way to login and we're all set

To login, the LoginControl just has to be put on the page. Here is the example Default.aspx page:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Earlz.FSCAuth.Example._Default" %>
<%@ Import Namespace="Earlz.FSCAuth" %>
<%@ Register TagPrefix="fsc" Assembly="FSCAuth.Extensions" Namespace="Earlz.FSCAuth.Extensions" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <% if(Authentication.IsAuthenticated()){ %>
            Welcome, <%= Server.HtmlEncode(Authentication.CurrentUser.Username) %>
            <fsc:LogoutControl runat="server" />
        <% }else{ %>
        Please login: <br />
        <fsc:LoginControl runat="server" />
        <br />
        Or, <a href="Register.aspx">register a new account</a> <br />
        <% } %>
        Also, view the <a href="UserList.aspx">full list of users</a> <br />
        Only logged in users can see <a href="Secrets.aspx">this page</a> <br />
        Only super secret authorized users can see <a href="SuperSecret.aspx">this page</a> <br />
    </div>
    </form>
</body>
</html>
Tags: fscauth
Posted: 4/16/2011 10:32:39 PM

Fast, Secure, and Concise Authentication -- Call for beta testers

Well, I'm announcing it finally. I've broken out the Authentication module from EFramework and I'm going commercial with it(after fixing quite a few things and extending it). If you still have a copy of the BSD licensed code, I ask you kindly to buy a commercial license. But of course, I can't force you to. I can't retroactively remove the BSD license afterall.

Anyway, I need beta testers! What is FSCAuth? Well, it's a very handy authentication module for ASP.Net. It works on Mono/.Net/Webforms/MVC/You name it. And it's easy, even after the initial "hey a user can login" phase. It's designed to be customized and works well no matter what your database is. Like GUIDs as a unique ID? Go ahead, use em'! Prefer integers instead? You can use those too!

A quick summary:

  • My nemesis is Forms Authentication. Why? Well, because I don't like writing 200 lines of code just so I can use something other than the crappy SQL Server Compact database. Or heaven forbid you want your database to be halfway clean without 50 stored procedures(which do nothing) littered in it.
  • My target audience is small and medium size web applications.
  • I find it a breeze to work with. This blog uses it(in a hidden form)

If you are interested in beta testing, please send me an email at earlz at this domain name(lastyearswishes.com). Beta testers that give me feedback will get a non-expiring single-site license when the project is released. The few that give me outstanding feedback will receive a non-expiring multi-site license. In your email please include "fscauth beta testing", a little about yourself, and how you will beta test it(for instance, are you going to put it in your own blog? etc)

Demo Application

Also, if you'd like to see a small demo application you can look at fscauth-demo. It uses an in memory list of users and is limited to 100 users. But you can see the hashes for everyone's account and try hacking stuff.

Below is some more information about it. NOTE: Some of this is not yet implemented(MongoDB and SQL Server UserStores particularly). This is a projection! Some of these features may change or be removed completely.

What is this?

This is a very easy to use authentication module for use in ASP.Net. It's been designed from the beginning to be flexible, but requiring as little setup as possible. It can be used for any database/data store imaginable. I provide as an example 3 different datastores implementations

  1. SQL Server
  2. MongoDB
  3. Generic in-memory list

Why use it?

Well, I created this because I thought ASP.Net Forms Authentication required too much work for simple systems. I wanted something easier, but also more secure out of the box.

Easier?

For setup you only have to populate 2 fields, and call an Authenticate method from Global.asax. After that, you're ready to show off awesome code like this:

//Some secret stuff you don't want to show to people
Authenticate.RequiresInGroup("secret");

or even

if(Authenticate.LoggedIn){
  //show something only logged in people see
}

Security?

Right from the beginning Fast, Secure, and Concise Authentication was designed to be fool proof for security. 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. By default, all passwords are SHA256 hashed and salted. All login cookies are impossible(or almost) to forge.

Don't take my word for it though; check out the source code. With every license but Personal No Commercial, 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.

Speed?

Of course, authentication is a core part of a website, so it needs to be fairly fast. Speed was actually the lowest priority of the project, but actually, it's still very fast. The only operation requiring more than one database access is adding a user, and that is only for ease of implementation. On each authentication, there is a total of two hashes computed. With SHA256 this equates to microseconds. This will not be the component that slows your website down.

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 what I like to call a "stateless" authentication system.

What's Capable?

Notably, this project does not try to implement everything that is in Forms Authentication. If you require Windows Authentication, or complex Member/Role/Task/Group support, then maybe you should stick with Forms. This is designed rather for the 90% of websites out there that just need a simple user login system with maybe a few groups, and don't want to mess around with writing 200 lines of code to do it.

What's included?

  1. The main authentication module(source code and assembly)
  2. SQL Server example UserStore implementation
  3. MongoDB example UserStore implementation
  4. Generic in-memory list UserStore implementation
  5. ASP.Net Login custom control
  6. ASP.Net UserStatus custom control
  7. ASP.Net Logout custom control
  8. ASP.Net example web application
  9. Offline copy of documentation

Batteries are not included

What Platforms

  • Mono 2.0 and greater
  • .Net 2.0 and greater (below 3.5 must degrade to the slower Managed SHA256 implementation)
  • Designed to run from any database
  • Runs within Medium Trust
  • Works equally well for both Webforms and ASP.Net MVC
  • Runs without modifications in a web cluster
Posted: 4/10/2011 4:55:04 AM