Security and Authorization

Radzen provides security support out of the box. It relies on ASP.NET Core Identity and needs a MS SQL server, MySQL or PostgreSQL data source to be configured in order to persist the users and roles.

The built-in security support provides the following features:

  • Login and registration of new users.
  • Optional email confirmation during user registration.
  • Ability to define custom user roles.
  • Specify the level of access for pages - everybody, authenticated users or members of a role.
  • Adding custom properties to the user entity.

Quick video

Enable security

To enable security in Radzen follow these steps.

  1. Click the security link at the top right corner (next to data).
  2. Select Default from the providers dropdown.
  3. Check the Auto generate pages for user and role management if you want Radzen to create login, register, user and role management pages.
  4. Pick an existing MS SQL Server, MySQL or PostgreSQL data source from the available list. You will have to add at least one in order to use the security feature of Radzen.
  5. Click the Save button.

Specify the access of a page

When security is enabled Radzen will allow you to specify which users can access a page. If a user doesn’t have access to certain page it will not appear in the application navigation. If the user enters that page URL manually in the browser he or she will see a generated unauthorized page.

  1. Click the edit button of any page.
  2. Pick the access level from the Access dropdown. The available options are:
    • Everybody - everyone can access this page - anonymous and authenticated users.
    • Authenticated - only users that have logged in can access that page.
    • Roles defined in the application - only members of the specified role(s) can access that page. If the application hasn’t been run or there are no roles defined yet you will only see Everybody and Authenticated.
  3. Click Save

The least restrictive role is taken under consideration when determining the current access rules. For example if a page is configured to be accessible to Everybody and the Marketing role it would end up being accessible by all users.

In-Development security

During development you can use a special account for testing. Log in with admin as both username and password. This account is only available during development when the ASPNETCORE_ENVIRONMENT environment variable is set to Development. It also belongs to all roles defined by the application and has full access as a result.

After deployment the account is no longer available! Be sure to either allow user registration (done by default if you allow Radzen to generate security pages) or create at least one user account during development.

Adding roles

An authorized user can add roles from the Roles page accessible from the top right-hand application menu.

By default all authenticated (logged-in) users have access to the role management pages. Make sure you restrict the access to those pages for production applications. Create an Administrator role and allow only member of this role to access the role management pages.

Adding users

A Radzen application starts with no users apart from the development-only special admin account.

By default all authenticated (logged-in) users have access to the user management pages. Make sure you restrict the access to those pages for production applications. Create an Administrator role and allow only member of this role to access the user management pages.

There are two ways to add users to an application

  1. By allowing user registration (enabled by default).
  2. Manually adding users from the Users page accessible from the top right-hand menu.

API

Server-side API

You can use the ASP.NET Core Identity API. For example to get the name of the authenticated user you can do the following:

var userName = this.HttpContext.User.FindFirst(ClaimTypes.Name).Value;
Dim userName = Me.HttpContext.User.FindFirst(ClaimTypes.Name).Value

To authorize only certain roles to have access to controllers decorate them with the Authorize attribute:

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;

namespace [ApplicationName].Controllers.[DataSourceName]
{
    [Authorize(Roles="Administrator", ActiveAuthenticationSchemes="Bearer")]
    public partial class OrdersController
    {
    }
}
Imports System.Security.Claims
Imports System.Threading.Tasks
Imports Microsoft.AspNetCore.Authorization
Imports Microsoft.AspNetCore.Identity

Namespace [ApplicationName].Controllers.[DataSourceName]
    <Authorize(Roles:="Administrator", ActiveAuthenticationSchemes:="Bearer")>
    Public Partial Class OrdersController
    End Class
End Namespace

Client-side API

When security is enabled Radzen will inject the Security Angular service to all pages.

Security methods

isAuthenticated(): boolean

Returns true if the user is authenticated; otherwise false;

Example
<div *ngIf="security.isAuthenticated()">
  Only authenticated users will see this.
