ActiveReports 18 .NET Edition
Developers / Create Designer and Viewer Applications / Role Based Authorization with JWT
In This Topic
    Role Based Authorization with JWT
    In This Topic

    You may want to grant permission to a user based on their role for designing and viewing reports (role-based reporting). To do so, you can add tokens to the WebDesigner and Viewer APIs, fetch these tokens, and use them with the report store. This topic explains fetching and using tokens with the JSON Web Token (JWT) authentication method for the ASP.NET Core application, using Microsoft.AspNetCore.Authentication.JwtBearer NuGet package. We recommend that you first read the Microsoft Documentation on the Dependency injection in ASP.NET Core.


    Web Designer Role Based Login using JWT

     

    1. Create an ASP.Net Core Web App (Model-View-Controller) project, e.g. 'WebDesigner_Authorization'.
    2. Add the Microsoft.AspNetCore.Authentication.JwtBearer NuGet package to the project to be able to generate and validate JWT.
    3. Add the following designer and viewer packages to the project.

      MESCIUS.ActiveReports.Aspnetcore.Designer
      MESCIUS.ActiveReports.Aspnetcore.Viewer

    4. Follow Step 3 to Step 5 from the ASP.NET MVC Core Integration topic to use npm packages of designer and viewer.

    5. Create the 'Implementation' folder. In this folder, add the ‘ReportStore.cs’ class file that will contain the IReportStore interface implementation.
      ReportStore.cs
      Copy Code
      using GrapeCity.ActiveReports.Web.Designer;
      using GrapeCity.ActiveReports.Web.Viewer;
      using WebDesigner_Authorization.Models;
      using GrapeCity.ActiveReports.Rendering.Tools;
      
      namespace WebDesigner_Authorization.Implementation
      {
          public class ReportStore : IReportStore
          {
              private static readonly string[] ReportExtensions =
              {
                  ".rdl",
                  ".rdlx",
                  ".rdlx-master",
                  ".rpx"
              };
      
              private readonly Dictionary<string, byte[]> _tempStorage = new Dictionary<string, byte[]>();
              private readonly string _userReportsPath;
              private readonly string _adminReportsPath;
              private readonly IHttpContextAccessor _httpContextAccessor;
              private string userRole { get{return LoginViewModel.GetUserRole(_httpContextAccessor); } }
      
              public ReportStore(DirectoryInfo rootDirectory, IHttpContextAccessor httpContextAccessor)
              {
                  _userReportsPath = rootDirectory.FullName;
                  _adminReportsPath = Path.Combine(rootDirectory.FullName, "Admin");
                  _httpContextAccessor = httpContextAccessor;
              }
              private FileInfo GetReportFile(string reportId)
              {
                  var reportFullPath = Path.Combine(_userReportsPath, reportId);
                  var fileInfo = new FileInfo(reportFullPath);
      
                  if (fileInfo.Exists)
                      return fileInfo;
      
                  if (userRole == "Admin")
                  {
                      reportFullPath = Path.Combine(_adminReportsPath, reportId);
                      fileInfo = new FileInfo(reportFullPath);
      
                      if (fileInfo.Exists)
                          return fileInfo;
                  }
      
                  return null;
              }
      
              public ReportDescriptor GetReportDescriptor(string reportId)
              {
                  if (_tempStorage.ContainsKey(reportId))
                      return new ReportDescriptor(GetReportTypeByExtension(Path.GetExtension(reportId)));
      
                  var fileInfo = GetReportFile(reportId);
                  if (fileInfo == null)
                      throw new GrapeCity.ActiveReports.Web.Designer.ReportNotFoundException();
      
                  return new ReportDescriptor(GetReportTypeByExtension(fileInfo.Extension));
              }
      
              public Stream LoadReport(string reportId)
              {
                  if (_tempStorage.TryGetValue(reportId, out var tempReport))
                      return new MemoryStream(tempReport);
      
                  var file = GetReportFile(reportId);
                  if (file == null)
                      throw new GrapeCity.ActiveReports.Web.Designer.ReportNotFoundException();
      
                  return file.OpenRead();
              }
      
              public string SaveReport(ReportType reportType, string reportId, Stream reportData, SaveSettings settings = SaveSettings.None)
              {
                  string reportPath;
                  string folderPath;
      
                  if ((settings & SaveSettings.IsTemporary) != 0)
                  {
                      var tempName = Guid.NewGuid() + GetReportExtension(reportType);
                      _tempStorage.Add(tempName, reportData.ToArray());
                      return tempName;
                  }
      
                  if (userRole == "Admin")
                  {
                      folderPath = _adminReportsPath;
                      reportPath = Path.Combine(folderPath, reportId);
                  }
                  else
                  {
                      folderPath = _userReportsPath;
                      reportPath = Path.Combine(folderPath, reportId);
                  }
                  Directory.CreateDirectory(folderPath);
      
                  using (var fileStream = new FileStream(reportPath, FileMode.Create, FileAccess.Write))
                  {
                      reportData.CopyTo(fileStream);
                  }
      
                  return reportId;
              }
      
              public string UpdateReport(ReportType reportType, string reportId, Stream reportData)
              {
                  return SaveReport(reportType, reportId, reportData);
              }
      
              public ReportInfo[] ListReports()
              {
                  var reports = new List<ReportInfo>();
      
                  var userReports = new DirectoryInfo(_userReportsPath)
                      .EnumerateFiles("*.*")
                      .Where(fileInfo => ReportExtensions.Any(ext =>
                          fileInfo.Extension.EndsWith(ext, StringComparison.InvariantCultureIgnoreCase)))
                      .Select(fileInfo => new ReportInfo()
                      {
                          Id = fileInfo.Name,
                          Name = fileInfo.Name,
                          ReportType = GetReportTypeByExtension(fileInfo.Extension),
                      }).ToList();
      
                  reports.AddRange(userReports);
      
                  if (userRole == "Admin")
                  {
                      var adminReports = new DirectoryInfo(_adminReportsPath)
                          .EnumerateFiles("*.*")
                          .Where(fileInfo => ReportExtensions.Any(ext =>
                              fileInfo.Extension.EndsWith(ext, StringComparison.InvariantCultureIgnoreCase)))
                          .Select(fileInfo => new ReportInfo()
                          {
                              Id = fileInfo.Name,
                              Name = fileInfo.Name,
                              ReportType = GetReportTypeByExtension(fileInfo.Extension),
                          }).ToList();
      
                      reports.AddRange(adminReports);
                  }
      
                  return reports.ToArray();
              }
      
              private static ReportType GetReportTypeByExtension(string extension)
              {
                  return extension switch
                  {
                      ".rdl" => ReportType.RdlXml,
                      ".rdlx" => ReportType.RdlXml,
                      ".rdlx-master" => ReportType.RdlMasterXml,
                      ".rpx" => ReportType.RpxXml,
                      _ => throw new ArgumentOutOfRangeException(nameof(extension), extension, null)
                  };
              }
      
              private static string GetReportExtension(ReportType type)
              {
                  return type switch
                  {
                      ReportType.RdlXml => ".rdlx",
                      ReportType.RdlMasterXml => ".rdlx-master",
                      ReportType.RpxXml => ".rpx",
                      _ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
                  };
              }
      
              public void DeleteReport(string reportId)
              {
                  if (_tempStorage.ContainsKey(reportId))
                  {
                      _tempStorage.Remove(reportId);
                      return;
                  }
                  var file = GetReportFile(reportId);
                  if (file != null && file.Exists)
                      file.Delete();
              }
          }
      }
      
         
    6. In the 'Implementation' folder, add the ‘ResourceProvider.cs’ class file that will contain the IResourceRepositoryProvider interface implementation.

      ResourceProvider.cs
      Copy Code
      using GrapeCity.ActiveReports.Web.Designer;
      using GrapeCity.ActiveReports;
      using GrapeCity.ActiveReports.Rendering.Tools;
      using WebDesigner_Authorization.Models;
      
      namespace WebDesigner_Authorization.Implementation
      {
          public class ResourceProvider : IResourceRepositoryProvider
          {
              private const string SharedDataSourceExtension = ".rdsx";
      
              private readonly DirectoryInfo _userResourcesPath;
              private readonly DirectoryInfo _adminResourcesPath;
              private readonly IHttpContextAccessor _httpContextAccessor;
              private string userRole { get { return LoginViewModel.GetUserRole(_httpContextAccessor); } }
      
              public ResourceProvider(DirectoryInfo rootDirectory, IHttpContextAccessor httpContextAccessor)
              {
                  _userResourcesPath = rootDirectory;
                  _adminResourcesPath = new DirectoryInfo(Path.Combine(rootDirectory.FullName, "Admin"));
                  _httpContextAccessor = httpContextAccessor;
              }
      
      
              public Stream GetResource(ResourceInfo resource)
              {
                  var role = userRole;
                  var filePath = Path.Combine(_userResourcesPath.FullName, resource.Name);
                  var file = new FileInfo(filePath);
      
                  if (!file.Exists && role == "Admin")
                  {
                      filePath = Path.Combine(_adminResourcesPath.FullName, resource.Name);
                      file = new FileInfo(filePath);
                  }
      
                  if (!file.Exists)
                      return null;
      
                  return file.OpenRead();
              }
      
              public ResourceDescriptor[] ListResources(ResourceType resourceType)
              {
                  var role = userRole;
                  var resources = new List<ResourceDescriptor>();
      
                  if (resourceType == ResourceType.SharedDataSource)
                  {
                      var userResources = _userResourcesPath
                          .EnumerateFiles("*" + SharedDataSourceExtension)
                          .Select(fileInfo => CreateResourceDescriptor(fileInfo))
                          .ToList();
      
                      resources.AddRange(userResources);
      
                      if (role == "Admin")
                      {
                          var adminResources = _adminResourcesPath
                              .EnumerateFiles("*" + SharedDataSourceExtension)
                              .Select(fileInfo => CreateResourceDescriptor(fileInfo))
                              .ToList();
      
                          resources.AddRange(adminResources);
                      }
                  }
      
                  return resources.ToArray();
              }
      
              private SharedDataSourceResourceDescriptor CreateResourceDescriptor(FileInfo fileInfo)
              {
                  using var stream = fileInfo.OpenRead();
                  var dataSource = DataSourceTools.LoadSharedDataSource(stream);
      
                  return new SharedDataSourceResourceDescriptor()
                  {
                      Id = fileInfo.Name,
                      Name = fileInfo.Name,
                      Type = dataSource.ConnectionProperties.DataProvider
                  };
              }
      
              public ResourceDescriptor[] DescribeResources(ResourceInfo[] resources)
              {
                  return Enumerable.Empty<ResourceDescriptor>().ToArray();
              }
          }
      }
      
    7. In the 'Models' folder, add the following two class files, 'User.cs' and 'LoginViewModel.cs', to contain the data for a user and role-based login.

      User.cs
      Copy Code
      namespace WebDesigner_Authorization.Models
      {
          public class User
          {
              public string Login { get; set; }
              public string Password { get; set; }
              public string Role { get; set; }
      
              public User(string login, string password, string role)
              {
                  Login = login;
                  Password = password;
                  Role = role;
              }
          }
      }
      
      LoginViewModel.cs
      Copy Code
      namespace WebDesigner_Authorization.Models
      {
          public class LoginViewModel
          {
              public string Username { get; set; }
              public string Password { get; set; }
      
              public static string GetUserRole(IHttpContextAccessor httpContextAccessor)
              {
                  return httpContextAccessor?.HttpContext?.Session.GetString("Role");
              }
          }
      }
      
    8. Create the 'Services' folder. In this folder, add the 'AuthService.cs' class file to store the login credentials, define roles for users, such as "admin" and "manager. The 'AuthService' class generates JWT using JwtSecurityTokenHandler.

      AuthService.cs
      Copy Code
      using Microsoft.IdentityModel.Tokens;
      using System.IdentityModel.Tokens.Jwt;
      using System.Security.Claims;
      using System.Text;
      using WebDesigner_Authorization.Models;
      
      namespace WebDesigner_Authorization.Services
      {
          public class AuthService
          {
              public class AuthOptions
              {
                  public const string ISSUER = "MyAuthServer";
                  public const string AUDIENCE = "MyAuthClient";
                  public const string KEY = "mysupersecret_secretkey_12345678";
                  public const int LIFETIME = 2;
      
                  public static SymmetricSecurityKey GetSymmetricSecurityKey() =>
                      new SymmetricSecurityKey(Encoding.UTF8.GetBytes(KEY));
              }
              private readonly List<User> _users = new List<User>
              {
                  new User("test1", "12345", "Admin"),
                  new User("test2", "53421", "Manager")
              };
      
              public async Task<string> Login(string username, string password)
              {
                  var user = _users.FirstOrDefault(u => u.Login == username && u.Password == password);
      
                  if (user == null)
                      return null;
      
                  return GenerateToken(user.Login, user.Role);
              }
      
              public string GenerateToken(string username, string role)
              {
                  var tokenHandler = new JwtSecurityTokenHandler();
                  var key = Encoding.ASCII.GetBytes(AuthOptions.KEY);
                  var tokenDescriptor = new SecurityTokenDescriptor
                  {
                      Subject = new ClaimsIdentity(new Claim[]
                      {
                          new Claim(ClaimTypes.Name, username),
                          new Claim(ClaimTypes.Role, role)
                      }),
                      Expires = DateTime.UtcNow.AddMinutes(AuthOptions.LIFETIME),
                      SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
                  };
                  var token = tokenHandler.CreateToken(tokenDescriptor);
                   var tokenString = tokenHandler.WriteToken(token);
                  return tokenString;
              }
      
              public string GetRoleFromToken(string token)
              {
                  var tokenHandler = new JwtSecurityTokenHandler();
                  var jwtToken = tokenHandler.ReadToken(token) as JwtSecurityToken;
                  if (jwtToken == null)
                  {
                      Console.WriteLine("Invalid JWT Token");
                      return null;
                  }
                  var roleClaim = jwtToken.Claims.FirstOrDefault(claim => claim.Type == "role").Value;
                  return roleClaim;
              }
          }
      }
      
    9. In the 'Controllers' folder, update 'HomeController.cs'.

      HomeController.cs
      Copy Code
      using Microsoft.AspNetCore.Mvc;
      
      namespace WebDesigner_Authorization.Controllers
      {
        
          public class HomeController : Controller
          {
              public IActionResult Index()
              {
                  ViewData["UserRole"] = HttpContext.Session.GetString("Role");
                  ViewData["Token"] = HttpContext.Session.GetString("Token");
                  return View();
              }
          }
      }
      
    10. In the 'Controllers' folder, add 'LoginController.cs'.

      LoginController.cs
      Copy Code
      using Microsoft.AspNetCore.Mvc;
      using WebDesigner_Authorization.Models;
      using WebDesigner_Authorization.Services;
      using Microsoft.AspNetCore.Http;
      using System.Threading.Tasks;
      
      namespace WebDesigner_Authorization.Controllers
      {
          public class LoginController : Controller
          {
              private readonly AuthService _authService;
      
              public LoginController(AuthService authService)
              {
                  _authService = authService;
              }
      
              public IActionResult Index()
              {
                  return View(new LoginViewModel());
              }
      
              [HttpPost]
              public async Task<IActionResult> Login(LoginViewModel model)
              {
                  if (!ModelState.IsValid)
                  {
                      return View("Index", model);
                  }
      
                  var token = await _authService.Login(model.Username, model.Password);
                  if (token == null)
                  {
                      ModelState.AddModelError(string.Empty, "Invalid username or password");
                      return View("Index", model);
                  }
                  var role = _authService.GetRoleFromToken(token);
                  HttpContext.Session.SetString("Token", token);
                  HttpContext.Session.SetString("Role", role);
                  return RedirectToAction("Index", "Home");
              }
      
              [HttpPost]
              [ValidateAntiForgeryToken]
              public IActionResult Logout()
              {
                  HttpContext.Session.Clear();
                  return RedirectToAction("Index", "Login");
              }
          }
      }
      
    11. Update the Views\Home\Index.cshtml page.

      Index.cshtml
      Copy Code
      @using Microsoft.AspNetCore.Http
      @using WebDesigner_Authorization.Services
      @inject AuthService _authService
      @{
          ViewData["Title"] = "Home";
          var userRole = ViewData["UserRole"]?.ToString();
          var token = ViewData["Token"]?.ToString();
      }
      
      @if (!string.IsNullOrEmpty(userRole))
      {
          <h2>Welcome, @userRole</h2>
      }
      
      <!-- Logout button -->
      <form id="logoutForm" method="post" action="/Login/Logout">
          <button type="submit">Logout</button>
          @Html.AntiForgeryToken()
      </form>
      
      <link rel="stylesheet" href="~/css/web-designer.css">
      <link rel="stylesheet" href="~/css/jsViewer.min.css">
      <div>
          <div id="ar-web-designer" class="ar-web-designer">
              <span class="ar-web-designer__loader"><b>AR Web Designer</b></span>
          </div>
      </div>
      
      <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
      <script src="~/js/web-designer.js"></script>
      <script src="~/js/jsViewer.min.js"></script>
      <script>
          let viewer = null;
          let serverUrl = getServerUrl();
      
          function getServerUrl() {
              let baseUrl = 'api';
              let virtualDirName = document.getElementById('virtualDir');
      
              if (virtualDirName && virtualDirName.href != window.location.origin + '/') {
                  return virtualDirName.href + baseUrl;
              }
      
              return baseUrl;
          }
      
          GrapeCity.ActiveReports.Designer.create('#ar-web-designer', {
              server: {
                  url: serverUrl
              },
              storeUnsavedReport: false,
              rpx: { enabled: true },
              appBar: { openButton: { visible: true } },
              data: { dataSets: { canModify: true }, dataSources: { canModify: true, shared: { enabled: true } } },
              preview: {
                  openViewer: (options) => {
                      if (viewer) {
                          viewer.openReport(options.documentInfo.id);
                          return;
                      }
                      viewer = GrapeCity.ActiveReports.JSViewer.create({
                          element: '#' + options.element,
                          reportService: {
                              url: 'api/reporting',
                          },
                          reportID: options.documentInfo.id,
                          settings: {
                              zoomType: 'FitPage',
                          },
                          theme: options.theme
                      });
                  }
              },
              server: {
                  onBeforeRequest: (init) => {
                      const headers = {
                          'Authorization': "Bearer: " + '@token',
                      };
                      init.headers = {
                          ...init.headers,
                          ...headers
                      };
                      return init;
                  }
              }
          });
      </script>
      
    12. Create the 'resources' folder in your sample project root. You can store your existing reports, themes, and images in this folder.

    13. Update the 'Program.cs' file as follows to implement the JWT authentication.

      • Configure AuthenticationMiddleware (app.UseAuthentication) and AuthorizationMiddleware (app.UseAuthorization). 
      • Validate the JWT using AddJWTBearer and extract the data hidden in that token.
      • Store the user's roles in the JWT claims when generating the token at login.
        Program.cs
        Copy Code
        using GrapeCity.ActiveReports.Aspnetcore.Viewer;
        using GrapeCity.ActiveReports.Aspnetcore.Designer;
        using Microsoft.AspNetCore.Authentication.JwtBearer;
        using WebDesigner_Authorization.Services;
        using Microsoft.IdentityModel.Tokens;
        using Microsoft.Extensions.FileProviders;
        using static WebDesigner_Authorization.Services.AuthService;
        using GrapeCity.ActiveReports.Web.Designer;
        using WebDesigner_Authorization.Implementation;
        
        DirectoryInfo resourcesRootDirectory = new DirectoryInfo(Path.Combine(Directory.GetCurrentDirectory(), "resources"));
        
        var builder = WebApplication.CreateBuilder(args);
        builder.Services.AddControllersWithViews();
        builder.Services.AddHttpContextAccessor();
        builder.Services.AddSession(); 
        builder.Services.AddScoped<AuthService>();
        
        builder.Services.AddAuthorization();
        
        builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
        {
            options.RequireHttpsMetadata = false;
            options.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuer = true,
                ValidIssuer = AuthOptions.ISSUER,
                ValidateAudience = true,
                ValidAudience = AuthOptions.AUDIENCE,
                ValidateLifetime = true,
                IssuerSigningKey = AuthOptions.GetSymmetricSecurityKey(),
                ValidateIssuerSigningKey = true,
            };
        });
        builder.Services.AddReportViewer();
        builder.Services.AddReportDesigner();
        
        //dependency injection
        builder.Services.AddSingleton<IReportStore>(sp => new ReportStore(resourcesRootDirectory, sp.GetRequiredService<IHttpContextAccessor>()));
        builder.Services.AddSingleton<IResourceRepositoryProvider>(sp => new ResourceProvider(resourcesRootDirectory, sp.GetRequiredService<IHttpContextAccessor>()));
        
        var app = builder.Build();
        if (!app.Environment.IsDevelopment())
        {
            app.UseExceptionHandler("/Home/Error");
            app.UseHsts();
        }
        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseRouting();
        app.UseAuthentication(); 
        app.UseAuthorization();
        app.UseSession();
        app.MapControllerRoute(
            name: "default",
            pattern: "{controller=Login}/{action=Index}/{id?}");
        app.UseReportDesigner(config =>
        {
            var reportStore = app.Services.GetRequiredService<IReportStore>();
            var resourceProvider = app.Services.GetRequiredService<IResourceRepositoryProvider>();
            config.UseReportsProvider(reportStore);
            config.UseResourcesProvider(resourceProvider);
        });
        
        
        app.UseDefaultFiles(new DefaultFilesOptions
        {
            FileProvider = new PhysicalFileProvider(
                Path.Combine(Directory.GetCurrentDirectory(), "wwwroot")),
            DefaultFileNames = new List<string> { "index.html" }
        });
        app.UseStaticFiles();
        app.Run();
        
    14. Build and run the application. You will see a login page where you should enter the login details as defined in AuthService.cs.
    See Also