Jul
15
2014

Custom Authentication and Authorization in ASP.NET MVC

When standard types of authentication do not meet your requirements, you need to modify an authentication mechanism to create a custom solution. A user context has principal which represents the identity and roles for that user. A user is authenticated by its identity and assigned roles to a user determine about authorization or permission to access resources.

ASP.NET provides IPrincipal and IIdentity interfaces to represents the identity and role for a user. You can create a custom solution by evaluating the IPrincipal and IIdentity interfaces which are bound to the HttpContext as well as the current thread.

  1. publicclassCustomPrincipalIPrincipal
  2. publicIIdentityIdentityprivate
  3. publicIsInRolestring role
  4. roles=>Contains
  5. return
  6. returnfalse
  7. publicCustomPrincipalstring Username
  8. IdentityGenericIdentityUsername
  9. publicUserId
  10. public string FirstName
  11. public string LastName
  12. public string roles

Now you can put this CustomPrincipal objects into the thread’s currentPrinciple property and into the HttpContext’s User property to accomplish your custom authentication and authorization process.

ASP.NET Forms Authentication

ASP.NET forms authentication occurs after IIS authentication is completed. You can configure forms authentication by using forms element with in web.config file of your application. The default attribute values for forms authentication are shown below:

  1. <system.web>
  2. <authentication"Forms"
  3. <formsloginUrl"Login.aspx"
  4. protection"All"
  5. timeout
  6. ".ASPXAUTH"
  7. requireSSL"false"
  8. slidingExpiration"true"
  9. defaultUrl"default.aspx"
  10. cookieless"UseDeviceProfile"
  11. enableCrossAppRedirects"false"/>
  12. </authentication>
  13. </system.web>

The FormsAuthentication class creates the authentication cookie automatically when SetAuthCookie() or RedirectFromLoginPage() methods are called. The value of authentication cookie contains a string representation of the encrypted and signed FormsAuthenticationTicket object.

You can create the FormsAuthenticationTicket object by specifying the cookie name, version of the cookie, directory path, issue date of the cookie, expiration date of the cookie, whether the cookie should be persisted, and optionally user-defined data as shown below:

  1. FormsAuthenticationTicket ticket FormsAuthenticationTicket
  2. "userName"
  3. DateTime
  4. DateTimeAddMinutes// value of time out property
  5. false// Value of IsPersistent property
  6. StringEmpty
  7. FormsAuthenticationFormsCookiePath

Now, you can encrypt this ticket by using the Encrypt method FormsAuthentication class as given below:

  1. string encryptedTicket FormsAuthenticationEncryptticket

To encrypt FormsAuthenticationTicket ticket set the protection attribute of the forms element to All or Encryption.

Custom Authorization

ASP.NET MVC provides Authorization filter to authorize a user. This filter can be applied to an action, a controller, or even globally. This filter is based on AuthorizeAttribute class. You can customize this filter by overriding OnAuthorization() method as shown below:

  1. publicclassCustomAuthorizeAttributeAuthorizeAttribute
  2. public string UsersConfigKey
  3. public string RolesConfigKey
  4. protectedvirtualCustomPrincipalCurrentUser
  5. returnHttpContextCurrentCustomPrincipal
  6. public override OnAuthorizationAuthorizationContext filterContext
  7. filterContextHttpContextRequestIsAuthenticated
  8. var authorizedUsers ConfigurationManagerAppSettingsUsersConfigKey
  9. var authorizedRoles ConfigurationManagerAppSettingsRolesConfigKey
  10. UsersStringIsNullOrEmptyUsers authorizedUsers Users
  11. RolesStringIsNullOrEmptyRoles authorizedRoles Roles
  12. StringIsNullOrEmptyRoles
  13. CurrentUserIsInRoleRoles
  14. filterContextResultRedirectToRouteResult
  15. RouteValueDictionary controller "Error" action "AccessDenied"
  16. // base.OnAuthorization(filterContext); //returns to login url
  17. StringIsNullOrEmptyUsers
  18. UsersContainsCurrentUserUserIdToString
  19. filterContextResultRedirectToRouteResult
  20. RouteValueDictionary controller "Error" action "AccessDenied"
  21. // base.OnAuthorization(filterContext); //returns to login url