</div>

isInRole(role: string): boolean

Returns true if the user is from the specified role or Administrator; otherwise false;

Example
<div *ngIf="security.isInRole('Sales')">
  Only sales people or administrators will see this.
</div>

Security properties

get name(): string

Returns the name of the authenticated user.

Example
<div *ngIf="security.isAuthenticated()">
  {{ security.name }}
</div>

get profile(): any

Returns the profile object of the authenticated user. Contains all claims associated with the user - name, role and custom ones.

Example
<div *ngIf="security.isAuthenticated()">
  {{ security.profile | json }}
</div>

get roles(): string[]

Returns the roles of the authenticated user.

Example
<div *ngIf="security.isAuthenticated()">
  {{ security.roles | json }}
</div>

Extensibility

Often there would be a need to extend the user entity by adding additional properties.

Add ApplicationUser partial class

  1. Go to the server\Authentication directory.
  2. Add a new file called ApplicationUser.Properties.cs. It will extend the ApplicationUser partial class.
  3. Add a new property e.g. Country
         using System;
         using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
    
         // Replace [ApplicationName] with the namespace of your Radzen application.
         namespace [ApplicationName].Models
         {
             public partial class ApplicationUser
             {
                 public string Country { get; set; }
             }
         }
    
         Imports System
         Imports Microsoft.AspNetCore.Identity.EntityFrameworkCore
    
         Namespace [ApplicationName].Models
             Public Partial Class ApplicationUser
                   Public Property Country As String
             End Class
         End Namespace
    
  4. Open the server directory in a terminal.
  5. Run dotnet ef migrations add Country -c ApplicationIdentityDbContext. This will create an Entity Framework Core migration which at runtime will add a Country column to the AspNetUsers table. It will store the Country property values.

Extend the Register User page

In order to capture the values of the custom properties created in the previous step you have to update the Register User page.

  1. Open Register User in design time.
  2. Select the Form component in the design surface.
  3. Add a new string form field. Set its Type to string and Property to Country.

Make the custom properties available at the client-side

Often you would need to use some of the custom properties at the client-side - in the Angular application. You need to expose them in the client-side profile object first.

  1. Go to the server\Authentication directory.
  2. Add a new file called ApplicationPrincipalFactory.Properties.cs. It will specify which properties to be available at the client-side.
  3. Define OnCreatePrincipal partial method with the following body:
         using System.Security.Claims;
         using [ApplicationName].Models;
    
         namespace [ApplicationName].Authentication
         {
             public partial class ApplicationPrincipalFactory
             {
                 partial void OnCreatePrincipal(ClaimsPrincipal principal, ApplicationUser user)
                 {
                     var identity = principal.Identity as ClaimsIdentity;
    
                     if (!string.IsNullOrEmpty(user.Country))
                     {
    
                         // the property will be available at the client-side.
                         identity.AddClaim(new Claim("country", user.Country));
                     }
                 }
             }
         }
    
       Imports System.Security.Claims
       Imports [ApplicationName]
    
       Namespace [ApplicationName].Authentication
             Public Partial Class ApplicationPrincipalFactory
                   Private Partial Sub OnCreatePrincipal(ByVal principal As ClaimsPrincipal, ByVal user As ApplicationUser)
                         Dim identity = TryCast(principal.Identity, ClaimsIdentity)
    
                         If Not String.IsNullOrEmpty(user.Country) Then
                               identity.AddClaim(New Claim("country", user.Country))
                         End If
                   End Sub
             End Class
       End Namespace
    
  4. At the client-side you can now use the Country property:

    <div *ngIf="security.isAuthenticated()">{{ security.profile.country }}</div>

Access current user server-side

You can accces current user server-side using the following code:

