Authentication Token Service for WCF Services (Part 2 – Database Authentication)
In the previous segment, Authentication Token Service for WCF Services (Part 1), we created a project that exposes an AuthenticationTokenService and a Test1Service. The object is to first authenticate using the AuthenticationTokenService. Authentication provides a token. Calls made to additional services should include the token as a header value.
We used concrete implementations of Interfaces to do our authentication, token creation, and token validation. The concrete implementations had stubbed-in code. I used interfaces because now to change this to use database authentication, I can create concrete implementation of the same interfaces. My implementation will be different but the methods and signatures should remain the same.
Download this project here: WCF BTS DB
So here is quick table that might help you visualize what is going on. We already have the interfaces, we already have the code example code. We need to write new classes that instead of using stub example code uses database code.
Interface | Concrete Code Example Class | Concrete Database Class |
---|---|---|
ICredentialsValidator | CodeExampleCredentialsValidator | DatabaseCredentialsValidator |
ITokenBuilder | CodeExampleTokenBuilder | DatabaseTokenBuilder |
ITokenValidator | CodeExampleTokenValidator | DatabaseTokenValidator |
OK. So we have one task to create database implementation of the interfaces. However, before we do that, we have two tasks we must do first if we are going to use a database.
- A database (SQL Server)
- A data access layer (Entity Framework)
You may already have a database, in which case, skip to Step 5& – Add Entity Framework.
Step 1 – Create the database
For now, let’s keep everything in Visual Studio. So we will create the database as a file in Visual Studio.
Note: For production deployment, we will use a real database and change the connection string in the web.config to point to the real database.
- Right-click on App_Data and choose Add | New Item . . . | Installed > Visual C# > Data | SQL Server Database.
- I named this database BasicTokenDatabase.mdf.
Step 2 – Create the User table
We will create only two tables. A user table and a Token table. A user table is needed that has at least a user and a password. The user field should be unique. The password field should NOT store the password in clear text. Instead it should store a salted hash of the password. Since we are using a salt, we need a column to store the salt. If you don’t know what a salt is, read about it here: https://crackstation.net/hashing-security.htm
- Double-click the database in Visual Studio.
The Data Connections widget should open with a connection to the BasicTokenDatabase.mdf. - Right-click on Tables and choose Add New Table.
- Keep the first Id column but also make it an identity so it autoincrements.
- Add three columns: User, Password, and Hash.
The table should look as follows:Name Data Type Allow Nulls Default Id int [ ] User nvarchar(50) [ ] Password nvarchar(250) [ ] Salt nvarchar(250) [ ] - Add a Unique constraint for the User column. I do this just by adding it to the table creation code.The SQL to create the table should look like this:
CREATE TABLE [dbo].[User] ( [Id] INT IDENTITY (1, 1) NOT NULL, [User] NVARCHAR (50) NOT NULL, [Password] NVARCHAR (250) NOT NULL, [Salt] NVARCHAR (250) NOT NULL, PRIMARY KEY CLUSTERED ([Id] ASC), CONSTRAINT [Unique_User] UNIQUE NONCLUSTERED ([User] ASC) );
- Click Update to create the table.
- Close the table designer window.
Step 3 – Create a Token table
For the purposes of our token service, we want to create a token and store it in the database. We need a table to store the token as well as some data about the token, such as create date, and which user the token belongs to, etc.
- Double-click the database in Visual Studio.
The Data Connections widget should open with a connection to the BasicTokenDatabase.mdf. - Right-click on Tables and choose Add New Table.
- Keep the first Id column but also make it an identity so it autoincrements.
- Add three columns: Token, UserId, CreateDateTime.
The table should look as follows:Name Data Type Allow Nulls Default Id int [ ] Token nvarchar(250) [ ] UserId int [ ] CreateDate DateTime [ ] - Add a foreign key constraint for the UserId column to the Id column of the User table. I do this just by adding it to the table creation code.
- Add a Unique constraint for the Token column. I do this just by adding it to the table creation code. The SQL to create the table should look like this:
CREATE TABLE [dbo].[Token] ( [Id] INT IDENTITY (1, 1) NOT NULL, [Token] NVARCHAR (250) NOT NULL, [UserId] INT NOT NULL, [CreateDate] DATETIME NOT NULL, PRIMARY KEY CLUSTERED ([Id] ASC), CONSTRAINT [Unique_Token] UNIQUE NONCLUSTERED ([Token] ASC), CONSTRAINT [FK_Token_ToUser] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id]) );
- Click Update to create the table.
- Close the table designer window.
Step 4 – Add a default user to the database
We need a user to test. We are going to add a user as follows:
User: user1
Password: pass1
Salt: salt1
- Double-click the database in Visual Studio.
The Data Connections widget should open with a connection to the BasicTokenDatabase.mdf. - Right-click on SimpleTokenConnection and choose New Query.
- Add SQL to insert a user.
The sql to insert the sample user is this:INSERT INTO [User] ([User],[Password],[Salt]) VALUES ('user1','63dc4400772b90496c831e4dc2afa4321a4c371075a21feba23300fb56b7e19c','salt1')
Step 5 – Add Entity Framework
- Right-click on the solution and choose Manage NuGet Packages for Solution.
- Click Online.
- Type “Entity” into the search.
- Click Install when EntityFramework comes up.
- You will be prompted to accept the license agreement.
Step 6 – Add a DBContext
Entity Framework has a lot of options. Because I expect you to already have a database, I am going to use Code First to an Existing database.
- Create a folder called Database in your project.
- Right-click on the Database folder and choose Add | New Item . . . | Installed > Visual C# > Data | ADO.NET Entity Data Model.
- Give it a name and click OK.
I named mine SimpleTokenDbContext. - Select Code First from database.
Your BasicTokenDatabase should selected by default. If not, you have to browse to it. - I named my connection in the web.config BasicTokenDbConnection and clicked next.
- Expand tables and expand dbo and check the User table and the Token table.
- Click Finish.
You should now have three new objects created:
- SimpleTokenDbContext.cs
- Token.cs
- User.cs
Entity Framework will allow us to use these objects when communicating with the database.
Note: I made one change to these. Because User is a table name and a column name, Entity Framework named the class object User and the property for the user column User1. That looked wierd to me, so I renamed the User1 property to Username but I left the table with and table column named User. Token and the Token property also had this issue. I changed the Token property to be Text.
[Column("User")] [Required] [StringLength(50)] public string Username { get; set; }
[Column("Token")] [Required] [StringLength(250)] public string Text { get; set; }
Step 7 – Implement ICredentialsValidator
- Create a new class called DatabaseCredentialsValidator.cs.
- Use Entity Framework and the Hash class to check if those credentials match what is in the User table of the database.
using System; using System.Linq; using WcfSimpleTokenExample.Database; using WcfSimpleTokenExample.Interfaces; namespace WcfSimpleTokenExample.Business { public class DatabaseCredentialsValidator : ICredentialsValidator { private readonly BasicTokenDbContext _DbContext; public DatabaseCredentialsValidator(BasicTokenDbContext dbContext) { _DbContext = dbContext; } public bool IsValid(Model.Credentials creds) { var user = _DbContext.Users.SingleOrDefault(u => u.Username.Equals(creds.User, StringComparison.CurrentCultureIgnoreCase)); return user != null && Hash.Compare(creds.Password, user.Salt, user.Password, Hash.DefaultHashType, Hash.DefaultEncoding); } } }
Step 8 – Implement ITokenBuilder
- Create a new class called DatabaseTokenBuilder.cs.
- Use Entity Framework to create a new token and add it to the Token table in the database.
- Instead of using Guid.NewGuid, which isn’t secure because it may not be cryptographically random, we will create a better random string generator using RNGCryptoServiceProvider. Se the BuildSecureToken() method below.
using System; using System.Linq; using System.Security.Authentication; using System.Security.Cryptography; using WcfSimpleTokenExample.Database; using WcfSimpleTokenExample.Interfaces; namespace WcfSimpleTokenExample.Business { public class DatabaseTokenBuilder : ITokenBuilder { public static int TokenSize = 100; private readonly BasicTokenDbContext _DbContext; public DatabaseTokenBuilder(BasicTokenDbContext dbContext) { _DbContext = dbContext; } public string Build(Model.Credentials creds) { if (!new DatabaseCredentialsValidator(_DbContext).IsValid(creds)) { throw new AuthenticationException(); } var token = BuildSecureToken(TokenSize); var user = _DbContext.Users.SingleOrDefault(u => u.Username.Equals(creds.User, StringComparison.CurrentCultureIgnoreCase)); _DbContext.Tokens.Add(new Token { Text = token, User = user, CreateDate = DateTime.Now }); _DbContext.SaveChanges(); return token; } private string BuildSecureToken(int length) { var buffer = new byte[length]; using (var rngCryptoServiceProvider = new RNGCryptoServiceProvider()) { rngCryptoServiceProvider.GetNonZeroBytes(buffer); } return Convert.ToBase64String(buffer); } } }
Step 9 – Implement ITokenValidator
- Create a new class called DatabaseTokenValidator.cs.
- Read the token from the header data.
- Use Entity Framework to verify the token is valid.
using System; using System.Linq; using WcfSimpleTokenExample.Database; using WcfSimpleTokenExample.Interfaces; namespace WcfSimpleTokenExample.Business { public class DatabaseTokenValidator : ITokenValidator { // Todo: Set this from a web.config appSettting value public static double DefaultSecondsUntilTokenExpires = 1800; private readonly BasicTokenDbContext _DbContext; public DatabaseTokenValidator(BasicTokenDbContext dbContext) { _DbContext = dbContext; } public bool IsValid(string tokentext) { var token = _DbContext.Tokens.SingleOrDefault(t => t.Text == tokentext); return token != null && !IsExpired(token); } internal bool IsExpired(Token token) { var span = DateTime.Now - token.CreateDate; return span.TotalSeconds > DefaultSecondsUntilTokenExpires; } } }
Step 10 – Update the services code
Ideally we would have our services code automatically get the correct interface implementations. But for this example, we want to keep things as simple as possible.
using System.Security.Authentication; using System.ServiceModel; using System.ServiceModel.Activation; using System.ServiceModel.Web; using WcfSimpleTokenExample.Business; using WcfSimpleTokenExample.Database; using WcfSimpleTokenExample.Interfaces; using WcfSimpleTokenExample.Model; namespace WcfSimpleTokenExample.Services { [ServiceContract] [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] public class AuthenticationTokenService { [WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Bare)] [OperationContract] public string Authenticate(Credentials creds) { using (var dbContext = new BasicTokenDbContext()) { ICredentialsValidator validator = new DatabaseCredentialsValidator(dbContext); if (validator.IsValid(creds)) return new DatabaseTokenBuilder(dbContext).Build(creds); throw new InvalidCredentialException("Invalid credentials"); } } } }
using System.ServiceModel; using System.ServiceModel.Activation; using System.ServiceModel.Web; using System.Web; using WcfSimpleTokenExample.Business; using WcfSimpleTokenExample.Database; using WcfSimpleTokenExample.Interfaces; namespace WcfSimpleTokenExample.Services { [ServiceContract] [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] public class Test1Service { [OperationContract] [WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Bare)] public string Test() { var token = HttpContext.Current.Request.Headers["Token"]; // This works whether aspNetCompatibilityEnabled is true of false. using (var dbContext = new BasicTokenDbContext()) { ITokenValidator validator = new DatabaseTokenValidator(dbContext); return validator.IsValid(token) ? "Your token worked!" : "Your token failed!"; } } } }
I didn’t make any changes to the web.config myself. However, the web.config was changed by adding Entity Framework and a database. Download the source code to see and example of it.
Testing using Postman
The steps for testing with Postman in Part 1 should still be valid for Part 2. Just remember to remove any escape characters from the returned string. For example, if a \/ is found, remove the \ as it is an escape character. If you look at the resulting token in Postman’s Pretty tab, the escape character is removed for you.
Well, by now, you should be really getting this down. Hopefully but this point, you can now take this code and implement your own Basic Token Service BTS. Hopefully you can use this where simple token authentication is needed and the bloat of an entire Secure Token Service framework is not.
Go on and read part 3 here: Authentication Token Service for WCF Services (Part 3 – Token Validation in IDispatchMessageInspector)