User Authentication

A user will be authenticated if IsAuthenticated property returns true. For authenticating a user you can use one of the following two ways:

  1. Thread.CurrentPrincipal.Identity.IsAuthenticated

  2. HttpContext.Current.User.Identity.IsAuthenticated

Designing Data Model

Now it’s time to create data access model classes for creating and accessing Users and Roles as shown below:

  1. publicclass
  2. publicUserId
  3. Required
  4. publicStringUsername
  5. Required
  6. publicStringEmail
  7. Required
  8. publicStringPassword
  9. publicStringFirstName
  10. publicStringLastName
  11. publicBooleanIsActive
  12. publicDateTimeCreateDate
  13. publicvirtualICollectionRoles
  1. publicclass
  2. publicRoleId
  3. Required
  4. public string RoleName
  5. public string Description
  6. publicvirtualICollectionUsers

Defining Database Context with code first mapping between User and Role

Using Entity Framework code first approach, create a DataContext having User and Role entities with its relational mapping details as shown below:

  1. publicclassDataContextDbContext
  2. publicDataContext
  3. "DefaultConnection"
  4. protected override OnModelCreatingDbModelBuilder modelBuilder
  5. modelBuilderEntity>()
  6. HasMany=>Roles
  7. WithMany=>Users
  8. =>
  9. ToTable"UserRoles"
  10. MapLeftKey"UserId"
  11. MapRightKey"RoleId"
  12. publicDbSetUsers
  13. publicDbSetRoles

Code First Database Migrations

With the help of entity framework code first database migrations create the database named as Security in the SQL Server. Run the following command through Visual Studio Package Manager Console to migrate your code into SQL Server database.

After running first command i.e. enabling migrations for your project, add seed data to Configuration.cs file of Migrations folder as shown below:

  1. protected override SecurityDataContext context
  2. role1 RoleName"Admin"
  3. role2 RoleName"User"
  4. user1 Username"admin"Email"admin@ymail.com"FirstName"Admin"Password"123456"IsActiveCreateDateDateTimeUtcNowRoles
  5. user2 Username"user1"Email"user1@ymail.com"FirstName"User1"Password"123456"IsActiveCreateDateDateTimeUtcNowRoles
  6. user1Rolesrole1
  7. user2Rolesrole2
  8. contextUsersuser1
  9. contextUsersuser2

When above three commands will be executed successfully as shown above, the following database will be created in your SQL Server.

Solution Structure

Designing View Model

Create a view model class for handing login process as given below:

  1. publicclassLoginViewModel
  2. Required
  3. Display"User name"
  4. public string Username
  5. Required
  6. DataTypeDataTypePassword
  7. Display"Password"
  8. public string Password
  9. Display"Remember me?"
  10. publicRememberMe
  1. publicclassCustomPrincipalSerializeModel
  2. publicUserId
  3. public string FirstName
  4. public string LastName
  5. public string roles