public partial class CategoriesController
{
    partial void OnCategoriesRead(ref IQueryable<Category> items)
    {
        var user = this.HttpContext.User.FindFirst(System.Security.Claims.ClaimTypes.Name).Value;
    }
}
Public Partial Class CategoriesController
    Private Partial Sub OnCategoriesRead(ByRef items As IQueryable(Of Category))
        Dim user = Me.HttpContext.User.FindFirst(System.Security.Claims.ClaimTypes.Name).Value
    End Sub
End Class

Services authorization

Radzen can access services with following authorizations:

  • OData: HTTP Basic, OAuth, API Key and Azure AD

  • Swagger and Rest: OAuth, API Key

Custom query parameters are supported for both OAuth and Azure AD authorizations.

Custom UserStore/RoleStore and UserManager

You can write your own custom user store (IUserStore)/role store (IRoleStore) and hook them using Startup class OnConfigureServices partial method:

Startup.Custom.cs

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MyApp.Models;
  
namespace MyApp
{
      public partial class Startup
      {
            partial void OnConfigureServices(IServiceCollection  services)
            {
                   services.AddTransient<IUserStore<ApplicationUser>, CustomUserStore>();
            }
      }
 
      public class CustomUserStore : IUserStore<ApplicationUser>
      {
            public Task<IdentityResult> CreateAsync(ApplicationUser  user, CancellationToken cancellationToken)
            {
                  throw new System.NotImplementedException();
            }

            public Task<IdentityResult> DeleteAsync(ApplicationUser  user, CancellationToken cancellationToken)
            {
                  throw new System.NotImplementedException();
            }
 
            public void Dispose()
            {
                  throw new System.NotImplementedException();
            }
 
            public Task<ApplicationUser> FindByIdAsync(string userId, CancellationToken cancellationToken)
            {
                  throw new System.NotImplementedException();
            }
 
            public Task<ApplicationUser> FindByNameAsync(string  normalizedUserName, CancellationToken cancellationToken)
            {
                  throw new System.NotImplementedException();
            }
 
            public Task<string> GetNormalizedUserNameAsync(ApplicationUser  user, CancellationToken cancellationToken)
            {
                    throw new System.NotImplementedException();
            }
 
            public Task<string> GetUserIdAsync(ApplicationUser  user, CancellationToken cancellationToken)
            {
                   throw new System.NotImplementedException();
            }

            public Task<string> GetUserNameAsync(ApplicationUser  user, CancellationToken cancellationToken)
            {
                  throw new System.NotImplementedException();
            }
 
            public Task SetNormalizedUserNameAsync(ApplicationUser  user, string normalizedName, CancellationToken cancellationToken)
            {
                   throw new System.NotImplementedException();
            }
 
            public Task SetUserNameAsync(ApplicationUser  user, string userName, CancellationToken cancellationToken)
            {
                  throw new System.NotImplementedException();
            }
 
            public Task<IdentityResult> UpdateAsync(ApplicationUser  user, CancellationToken cancellationToken)
            {
                  throw new System.NotImplementedException();
            } 
      }
}

Startup.Custom.vb

Imports System
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks
Imports Microsoft.AspNetCore.Builder
Imports Microsoft.AspNetCore.Identity
Imports Microsoft.Extensions.DependencyInjection
Imports Microsoft.Extensions.Logging
Imports Microsoft.Extensions.Options
Imports MyApp.Models

