Login or Sign Up to become a member!
LessThanDot Sit Logo

LessThanDot

Web Developer

Less Than Dot is a community of passionate IT professionals and enthusiasts dedicated to sharing technical knowledge, experience, and assistance. Inside you will find reference materials, interesting technical discussions, and expert tips and commentary. Once you register for an account you will have immediate access to the forums and all past articles and commentaries.

LTD Social Sitings

Lessthandot twitter Lessthandot Linkedin Lessthandot friendfeed Lessthandot facebook Lessthandot rss

Note: Watch for social icons on posts by your favorite authors to follow their postings on these and other social sites.

Your profile

    Search

    XML Feeds

    Google Ads

    « Nancy and VB.Net: Using easyhttp as our clientAdding Stackoverflow user feed to your homepage »
    comments

    Introduction

    I am making this little project in Nancy because I want to use it at work next year. I tried out most of the things I will be needing and trying to get a feel of the product. This has of course resulted in some blogposts. And it will save me a lot of time once I get back.

    Yesterday it was time to add some authentication to my pages. I choose to use Form Authentication.

    Setting it up

    You will just have to add the Nancy.Authentication.Forms to your project.

    And then the first thing to do is to alter your bootstrapper.

    And add this to it.

    1. Protected Overrides Sub ConfigureRequestContainer(container As TinyIoCContainer, context As NancyContext)
    2.         MyBase.ConfigureRequestContainer(container, context)
    3.         container.Register(Of IUserMapper, FakeUserMapper)()
    4.     End Sub
    5.  
    6.     Protected Overrides Sub RequestStartup(container As TinyIoCContainer, pipelines As IPipelines, context As NancyContext)
    7.         Dim formsAuthConfiguration = New FormsAuthenticationConfiguration With
    8.         {
    9.             .RedirectUrl = "~/login",
    10.             .UserMapper = container.Resolve(Of IUserMapper)()
    11.         }
    12.         FormsAuthentication.Enable(pipelines, formsAuthConfiguration)
    13.     End Sub

    The above tells us that the route to our login page will be /login and that we need a class that implement IUserMapper and that is called FakeUserMapper. So here we go.

    1. Imports Nancy.Authentication.Forms
    2. Imports WebApplication2.Services
    3.  
    4. Namespace Security
    5.     Public Class FakeUserMapper
    6.         Implements IUserMapper
    7.  
    8.         Private ReadOnly _userService As UserService
    9.  
    10.         Public Sub New(userService As UserService)
    11.             _userService = userService
    12.         End Sub
    13.  
    14.  
    15.         Public Function GetUserFromIdentifier(ByVal identifier As Guid, ByVal context As Nancy.NancyContext) As Nancy.Security.IUserIdentity Implements IUserMapper.GetUserFromIdentifier
    16.             Dim user = _userService.GetById(identifier)
    17.             Return New AuthenticatedUser() With
    18.             {
    19.                 .UserName = user.Name,
    20.                 .Claims = user.Claims
    21.             }
    22.         End Function
    23.  
    24.     End Class
    25. End Namespace

    So I will also need a UserService and an AuthenticatedUser class.

    1. Imports Nancy.Security
    2.  
    3. Namespace Security
    4.  
    5.     Public Class AuthenticatedUser
    6.         Implements IUserIdentity
    7.  
    8.         Public Property UserName() As String Implements IUserIdentity.UserName
    9.         Public Property Claims() As IEnumerable(Of String) Implements IUserIdentity.Claims
    10.     End Class
    11. End Namespace
    1. Imports Microsoft.VisualBasic.ApplicationServices
    2. Imports WebApplication2.Model
    3.  
    4. Namespace Services
    5.     Public Class UserService
    6.  
    7.         Private ReadOnly _users As IList(Of UserModel)
    8.  
    9.         Public Sub New()
    10.             _users = New List(Of UserModel)
    11.             _users.Add(New UserModel With {.Id = New Guid("00000000000000000000000000000004"), .Name = "Chris1", .Password = "123"})
    12.             _users.Add(New UserModel With {.Id = New Guid("00000000000000000000000000000001"), .Name = "Chris2", .Password = "123"})
    13.             _users.Add(New UserModel With {.Id = New Guid("00000000000000000000000000000002"), .Name = "Chris3", .Password = "123"})
    14.             _users.Add(New UserModel With {.Id = New Guid("00000000000000000000000000000003"), .Name = "Chris4", .Password = "123"})
    15.         End Sub
    16.  
    17.         Public Function GetUsers() As IList(Of UserModel)
    18.             Return _users
    19.         End Function
    20.  
    21.         Public Function AuthenticateUser(ByVal username As String, ByVal password As String)
    22.             Dim user = _users.SingleOrDefault(Function(userModel) userModel.Name = username)
    23.             If user IsNot Nothing AndAlso Not user.Password.Equals(password) Then
    24.                 Return Nothing
    25.             End If
    26.             Return user
    27.         End Function
    28.  
    29.         Public Function GetById(ByVal identifier As Guid) As UserModel
    30.             Return _users.SingleOrDefault(Function(userModel) userModel.Id = identifier)
    31.         End Function
    32.     End Class
    33. End Namespace

    Warning! Warning! Warning!

    Don't use New Guid("00000000000000000000000000000000") that is not considered to be a Guid. And it will have you scratching your hair for a few hours. If only I had hair.

    Now we just need a module to intercept our login route.

    1. Option Strict Off
    2.  
    3. Imports System.Dynamic
    4. Imports Nancy.Authentication.Forms
    5. Imports Nancy.ModelBinding
    6. Imports Nancy
    7. Imports WebApplication2.Services
    8.  
    9. Namespace Modules
    10.  
    11.     Public Class LoginModule
    12.         Inherits NancyModule
    13.  
    14.         Public Sub New(ByVal userService As UserService)
    15.             MyBase.Get("/login") = Function(parameters)
    16.                                        Return View("login.vbhtml")
    17.                                    End Function
    18.             MyBase.Post("/login") = Function(parameters)
    19.                                         Dim loginParams = Me.Bind(Of LoginParams)()
    20.                                         Dim user = userService.AuthenticateUser(loginParams.Username, loginParams.Password)
    21.                                         If user Is Nothing Then
    22.                                             Return "Your username and password were incorrect please enter a correct one."
    23.                                         End If
    24.                                         Return Me.LoginAndRedirect(user.Id)
    25.                                     End Function
    26.             MyBase.Get("/logout") = Function(parameters)
    27.                                         Return Me.LogoutAndRedirect("~/")
    28.                                     End Function
    29.         End Sub
    30.     End Class
    31.  
    32.     Public Class LoginParams
    33.         Public Property Username As String
    34.         Public Property Password As String
    35.     End Class
    36. End Namespace

    So we got a get for login to show our login page, we got a post for login so that people can be authenticated and we got a get for logout. The important methods are LoginAndRedirect and LogoutAndRedirect.

    Here is the View that goes with that.

    1. @Code
    2.     ViewBag.Title = "Index page"
    3.     Layout = "Master.vbhtml"
    4. End Code
    5.  
    6.     <form method="POST">
    7.         Username <input type="text" name="Username" />
    8.         <br />
    9.         Password <input name="Password" type="password" />
    10.         <br />
    11.         <input type="submit" value="Login" />
    12.     </form>

    So now we have our login process all set up. Now it is time to protect something.

    The protected

    I will protect the user information in this one.

    Our module is super simple.

    1. Imports WebApplication2.Model
    2. Imports Nancy
    3. Imports Nancy.Security
    4. Imports WebApplication2.Services
    5.  
    6. Namespace Modules
    7.  
    8.     Public Class UsersModule
    9.         Inherits NancyModule
    10.  
    11.         Public Sub New(userService As UserService)
    12.             Me.RequiresAuthentication()
    13.             MyBase.Get("/users") = Function(parameters)
    14.                                        Return View(New UsersModel() With {.Users = userService.GetUsers()})
    15.                                    End Function
    16.             MyBase.Get("/users/{Id}") = Function(parameters)
    17.                                             Dim result As Guid
    18.                                             Dim isInteger = Guid.TryParse(parameters.id, result)
    19.                                             Dim user = userService.GetById(result)
    20.                                             If isInteger AndAlso user IsNot Nothing Then
    21.                                                 Return View(user)
    22.                                             Else
    23.                                                 Return HttpStatusCode.NotFound
    24.                                             End If
    25.                                         End Function
    26.         End Sub
    27.     End Class
    28. End Namespace

    The one important line is the RequiresAuthetication. And that will make it work. Simple.

    Now we just need our views.

    But you can find all those on the github thing, together with the models.

    Testing

    So now that you got all that working it is time to write our tests... and from this point forward you can start writing tests first.

    If we take the way we have been testing our modules before we will get a 401. But we needed to test that anyway.

    Here is one such test in complete isolation, just for you.

    1. <Test()>
    2.         Public Sub IfPlantWithId2ReturnsWebpagePlantWithId2()
    3.             Dim loggedInBrowserResponse As BrowserResponse
    4.             Dim formsAuthenticationConfiguration = New FormsAuthenticationConfiguration() With
    5.                         {
    6.                             .RedirectUrl = "~/login",
    7.                             .UserMapper = New FakeUserMapper(New UserService())
    8.                         }
    9.             Dim configuration = A.Fake(Of IRazorConfiguration)()
    10.             Dim bootstrapper = New ConfigurableBootstrapper(Sub(config)
    11.                                                                  config.Module(Of UsersModule)()
    12.                                                                  config.Module(Of LoginModule)()
    13.                                                                  config.ViewEngine(New RazorViewEngine(configuration))
    14.                                                                  config.RequestStartup(Sub(x, pipelines, z)
    15.                                                                                            FormsAuthentication.Enable(pipelines, formsAuthenticationConfiguration)
    16.                                                                                        End Sub)
    17.                                                              End Sub)
    18.             loggedInBrowserResponse = New Browser(bootstrapper2).Post("/login", Sub(x)
    19.                                                                                      x.HttpRequest()
    20.                                                                                      x.FormValue("Username", "Chris1")
    21.                                                                                      x.FormValue("Password", "123")
    22.                                                                                  End Sub)
    23. Dim result = loggedInBrowserResponse.Then.Get("/users/00000000-0000-0000-0000-000000000004", Sub(x)
    24.                                                                                                               x.HttpRequest()
    25.                                                                                                           End Sub)
    26.             Assert.AreEqual("00000000-0000-0000-0000-000000000004", result.BodyAsXml.Descendants("td")(1).Value)
    27.         End Sub

    As you can see I had to add FormsAuth to my custom bootstrapper.
    I then did a post with some correct credentials.
    And then we do a get of a user. And checked if that data was in the response.
    Also make sure you add the LoginModule to your bootstrapper
    Simples, once you know how.

    Conclusion

    This seems like a lot of code, but once you have it set up it's pretty much ok.

    Next post I will explain how to get the login/logout link to appear on each page. Next post after the next post that is. Because the next post is post 500 and that is special like me.

    About the Author

    User bio imageChris is awesome.
    Social SitingsTwitterHomePageLTD RSS Feed
    nancy, vb.net
    InstapaperVote on HN

    No feedback yet

    Leave a comment


    Your email address will not be revealed on this site.

    To mislead the spambots.

    Your URL will be displayed.
    (Line breaks become <br />)
    (Name, email & website)
    (Allow users to contact you through a message form (your email will not be revealed.)