Role based accessibility is another integral part of web development because it provides encapsulation for designated information accessibility to designated credentials. Microsoft MVC paradigm provides a very simple and effective mechanism to achieve role based accessibility. So, for today's discussion, I will be demonstrating role based accessibility using ASP.NET MVC 5 technology.
The following are some prerequisites before you proceed any further in this tutorial:
Prerequisites:
Before moving further, you should have knowledge of the following technologies:
- ASP.NET MVC 5.
- ADO.NET.
- Entity Framework.
- OWIN.
- Claim Base Identity Model.
- C# programming.
- C# LINQ.
You can download the complete source code or you can follow the step by step discussion below. The sample code is developed in Microsoft Visual Studio 2013 Ultimate. I am using SQL Server 2008 as database.
Let's Begin now.
1. Firstly, you need to create a sample database with "Login" & "Role" tables, I am using the following scripts to generate my sample database. My database name is "RoleBaseAccessibility", below is the snippet for it:
- USE [RoleBaseAccessibility]
- GO
-
- IF EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'[dbo].[R_10]') AND parent_object_id = OBJECT_ID(N'[dbo].[Login]'))
- ALTER TABLE [dbo].[Login] DROP CONSTRAINT [R_10]
- GO
-
- IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[LoginByUsernamePassword]') AND type in (N'P', N'PC'))
- DROP PROCEDURE [dbo].[LoginByUsernamePassword]
- GO
-
- IF EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'[dbo].[R_10]') AND parent_object_id = OBJECT_ID(N'[dbo].[Login]'))
- ALTER TABLE [dbo].[Login] DROP CONSTRAINT [R_10]
- GO
- IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Login]') AND type in (N'U'))
- DROP TABLE [dbo].[Login]
- GO
-
- IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Role]') AND type in (N'U'))
- DROP TABLE [dbo].[Role]
- GO
-
- SET ANSI_NULLS ON
- GO
- SET QUOTED_IDENTIFIER ON
- GO
- IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Role]') AND type in (N'U'))
- BEGIN
- CREATE TABLE [dbo].[Role](
- [role_id] [int] IDENTITY(1,1) NOT NULL,
- [role] [nvarchar](max) NOT NULL,
- CONSTRAINT [PK_Role] PRIMARY KEY CLUSTERED
- (
- [role_id] ASC
- )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
- ) ON [PRIMARY]
- END
- GO
- SET IDENTITY_INSERT [dbo].[Role] ON
- INSERT [dbo].[Role] ([role_id], [role]) VALUES (1, N'Admin')
- INSERT [dbo].[Role] ([role_id], [role]) VALUES (2, N'User')
- SET IDENTITY_INSERT [dbo].[Role] OFF
-
- SET ANSI_NULLS ON
- GO
- SET QUOTED_IDENTIFIER ON
- GO
- SET ANSI_PADDING ON
- GO
- IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Login]') AND type in (N'U'))
- BEGIN
- CREATE TABLE [dbo].[Login](
- [id] [int] IDENTITY(1,1) NOT NULL,
- [username] [varchar](50) NOT NULL,
- [password] [varchar](50) NOT NULL,
- [role_id] [int] NOT NULL,
- CONSTRAINT [PK_Login] PRIMARY KEY CLUSTERED
- (
- [id] ASC
- )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
- ) ON [PRIMARY]
- END
- GO
- SET ANSI_PADDING OFF
- GO
- SET IDENTITY_INSERT [dbo].[Login] ON
- INSERT [dbo].[Login] ([id], [username], [password], [role_id]) VALUES (1, N'admin', N'admin', 1)
- INSERT [dbo].[Login] ([id], [username], [password], [role_id]) VALUES (2, N'user', N'user', 2)
- SET IDENTITY_INSERT [dbo].[Login] OFF
-
- SET ANSI_NULLS ON
- GO
- SET QUOTED_IDENTIFIER ON
- GO
- IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[LoginByUsernamePassword]') AND type in (N'P', N'PC'))
- BEGIN
- EXEC dbo.sp_executesql @statement = N'-- =============================================
- -- Author: <Author,,Name>
- -- Create date: <Create Date,,>
- -- Description: <Description,,>
- -- =============================================
- CREATE PROCEDURE [dbo].[LoginByUsernamePassword]
- @username varchar(50),
- @password varchar(50)
- AS
- BEGIN
- SELECT id, username, password, role_id
- FROM Login
- WHERE username = @username
- AND password = @password
- END
- '
- END
- GO
-
- IF NOT EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'[dbo].[R_10]') AND parent_object_id = OBJECT_ID(N'[dbo].[Login]'))
- ALTER TABLE [dbo].[Login] WITH CHECK ADD CONSTRAINT [R_10] FOREIGN KEY([role_id])
- REFERENCES [dbo].[Role] ([role_id])
- ON UPDATE CASCADE
- ON DELETE CASCADE
- GO
- IF EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'[dbo].[R_10]') AND parent_object_id = OBJECT_ID(N'[dbo].[Login]'))
- ALTER TABLE [dbo].[Login] CHECK CONSTRAINT [R_10]
- GO
Here I have created a simple login & role tables with sample data and a store procedure to retrieve the data.
2. Create new visual studio web MVC project and name it "RoleBaseAccessibility".
3. You need to create "ADO.NET" database connectivity. You can visit here for details.
4. You also need to create basic "Login" interface, I am not going to show you how you can create a basic login application by using Claim Base Identity Model. You can either download source code for this tutorial or you can go through detail tutorial here for better understanding.
5. Now, open "App_Start->Startup.Auth.cs" file and replace it with following code:
- using Microsoft.AspNet.Identity;
- using Microsoft.AspNet.Identity.EntityFramework;
- using Microsoft.AspNet.Identity.Owin;
- using Microsoft.Owin;
- using Microsoft.Owin.Security.Cookies;
- using Microsoft.Owin.Security.DataProtection;
- using Microsoft.Owin.Security.Google;
- using Owin;
- using System;
- using RoleBaseAccessibility.Models;
- namespace RoleBaseAccessibility
- {
- public partial class Startup
- {
-
- public void ConfigureAuth(IAppBuilder app)
- {
-
-
-
- app.UseCookieAuthentication(new CookieAuthenticationOptions
- {
- AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
- LoginPath = new PathString("/Account/Login"),
- LogoutPath = new PathString("/Account/LogOff"),
- ExpireTimeSpan = TimeSpan.FromMinutes(5.0),
- ReturnUrlParameter = "/Home/Index"
- });
- app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- }
- }
- }
In above code following line of code will redirect the user to home page if he/she tries to access a link which is not authorized to him/her:
- ReturnUrlParameter = "/Home/Index"
6. Create new controller, name it "AccountController.cs" under "Controller" folder and replace it with the following code:
-
-
-
-
-
-
- namespace RoleBaseAccessibility.Controllers
- {
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Security.Claims;
- using System.Web;
- using System.Web.Mvc;
- using Microsoft.AspNet.Identity;
- using Microsoft.Owin.Security;
- using RoleBaseAccessibility.Models;
-
-
-
- public class AccountController : Controller
- {
- #region Private Properties
-
-
-
- private RoleBaseAccessibilityEntities databaseManager = new RoleBaseAccessibilityEntities();
- #endregion
- #region Default Constructor
-
-
-
- public AccountController()
- {
- }
- #endregion
- #region Login methods
-
-
-
-
-
- [AllowAnonymous]
- public ActionResult Login(string returnUrl)
- {
- try
- {
-
- if (this.Request.IsAuthenticated)
- {
-
- return this.RedirectToLocal(returnUrl);
- }
- }
- catch (Exception ex)
- {
-
- Console.Write(ex);
- }
-
- return this.View();
- }
-
-
-
-
-
-
- [HttpPost]
- [AllowAnonymous]
- [ValidateAntiForgeryToken]
- public ActionResult Login(LoginViewModel model, string returnUrl)
- {
- try
- {
-
- if (ModelState.IsValid)
- {
-
- var loginInfo = this.databaseManager.LoginByUsernamePassword(model.Username, model.Password).ToList();
-
- if (loginInfo != null && loginInfo.Count() > 0)
- {
-
- var logindetails = loginInfo.First();
-
- this.SignInUser(logindetails.username, logindetails.role_id, false);
-
- this.Session["role_id"] = logindetails.role_id;
-
- return this.RedirectToLocal(returnUrl);
- }
- else
- {
-
- ModelState.AddModelError(string.Empty, "Invalid username or password.");
- }
- }
- }
- catch (Exception ex)
- {
-
- Console.Write(ex);
- }
-
- return this.View(model);
- }
- #endregion
- #region Log Out method.
-
-
-
-
- [HttpPost]
- [ValidateAntiForgeryToken]
- public ActionResult LogOff()
- {
- try
- {
-
- var ctx = Request.GetOwinContext();
- var authenticationManager = ctx.Authentication;
-
- authenticationManager.SignOut();
- }
- catch (Exception ex)
- {
-
- throw ex;
- }
-
- return this.RedirectToAction("Login", "Account");
- }
- #endregion
- #region Helpers
- #region Sign In method.
-
-
-
-
-
-
- private void SignInUser(string username, int role_id, bool isPersistent)
- {
-
- var claims = new List<Claim>();
- try
- {
-
- claims.Add(new Claim(ClaimTypes.Name, username));
- claims.Add(new Claim(ClaimTypes.Role, role_id.ToString()));
- var claimIdenties = new ClaimsIdentity(claims, DefaultAuthenticationTypes.ApplicationCookie);
- var ctx = Request.GetOwinContext();
- var authenticationManager = ctx.Authentication;
-
- authenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, claimIdenties);
- }
- catch (Exception ex)
- {
-
- throw ex;
- }
- }
- #endregion
- #region Redirect to local method.
-
-
-
-
-
- private ActionResult RedirectToLocal(string returnUrl)
- {
- try
- {
-
- if (Url.IsLocalUrl(returnUrl))
- {
-
- return this.Redirect(returnUrl);
- }
- }
- catch (Exception ex)
- {
-
- throw ex;
- }
-
- return this.RedirectToAction("Index", "Home");
- }
- #endregion
- #endregion
- }
- }
In above code, the following piece of code is important i.e.
- #region Sign In method.
-
-
-
-
-
-
- private void SignInUser(string username, int role_id, bool isPersistent)
- {
-
- var claims = new List<Claim>();
- try
- {
-
- claims.Add(new Claim(ClaimTypes.Name, username));
- claims.Add(new Claim(ClaimTypes.Role, role_id.ToString()));
- var claimIdenties = new ClaimsIdentity(claims, DefaultAuthenticationTypes.ApplicationCookie);
- var ctx = Request.GetOwinContext();
- var authenticationManager = ctx.Authentication;
-
- authenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, claimIdenties);
- }
- catch (Exception ex)
- {
-
- throw ex;
- }
- }
- #endregion
Here, along with claiming "Username" in OWIN security layer, we are also claiming "role_id" to provide role base accessibility:
- claims.Add(new Claim(ClaimTypes.Role, role_id.ToString()));
7. Now, create new controller under "Controller" folder, name it "HomeController.cs" and replace it with the following code i.e.
-
-
-
-
-
- namespace RoleBaseAccessibility.Controllers
- {
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Web;
- using System.Web.Mvc;
-
-
-
- [Authorize]
- public class HomeController : Controller
- {
- #region Index method.
-
-
-
-
- public ActionResult Index()
- {
- return this.View();
- }
- #endregion
- #region Admin Only Link
-
-
-
-
- [Authorize(Roles = "1")]
- public ActionResult AdminOnlyLink()
- {
- return this.View();
- }
- #endregion
- }
- }
In above code, we have simply created two views, one is accessible to all the user which is "Index" view and second method is accessible to only "Admin" role user with role_id = 1. Following piece of code will translate the role base accessibility in OWIN security layer i.e.
If you want to define role accessibility for multiple roles you can achieve it like following:
- [Authorize(Roles = "1, 2, 3")]
where, 2, 3 are role id(s).
8. Replace the following code in "_LoginPartial.cshtml" file:
- @*@using Microsoft.AspNet.Identity*@
- @if (Request.IsAuthenticated)
- {
- using (Html.BeginForm("LogOff", "Account", FormMethod.Post, new { id = "logoutForm", @class = "navbar-right" }))
- {
- @Html.AntiForgeryToken()
- <ul class="nav navbar-nav navbar-right">
- @if (Convert.ToInt32(this.Session["role_id"]) == 1)
- {
- <li>
- @Html.ActionLink("Admin Only Link", "AdminOnlyLink", "Home")
- </li>
- }
- <li>
- @Html.ActionLink("Hello " + User.Identity.Name + "!", "Index", "Home", routeValues: null, htmlAttributes: new { title = "Manage" })
- </li>
- <li><a href="javascript:document.getElementById('logoutForm').submit()">Log off</a></li>
- </ul>
- }
- }
- else
- {
- <ul class="nav navbar-nav navbar-right">
- <li>@Html.ActionLink("Log in", "Login", "Account", routeValues: null, htmlAttributes: new { id = "loginLink" })</li>
- </ul>
- }
9. Execute the project and you will see the following:
10. When you login as admin account you will able to see a link that only admin can see as follow:
11. When you login as non-admin account you won't see the link that admin can see but, if you try to open the link which supposedly you do not have access of, you will be redirected to your home page as follow:
Conclusion
In this tutorial you learned how to use create role based accessibility. You also learned how you can encapsulate role based accessibility for multiple roles.