Namespace MyApp
    Public Partial Class Startup
        Private Partial Sub OnConfigureServices(ByVal services As IServiceCollection)
            services.AddTransient(Of IUserStore(Of ApplicationUser), CustomUserStore)()
        End Sub
    End Class

    Public Class CustomUserStore
        Inherits IUserStore(Of ApplicationUser)

        Public Function CreateAsync(ByVal user As ApplicationUser, ByVal cancellationToken As CancellationToken) As Task(Of IdentityResult)
            Throw New System.NotImplementedException()
        End Function

        Public Function DeleteAsync(ByVal user As ApplicationUser, ByVal cancellationToken As CancellationToken) As Task(Of IdentityResult)
            Throw New System.NotImplementedException()
        End Function

        Public Sub Dispose()
            Throw New System.NotImplementedException()
        End Sub

        Public Function FindByIdAsync(ByVal userId As String, ByVal cancellationToken As CancellationToken) As Task(Of ApplicationUser)
            Throw New System.NotImplementedException()
        End Function

        Public Function FindByNameAsync(ByVal normalizedUserName As String, ByVal cancellationToken As CancellationToken) As Task(Of ApplicationUser)
            Throw New System.NotImplementedException()
        End Function

        Public Function GetNormalizedUserNameAsync(ByVal user As ApplicationUser, ByVal cancellationToken As CancellationToken) As Task(Of String)
            Throw New System.NotImplementedException()
        End Function

        Public Function GetUserIdAsync(ByVal user As ApplicationUser, ByVal cancellationToken As CancellationToken) As Task(Of String)
            Throw New System.NotImplementedException()
        End Function

        Public Function GetUserNameAsync(ByVal user As ApplicationUser, ByVal cancellationToken As CancellationToken) As Task(Of String)
            Throw New System.NotImplementedException()
        End Function

        Public Function SetNormalizedUserNameAsync(ByVal user As ApplicationUser, ByVal normalizedName As String, ByVal cancellationToken As CancellationToken) As Task
            Throw New System.NotImplementedException()
        End Function

        Public Function SetUserNameAsync(ByVal user As ApplicationUser, ByVal userName As String, ByVal cancellationToken As CancellationToken) As Task
            Throw New System.NotImplementedException()
        End Function

        Public Function UpdateAsync(ByVal user As ApplicationUser, ByVal cancellationToken As CancellationToken) As Task(Of IdentityResult)
            Throw New System.NotImplementedException()
        End Function
    End Class
End Namespace

Startup.Custom.cs

namespace MyApp
{
      public partial class Startup
      {
            partial void OnConfigureServices(IServiceCollection  services)
            {
                  services.AddTransient<UserManager<ApplicationUser>, CustomUserManager>();
            }
      }
 
      public class CustomUserManager : UserManager<ApplicationUser>
      {
            public CustomUserManager(IUserStore<ApplicationUser>  store, 
                  IOptions<IdentityOptions>  optionsAccessor, 
                  IPasswordHasher<ApplicationUser>  passwordHasher, 
                  IEnumerable<IUserValidator<ApplicationUser>>  userValidators, 
                  IEnumerable<IPasswordValidator<ApplicationUser>>  passwordValidators, 
                  ILookupNormalizer keyNormalizer, IdentityErrorDescriber errors, 
                  IServiceProvider services, ILogger<UserManager<ApplicationUser>>  logger) : 
                         base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger) 
            {
 
            }
      } 
}

Startup.Custom.vb

Namespace MyApp
    Public Partial Class Startup
        Private Partial Sub OnConfigureServices(ByVal services As IServiceCollection)
            services.AddTransient(Of UserManager(Of ApplicationUser), CustomUserManager)()
        End Sub
    End Class

    Public Class CustomUserManager
        Inherits UserManager(Of ApplicationUser)

        Public Sub New(ByVal store As IUserStore(Of ApplicationUser), ByVal optionsAccessor As IOptions(Of IdentityOptions), ByVal passwordHasher As IPasswordHasher(Of ApplicationUser), ByVal userValidators As IEnumerable(Of IUserValidator(Of ApplicationUser)), ByVal passwordValidators As IEnumerable(Of IPasswordValidator(Of ApplicationUser)), ByVal keyNormalizer As ILookupNormalizer, ByVal errors As IdentityErrorDescriber, ByVal services As IServiceProvider, ByVal logger As ILogger(Of UserManager(Of ApplicationUser)))
            MyBase.New(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger)
        End Sub
    End Class
End Namespace

Get Radzen

Sign-up to download the free 30-day Radzen trial.