Forms Authentication Initialization

  1. publicclassAccountControllerController
  2. DataContextContextDataContext
  3. // GET: /Account/
  4. publicActionResultIndex
  5. return
  6. HttpPost
  7. publicActionResultIndexLoginViewModel model string returnUrl
  8. ModelStateIsValid
  9. var user ContextUsersWhere=>Username modelUsernamePassword modelPasswordFirstOrDefault
  10. var rolesRolesSelect=>RoleNameToArray
  11. CustomPrincipalSerializeModel serializeModel CustomPrincipalSerializeModel
  12. serializeModelUserIdUserId
  13. serializeModelFirstNameFirstName
  14. serializeModelLastNameLastName
  15. serializeModelroles roles
  16. string userData JsonConvertSerializeObjectserializeModel
  17. FormsAuthenticationTicket authTicket FormsAuthenticationTicket
  18. Email
  19. DateTime
  20. DateTimeAddMinutes
  21. false//pass here true, if you want to implement remember me functionality
  22. userData
  23. string encTicket FormsAuthenticationEncryptauthTicket
  24. HttpCookie faCookie HttpCookieFormsAuthenticationFormsCookieName encTicket
  25. ResponseCookiesfaCookie
  26. rolesContains"Admin"
  27. returnRedirectToAction"Index""Admin"
  28. rolesContains"User"
  29. returnRedirectToAction"Index""User"
  30. returnRedirectToAction"Index""Home"
  31. ModelStateAddModelError"Incorrect username and/or password"
  32. returnmodel
  33. AllowAnonymous
  34. publicActionResultLogOut
  35. FormsAuthenticationSignOut
  36. returnRedirectToAction"Login""Account"
  1. publicclassMvcApplicationSystemHttpApplication
  2. protectedApplication_Start
  3. AreaRegistrationRegisterAllAreas
  4. WebApiConfigRegisterGlobalConfigurationConfiguration
  5. FilterConfigRegisterGlobalFiltersGlobalFiltersFilters
  6. RouteConfigRegisterRoutesRouteTableRoutes
  7. BundleConfigRegisterBundlesBundleTableBundles
  8. DatabaseSetInitializerDataContext>(DataContextInitilizer
  9. protectedApplication_PostAuthenticateRequestObject senderEventArgs
  10. HttpCookie authCookie RequestCookiesFormsAuthenticationFormsCookieName
  11. authCookie
  12. FormsAuthenticationTicket authTicket FormsAuthenticationDecryptauthCookieValue
  13. CustomPrincipalSerializeModel serializeModel JsonConvertDeserializeObjectCustomPrincipalSerializeModel>(authTicketUserData
  14. CustomPrincipal newUser CustomPrincipalauthTicket
  15. newUserUserId serializeModelUserId
  16. newUserFirstName serializeModelFirstName
  17. newUserLastName serializeModelLastName
  18. newUserroles serializeModelroles
  19. HttpContextCurrent newUser

Base Controller for accessing Current User

Create a base controller for accessing your User data in your all controller. Inherit, your all controller from this base controller to access user information from the UserContext.

  1. publicclassBaseControllerController
  2. protectedvirtualCustomPrincipal
  3. returnHttpContextCustomPrincipal
  1. publicclassHomeControllerBaseController
  2. // GET: /Home/
  3. publicActionResultIndex
  4. string FullNameFirstNameLastName
  5. return

Base View Page for accessing Current User

Create a base class for all your views for accessing your User data in your all views as shown below:

  1. public abstract classBaseViewPageWebViewPage
  2. publicvirtualCustomPrincipal
  3. returnCustomPrincipal
  4. public abstract classBaseViewPageTModelWebViewPageTModel
  5. publicvirtualCustomPrincipal
  6. returnCustomPrincipal

Register this class with in the \Views\Web.config as base class for all your views as given below:

  1. <system.web.webPages.razor>
  2. <!--Other code has been removed for clarity-->
  3. <pagespageBaseType"Security.DAL.Security.BaseViewPage"
  4. <namespaces>
  5. <!--Other code has been removed for clarity-->
  6. </namespaces>
  7. </pages>
  8. </system.web.webPages.razor>

Now you can access the authenticated user information on all your view in easy and simple way as shown below in Admin View:

  1. ViewBag.Title = "Index";
  2. Layout = "~/Views/Shared/_AdminLayout.cshtml";
  3. <h4>Welcome : @User.FirstName</h4>
  4. <h1>Admin DashBoard</h1>

Login View

  1. @model Security.Models.LoginViewModel
  2. ViewBag.Title = "Index";
  3. @using (Html.BeginForm())
  4. @Html.AntiForgeryToken()
  5. <divclass"form-horizontal"
  6. <h4>User Login</h4>
  7. <hr/>
  8. @Html.ValidationSummary(true)
  9. <divclass"form-group"
  10. @Html.LabelFor(model => model.Username, new { @class = "control-label col-md-2" })
  11. <divclass"col-md-10"
  12. @Html.EditorFor(model => model.Username)
  13. @Html.ValidationMessageFor(model => model.Username)
  14. </div>
  15. </div>
  16. <divclass"form-group"
  17. @Html.LabelFor(model => model.Password, new { @class = "control-label col-md-2" })
  18. <divclass"col-md-10"
  19. @Html.EditorFor(model => model.Password)
  20. @Html.ValidationMessageFor(model => model.Password)
  21. </div>
  22. </div>
  23. <divclass"form-group"
  24. @Html.LabelFor(model => model.RememberMe, new { @class = "control-label col-md-2" })
  25. <divclass"col-md-10"
  26. @Html.EditorFor(model => model.RememberMe)
  27. @Html.ValidationMessageFor(model => model.RememberMe)
  28. </div>
  29. </div>
  30. <divclass"form-group"
  31. <divclass"col-md-offset-2 col-md-10"
  32. <input"submit"value"Login"class"btn btn-default"/>
  33. </div>
  34. </div>
  35. </div>

Applying CustomAuthorize attribute

To make secure your admin or user pages, decorate your Admin and User controllers with CustomAuthorize attribute as defined above and specify the uses or roles to access admin and user pages.

  1. CustomAuthorizeRoles"Admin"
  2. // [CustomAuthorize(Users = "1")]
  3. publicclassAdminControllerBaseController
  4. // GET: /Admin/
  5. publicActionResultIndex
  6. return
  1. CustomAuthorizeRoles"User"
  2. // [CustomAuthorize(Users = "1,2")]
  3. publicclassUserControllerBaseController
  4. // GET: /User/
  5. publicActionResultIndex
  6. return

You can also specify the Roles and Users with in your web.config as a key to avoid hard code values for Users and Roles at the controller level.

  1. add key"RolesConfigKey" value"Admin"/>
  2. add key"UsersConfigKey" value"2,3"/>

Use one of these keys within the CustomAuthorize attribute as shown below:

  1. //[CustomAuthorize(RolesConfigKey = "RolesConfigKey")]
  2. CustomAuthorizeUsersConfigKey"UsersConfigKey"
  3. publicclassAdminControllerBaseController
  4. // GET: /Admin/
  5. publicActionResultIndex
  6. return
  1. CustomAuthorizeRolesConfigKey"RolesConfigKey"
  2. // [CustomAuthorize(UsersConfigKey = "UsersConfigKey")]
  3. publicclassUserControllerBaseController
  4. // GET: /User/
  5. publicActionResultIndex
  6. return

Test your application

When you will run your application and will login into the application using user1, you will be redirected to User dashboard as shown below:

When user will try to access unauthorized pages such as Admin dashboard using URL: http://localhost:11681/Admin , he will get the custom error page as shown below:

What do you think?

I hope you will enjoy the tips while implmenting role-based or user-based security in your ASP.NET MVC application. I would like to have feedback from my blog readers. Your valuable feedback, question, or comments about this article are always welcome.

Reference url: http://www.dotnet-tricks.com/Tutorial/mvc/G54G220114-Custom-Authentication-and-Authorization-in-ASP.NET-MVC.html 

Mar
2
2014

ASP.Net MVC'de Tema Gerçekleştirme

Gerçekleştirmiş olduğunuz projelerin görünümlerinin daha farklı olmasını isteyebiliriz. Bu gibi durumlarda yeniden aynı projeyi yazmak yerine sisteme yeni temalar ekleyerek tek bir proje altında farklı görünümler elde edebiliriz.

Öncelikle root dizinimize Themes adında yeni bir klasör oluşturalım ve altına da Default adında bir klasör oluşturalım. Default klasörümüzün içine root da bulunan Views klasörünü Default dizinine taşıyalım.

Dizin yapımış şu şekilde olacaktır: 

 

Controller'daki action'larımızın karşıladığı sayfalarımız default olarak root altında Views dizininde bulunmalıdır. Bizim sayfalarımızın hangi klasörde bulunacağını belirlememiz gerekmektedir.

Öncelikle tema özelliklerini tutan bir sınıf yaratıyoruz:

public class Theme
{
    public string Name { get; set; }
    public string BasePath { get; set; }
    public string Path { get { return String.Format("~/{0}/{1}/",BasePath,Name); } }
    public Theme(string basePath, string name)
    {
        Name = name;
        BasePath = basePath;
    }
}

Şimdi de System.Web.Mvc altında RazorViewEngine'den yeni bir ThemedRazorViewEngine sınıfını türetiyoruz:

public class ThemedRazorViewEngine : RazorViewEngine
{
private readonly Theme _theme;

public ThemedRazorViewEngine(Theme theme)
{
_theme = theme;

base.ViewLocationFormats = new[]
{
_theme.Path + "/Views/{1}/{0}.cshtml",
_theme.Path + "/Views/Shared/{0}.cshtml",
"~/Themes/Default/Views/{1}/{0}.cshtml"
};

base.PartialViewLocationFormats = new[]
{
_theme.Path + "/Views/{1}/{0}.cshtml",
_theme.Path + "/Views/Shared/{0}.cshtml",
"~/Themes/Default/Views/Shared/{0}.cshtml"
};

base.AreaViewLocationFormats = new[]
{
_theme.Path + "/Views/{2}/{1}/{0}.cshtml",
_theme.Path + "/Views/Shared/{0}.cshtml",
"~/Themes/Default/Views/{1}/{0}.cshtml"
};
base.AreaPartialViewLocationFormats = new[]
{
_theme.Path + "/Views/{2}/{1}/{0}.cshtml",
_theme.Path + "/Views/{1}/{0}.cshtml",
_theme.Path + "/Views/Shared/{0}.cshtml",
"~/Themes/Default/Views/Shared/{0}.cshtml"
};
}

public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName,
string masterName, bool useCache)
{
// eğer çalışma anında temayı değiştirmek istiyorsanız viewCache'in değeri false olmaalıdır.
const bool useViewCache = false;

return base.FindView(controllerContext, viewName, masterName, useViewCache);
}
}

Web.Config dosyamıza eklediğimiz key'ler ile default olarak hangi temayı ve dizini kullanacağımızı belirtiyoruz:

<appSettings>
<add key="ThemeName" value="Default" />
<add key="ThemeBasePath" value="Themes" />
</appSettings>

 Son aşamada ise global.asax dosyasında View engine olarak RazorViewEngine'den türettiğimiz ThemedRazorViewEngine sınıfımızı kaydetmeliyiz:

protected void Application_Start()
{
//..
RegisterViewEngine(ViewEngines.Engines);
}

public static void RegisterViewEngine(ViewEngineCollection viewEngines)
{
// mevcut engineleri temizliyoruz.
viewEngines.Clear();

var basePath = ConfigurationManager.AppSettings["ThemeBasePath"];
var themeName = ConfigurationManager.AppSettings["ThemeName"];

var theme = new Theme(basePath,themeName);

var themeableRazorViewEngine = new ThemedRazorViewEngine(theme);

viewEngines.Add(themeableRazorViewEngine);
}

Artık tema desteği olan bir ViewEngine'e sahip olduk. Artık dilediğimiz kadar tema ekleyebiliriz.

 

Kaynak: http://onuralptaner.com/arsivler/2013/07/18/mvc-razor-tema-destegi/

Jan
12
2014

Varolan bir veri tabanı ile Entity Framework Code First Uygulaması

Merhaba, öncelikle code first ne işe yarar bundan bahsedelim. Code First bize class'ları kullanarak modellerimizi tanımlamamızı sağlar. Peki avantajları nelerdir? Temel olarak üç avantajı vardır:

  • Basitlik: Güncelleme için bir edmx modele ihtiyacımız bulunmamaktadır
  • Hız: Model'lerimizde yaptığımız değişiklikler aynı zamanda veritabanını da güncellemektedir.
  • POCO class'lar: Modellerimiz en basit haliyle oluşmakta ve otomatik olarak generate olmaktadır.

Öncelikle var olan bir veritabanımız olduğunu düşünelim. Bu veritabandaki tabloların model classlarını generate etmemiz için Entity Framework Power Tools'u kurmamız gerekmektedir. Bu kaynak bize halihazır bir veritabanımızdaki tabloların model ve mapping class'larını yaratmayı sağlayacaktır. Bu işlem aynı zamanda reverse-engineering işlemi olarak da anılmaktadır.

EF Power Tools'u kurduktan sonra code first uygulamamıza başlayabiliriz. Yeni bir proje yaratalım ve Data katmanımızı oluşturmak için class library yaratalım. Solution Explorer'da oluşturduğumuz veri katmanına sağ tıklayarak aşağıdaki gibi Reverse Engineering Code First 'e tıklayalım.

Bu işlemden sonra data layer projemizde Models klasörü oluşacaktır. Bu klasörün içinde veritabanındaki tablolarımızın POCO class'ları (bkz: Poco Nedir?) yaratılmış olacaktır. Bunlar bizim modellerimizi oluşturan domain class'larımızdır.

Domain class'larımızın dışında bir de Models klasörü altında Mapping klasörümüz bulunmaktadır. Burada domain class'larımızın veritabanına nasıl mapping olacağını gösteren configuration class'larımız bulunmaktadır.

  • Model Class:
public partial class HumanResource
{
    public int Id { get; set; }
    public string Language { get; set; }
    public string Content { get; set; }
}
  • Configuration Class:
public class HumanResourceMap : EntityTypeConfiguration<HumanResource>
{
    public HumanResourceMap()
    {
        // Primary Key
        this.HasKey(t => t.Id);

        // Properties
        this.Property(t => t.Language)
            .IsRequired();

        // Table & Column Mappings
        this.ToTable("HumanResource");
        this.Property(t => t.Id).HasColumnName("Id");
        this.Property(t => t.Language).HasColumnName("Language");
        this.Property(t => t.Content).HasColumnName("Content");
    }
 }
  • CRUD işlemleri:

Artık veritabanına ulaşabiliriz. Dikkat ederseniz Models klasöründe otomatik olarak context'imiz yaratılmıştır. Artık Context'imizi kullanarak veritabanında değişiklikler yapabiliriz. Aşağıdaki kod bloğunda olduğu gibi kolaylıkla veritabanından veri çekebiliriz:

using (var db = new HumanResourceContext())
        {
            // Create and save a new HumanResource
            
            var content = "İlgili içerik";
            var lang = "TR";
 
            var hr = new HumanResource { Content = content, Language = lang };
            db.Blogs.Add(blog);
            db.SaveChanges();
 
            // Display all HumanResources from the database
            var query = from b in db.HumanResource
                        select b;
        }


Dec
29
2013

T-SQL ile tekrar eden satırların silinmesi

DELETE TableName 
FROM TableName
LEFT OUTER JOIN (
   SELECT MIN(Id) as Id, TargetColumn
   FROM TableName
   GROUP BY TargetColumn
) as KeepRows ON
   TableName.Id = KeepRows.Id
WHERE
   KeepRows.SettingRowId IS NULL