Nov
1
Written by:
Michael Washington
11/1/2013 6:23 PM
Creating a LightSwitch website and setting security is easy. However, you must set up all your users manually. In some cases you want to allow users to self-register.
LightSwitch uses the standard ASP.NET Membership Provider. You can integrate it with any security, (see: Integrating LightSwitch Into An ASPNET Application To Provide Single Sign On). However, if you only require a self-registration page, it is simple to set-up.
This topic has been covered in the article: Allowing Users To Self Register In Your LightSwitch Website. However, that article uses Web Forms (this article uses MVC) and does not allow users to change their passwords. Also, this article provides a landing page for the root of your LightSwitch application.
First, we start with the code from the article: Using MVC With Visual Studio LightSwitch.
Turn On Forms Authentication
Go into Properties and select Use Forms authentication.
This will cause LightSwitch to create LogIn and LogOut pages. These pages will redirect to the LightSwitch application.
If you have other non-LightSwitch pages, for example public pages for your website, you will want to use the pages we are about to create.
Add The Model
Add a class called UserDTO.cs to the Models folder and use the following code:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web;
namespace LightSwitchApplication.Models
{
public class UserDTO
{
public string UserName { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public bool IsLockedOut { get; set; }
public bool IsOnline { get; set; }
public bool RememberMe { get; set; }
}
public class ChangePasswordDTO
{
[Display(Name = "Old Password")]
public string OldPassword { get; set; }
[Display(Name = "New Password")]
public string NewPassword { get; set; }
[Display(Name = "Confirm Password")]
public string ConfirmPassword { get; set; }
}
}
Add The Views
Create a folder under the Views folder and call it Account.
Right-click on the Account folder and select Add then MVC 5 View Page (Razor).
Repeat the process to create:
- ChangePassword.cshtml
- Login.cshtml
- Register.cshtml
Use the following code for each page:
ChangePassword.cshtml
@{
Layout = null;
}
@model LightSwitchApplication.Models.ChangePasswordDTO
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Change Password</title>
</head>
<body>
<h2>Change Password</h2>
<div>
@using (Html.BeginForm("ChangePassword", "Account",
FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
{
@Html.AntiForgeryToken()
@Html.ValidationSummary()
<div class="form-group">
@Html.LabelFor(model => model.OldPassword,
new { @class = "col-md-2 control-label" })
<div class="col-md-10">
@Html.PasswordFor(model => model.OldPassword,
new { @class = "form-control" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.NewPassword,
new { @class = "col-md-2 control-label" })
<div class="col-md-10">
@Html.PasswordFor(model => model.NewPassword,
new { @class = "form-control" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.ConfirmPassword,
new { @class = "col-md-2 control-label" })
<div class="col-md-10">
@Html.PasswordFor(model => model.ConfirmPassword,
new { @class = "form-control" })
</div>
</div>
<br />
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit"
value="Change password"
class="btn btn-default" />
</div>
</div>
}
<br />
<div>
@Html.ActionLink("Back", "Index", "Home")
</div>
</div>
</body>
</html>
Login.cshtml
@{
Layout = null;
}
@model LightSwitchApplication.Models.UserDTO
<!DOCTYPE html>
<html>
<head>
<meta name="HandheldFriendly" content="true" />
<meta name="viewport" content="width=device-width, initial-scale=1,
minimum-scale=1, maximum-scale=1, user-scalable=no" />
<title>Log in</title>
</head>
<body>
<h2>Log In</h2>
<div class="row">
<div class="col-md-8">
<section id="loginForm">
@using (Html.BeginForm("Login", "Account", new { ReturnUrl = ViewBag.ReturnUrl },
FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
{
@Html.AntiForgeryToken()
@Html.ValidationSummary(true)
<div class="form-group">
@Html.LabelFor(model => model.UserName, new { @class = "col-md-2 control-label" })
<div class="col-md-10">
@Html.TextBoxFor(model => model.UserName, new { @class = "form-control" })
@Html.ValidationMessageFor(model => model.UserName)
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.Password, new { @class = "col-md-2 control-label" })
<div class="col-md-10">
@Html.PasswordFor(model => model.Password, new { @class = "form-control" })
@Html.ValidationMessageFor(model => model.Password)
</div>
</div>
<br />
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<div class="checkbox">
@Html.CheckBoxFor(model => model.RememberMe)
@Html.LabelFor(model => model.RememberMe)
</div>
</div>
</div>
<br />
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Log in" class="btn btn-default" />
</div>
</div>
<p>
@Html.ActionLink("Register", "Register") if you don't have a local account.
</p>
}
</section>
</div>
</div>
</body>
</html>
Register.cshtml
@{
Layout = null;
}
@model LightSwitchApplication.Models.UserDTO
<!DOCTYPE html>
<html>
<head>
<meta name="HandheldFriendly" content="true" />
<meta name="viewport" content="width=device-width, initial-scale=1,
minimum-scale=1, maximum-scale=1, user-scalable=no" />
<title>Register</title>
</head>
<body>
<h2>Register</h2>
<div>
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class="control-group">
@Html.LabelFor(model => model.UserName, new { @class = "control-label" })
<div class="controls">
@Html.EditorFor(model => model.UserName)
@Html.ValidationMessageFor(model => model.UserName,
null, new { @class = "help-inline" })
</div>
</div>
<div class="control-group">
@Html.LabelFor(model => model.Password, new { @class = "control-label" })
<div class="controls">
@Html.PasswordFor(model => model.Password)
@Html.ValidationMessageFor(model => model.Password,
null, new { @class = "help-inline" })
</div>
</div>
<div class="control-group">
@Html.LabelFor(model => model.Email, new { @class = "control-label" })
<div class="controls">
@Html.EditorFor(model => model.Email)
@Html.ValidationMessageFor(model => model.Email,
null, new { @class = "help-inline" })
</div>
</div>
<br />
<div class="form-actions no-color">
<input type="submit" value="CreateUser" class="btn" />
</div>
@Html.ValidationSummary(true)
}
<br />
<div>
@Html.ActionLink("Back", "Index", "Home")
</div>
</div>
</body>
</html>
Add The Controller
Add a class called AccountController.cs to the Controllers folder and use the following code:
using System;
using System.Text;
using System.Web.Mvc;
using System.Web.Security;
using LightSwitchApplication.Models;
using Microsoft.LightSwitch.Security.ServerGenerated.Implementation;
using Microsoft.LightSwitch.Server;
namespace LightSwitchApplication.Controllers
{
public class AccountController : Controller
{
// =============================
// Register - Create a new user
// =============================
public ActionResult Register()
{
return View(new UserDTO());
}
[HttpPost]
public ActionResult Register(FormCollection collection)
{
try
{
var UserName = collection["UserName"];
var Password = collection["Password"];
var Email = collection["Email"];
if (UserName == "")
{
throw new Exception("No UserName");
}
if (Password == "")
{
throw new Exception("No Password");
}
// Keep our UserName as LowerCase
UserName = UserName.ToLower();
// Create LightSwitch user
MembershipUser objMembershipUser = Membership.CreateUser(UserName, Password, Email);
// Log User in
// Create a new instance of the LightSwitch Authentication Service
using (var authService = new AuthenticationService())
{
var LoggedInUser = authService.Login(
UserName,
Password,
false,
null);
// Successful login? If so, return the user
if (LoggedInUser != null)
{
return Redirect("~/Home");
}
else
{
ModelState.AddModelError(string.Empty, "Login failed.");
return View();
}
}
}
catch (Exception ex)
{
ModelState.AddModelError(
string.Empty, "Error: " + ex);
return View();
}
}
// ========================================================
// ChangePassword - Change the password of an existing user
// ========================================================
[Authorize]
public ActionResult ChangePassword()
{
return View(new ChangePasswordDTO());
}
[Authorize]
[HttpPost]
public ActionResult ChangePassword(FormCollection collection)
{
try
{
using (var authService = new AuthenticationService())
{
if (collection["NewPassword"] != collection["ConfirmPassword"])
{
throw new Exception("New Password and Confirm Password must match");
}
if (!Membership.GetUser()
.ChangePassword(collection["OldPassword"], collection["NewPassword"]))
{
throw new Exception("Password change failed.");
}
return Redirect("~/Home");
}
}
catch (Exception ex)
{
ModelState.AddModelError(string.Empty, "Error: " + ex);
return View();
}
}
// ===================================================
// Login - Log a user in, return authentication cookie
// ===================================================
public ActionResult Login()
{
return View(new UserDTO());
}
[HttpPost]
public ActionResult Login(FormCollection collection)
{
try
{
// Create a new instance of the LightSwitch Authentication Service
using (var authService = new AuthenticationService())
{
// Log User in
var user = authService.Login(
collection["UserName"].ToLower(),
collection["Password"],
Convert.ToBoolean(collection["Persistent"]),
null);
// Successful login? If so, return the user
if (user != null)
{
return Redirect("~/Home");
}
else
{
ModelState.AddModelError(string.Empty,
"Login failed. Check User Name and/or Password.");
return View();
}
}
}
catch (Exception ex)
{
ModelState.AddModelError(string.Empty, "Error: " + ex.Message);
return View();
}
}
// ============================================================
// LogOff - Clears the cookie, logging a user out of the system
// ============================================================
public ActionResult LogOff()
{
// Create a new instance of the LightSwitch Authentication Service
using (var authService = new AuthenticationService())
{
var user = authService.Logout();
return Redirect("~/Home");
}
}
}
}
Update the Index Page
Open the Index.cshtml page in the Views / Home folder and change the code to the following:
@{
Layout = null;
}
@using Microsoft.AspNet.Identity
<!DOCTYPE html>
<html>
<head>
<meta name="HandheldFriendly" content="true" />
<meta name="viewport" content="width=device-width,
initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no" />
<title>Log In</title>
</head>
<body>
<div>
@if (Request.IsAuthenticated)
{
using (Html.BeginForm("LogOff", "Account",
FormMethod.Post, new { id = "logoutForm", @class = "navbar-right" }))
{
@Html.AntiForgeryToken()
<p>
Hello @User.Identity.GetUserName() |
@Html.ActionLink("Change Password",
"ChangePassword", "Account",
routeValues: null, htmlAttributes: new { id = "changepassword" }) |
<a href="javascript:document.getElementById('logoutForm').submit()">Log off</a>
</p>
<a href="HTMLClient">LightSwitch Application</a>
}
}
else
{
<p>
@Html.ActionLink("Register", "Register", "Account",
routeValues: null, htmlAttributes: new { id = "registerLink" }) |
@Html.ActionLink("Log in", "Login", "Account",
routeValues: null, htmlAttributes: new { id = "loginLink" })
</p>
}
</div>
</body>
</html>
Special Thanks
This article would not be possible without the information provided by Dale Morrison (blog.ofanitguy.com):
More On LightSwitch and MVC
Creating an AngularJS CRUD Application Using Visual Studio LightSwitch
Using JayData to Consume the Visual Studio LightSwitch OData Business Layer in a AngularJs CRUD Application
Download Code
The LightSwitch project is available at http://lightswitchhelpwebsite.com/Downloads.aspx
(you must have Visual Studio 2013 (or higher) installed to run the code)
15 comment(s) so far...
Michael, excellent article and something I am looking forward to implementing in an up coming project. Thanks for your continued work in guiding people like me along. I personally really appreciate it!
By Paul Pitchford on
11/8/2013 6:40 AM
|
@Paul Pitchford - Thank you for the feedback, it is much appreciated :)
By Michael Washington on
11/8/2013 6:41 AM
|
Hi Michael, with regard to adding the MVC functionality to a Lightswitch project, I have found that you cannot add the Global.asax directly to the Server project, as it would not compile due to the fact that it does not create the code behind file automatically.
So in order to solve this one may download your sample and copy the Global.asax file to the project, or otherwise create two files, one is "Global" without any file type and one is "Global.asax.cs".
In the file "Global" add the following script:
And then in the "Global.asax.cs" add the rest of the code as per mentioned in your blog.
If you are in VS2013 you can right click the Global.asax and then open with Notepad to edit the "Global" file, whilst open directly would let you edit the "Global.asax.cs".
I have googled a bit and found that in the past we may add the "Global.asax" as the Global class file in the new item in VS2010, but it is deprecated in VS2013. This might be due to the fact that Microsoft does not expect you to create the Global.asax directly, but being scaffolded when creating a new MVC project.
By Cliff Lo on
12/25/2013 6:58 AM
|
Hi Michael,
With regard to adding roles for the self-registered user, add the following line below:
//Create LightSwitch user MembershipUser objMembershipUser = Membership.CreateUser(UserName, Password, Email);
//Add this line Roles.AddUserToRole(UserName, "Your Role");
However, remember to create your role with your administrator account first.
By Cliff Lo on
12/25/2013 8:52 AM
|
Hi I have missed the script for global.asax. The script is below:
Add this to "Global" to bind "Global" to "Global.asax.cs".
By Cliff Lo on
12/25/2013 8:55 AM
|
@Cliff Lo - Thanks. To post your code it may be better to post it on the official LightSwitch forums (http://social.msdn.microsoft.com/Forums/vstudio/en-US/home?forum=lightswitch) and then post a link to your post. I am going to leave the article as it is because I have presented it numerous times going though it step-by-step so I am confidant that what I have presented works.
By Michael Washington on
12/25/2013 11:46 AM
|
Hi Micheal I am using vs2013 with this project and would like get the logged on username in my startup Main application? I have looked everywhere including adding you GetUserName.ashx routine which fails on this project. Could you please help me.
By pp8357 on
5/19/2014 4:07 PM
|
@pp8357 - I don't know why it is not working for you. It works for me. It may be better to post it on the official LightSwitch forums (http://social.msdn.microsoft.com/Forums/vstudio/en-US/home?forum=lightswitch).
By Michael Washington on
5/19/2014 6:38 PM
|
Hi Michael, I found this to be a great article. However, after implementing the pattern, I couldn't help to think about security. How does one prevent registered users from performing CRUD against both the SecurityData and ApplicationData via the controller actions and/or svc OData endpoints? If a user is registered, the user can perform CRUD against the MVC application from any client that can consume the endpoints, since all LightSwitch instrinsic data is under the same endpoint name, ApplicationData.svc. Please advise how one could use this pattern in a real-world application and not allow its users to have access to performing CRUD against any table in it via an OData client consuming ApplicationData.svc and using the user's credentials.
Thanks in advance
By Michael Faraday on
10/20/2014 8:36 AM
|
@Michael Faraday - See Creating an AngularJS CRUD Application Using Visual Studio LightSwitch (http://lightswitchhelpwebsite.com/Blog/tabid/61/EntryId/2230/Creating-an-AngularJS-CRUD-Application-Using-Visual-Studio-LightSwitch.aspx) for a real world example. The MVC end points call ServerApplicationContext and this is a secure LightSwitch API that enforces row level security.
By Michael Washington on
7/6/2014 2:38 PM
|
Hi Michael,
i bought all your books, and you are my LS guru. I use LS SecurityData form for authentication and i can change the password, but how to reset user passwords if they forget?
Thank you so much
By silva on
3/5/2015 11:22 AM
|
@silva - Add a Desktop (Silverlight) Client to manage users. See: http://blogs.msdn.com/b/bethmassi/archive/2013/06/25/how-to-assign-users-roles-and-permissions-to-a-lightswitch-html-mobile-client.aspx
By Michael Washington on
3/5/2015 11:23 AM
|
hi, this Project download/tool is perfect and helps a lot. one question, is there also a version with email confirmation and PW forgot & reset around? this would be awesome. thanks for your feedback, marcel
By mw on
7/18/2016 7:46 AM
|
@mw - It is actually a complex requirement with a lot of permutations because each site would have different business rules. It needs to be customized in each implementation.
By Michael Washington on
7/18/2016 7:48 AM
|
Hi Michael, thanks for your time and effort to provide information in this wonderful technology for people just starting like me. Great job and thank you so much.
By Gustavo Jaime on
9/25/2016 6:49 AM
|