Client Application One using ASP.NET Core MVC

Client Application One using ASP.NET Core MVC:

In this article, I will discuss implementing Client Application One using the ASP.NET Core MVC Project. Please read our previous article about implementing the Resource Server Application using the ASP.NET Core Web API. This is the Third Application of our SSO Implementation.

Implementing Client Application One using ASP.NET Core MVC

This client application (built using ASP.NET Core MVC) communicates with the Authentication Server to allow user registration, login, logout, and SSO token validation. Once the user is logged in, the application stores the JWT token in session storage to access protected endpoints on a Resource Server. This application will provide the following features:

  • User Registration: Allows users to register via the Authentication Server API.
  • Login: Authenticates the user using the Authentication Server API. On successful login, a JWT token is returned and stored securely (in session storage) to access protected services.
  • Logout: Logs the user out by removing the JWT token from session storage.
  • SSO Endpoint: Provides an endpoint that accepts an SSO token (from another application), validates it with the Authentication Server API, and, if valid, logs in the user by storing the access token. The user is redirected to the login page if the token is invalid or expired.
Creating a New ASP.NET Core MVC Project:

Open Visual Studio and create a new ASP.NET Core MVC project named ClientApplicationOne.

Modifying appSettings.json file:

In this application, we will consume the services provided by Authentication Server and Resource Server. Instead of hard coding the Authentication Server and Resource Server URLs, we will store the base URLs of these two servers in the appsettings.json file. So, please modify the appSettings.json file as follows:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "AuthenticationServer": {
    "BaseUrl": "https://localhost:7042" //Please replace with the URL of your Authentication Server
  },
  "ResourceServer": {
    "BaseUrl": "https://localhost:7254" //Please replace with the URL of your Resource Server
  }
}
Creating Models and View Models

Next, we need to create the models and view models. These classes define the data structures the application uses both for capturing user input in forms and sending data to the authentication/resource servers.

RegisterViewModel

Create a class file named RegisterViewModel.cs within the Models folder, then copy and paste the following code. This represents the data required when a new user registers. It contains properties for Username, Email, and Password (all marked with [Required]). It will be used by the Register view to bind form input to this model. 

using System.ComponentModel.DataAnnotations;
namespace ClientApplicationOne.Models
{
    public class RegisterViewModel
    {
        [Required]
        public string Username { get; set; }
        [Required]
        public string Email { get; set; }
        [Required]
        public string Password { get; set; }
    }
}
LoginViewModel

Create a class file named LoginViewModel.cs within the Models folder, and then copy and paste the following code. This represents the user credentials needed for login. It contains properties for Username and Password (both required). It is used by the Login view to capture user input.

using System.ComponentModel.DataAnnotations;
namespace ClientApplicationOne.Models
{
    public class LoginViewModel
    {
        [Required]
        public string Username { get; set; }
        [Required]
        public string Password { get; set; }
    }
}
ValidateSSOTokenModel

Create a class file named ValidateSSOTokenModel.cs within the Models folder, and then copy and paste the following code. This Model holds another application’s Single Sign-On (SSO) token. It contains a single property, SSOToken, that stores the token string.

namespace ClientApplicationOne.Models
{
    public class ValidateSSOTokenModel
    {
        public string SSOToken { get; set; }
    }
}
Define Response Models

These models represent the data received from the Authentication Server or Resource Server APIs. That means these classes are designed to mirror the JSON responses returned from the Authentication Server and the Resource Server.

LoginResponseModel

Create a new file named LoginResponseModel.cs in your Models folder. These models represent the response from the Authentication Server after a successful login. It contains a Token property that holds the JWT (JSON Web Token). It will be used in the login action method to extract and store the JWT token during the session.

namespace ClientApplicationOne.Models
{
    // Represents the response from the login endpoint.
    public class LoginResponseModel
    {
        public string Token { get; set; }
    }
}
UserDetailsModel

Create a new file named UserDetailsModel.cs in your Models folder. These models encapsulate additional user details returned with the SSO token validation. It contains properties like UserId, Username, and Email. It helps provide a more comprehensive user context when SSO token validation is successful.

namespace ClientApplicationOne.Models
{ 
    // Contains user details returned with the SSO token validation.
    public class UserDetailsModel
    {
        public string UserId { get; set; }
        public string Username { get; set; }
        public string Email { get; set; }
    }
}
ValidateSSOResponseModel

Create a new file named ValidateSSOResponseModel.cs in your Models folder. These models represent the response from the Authentication Server when validating an SSO token. It contains a Token property for the JWT and a UserDetails property (of type UserDetailsModel). It will be used in the ValidateSSO action method to store both the token and the username in session upon successful SSO validation.

namespace ClientApplicationOne.Models
{
    // Represents the response from the SSO token validation endpoint.
    public class ValidateSSOResponseModel
    {
        public string Token { get; set; }
        public UserDetailsModel UserDetails { get; set; }
    }
}
DataResponseModel

Create a new file named DataResponseModel.cs in your Models folder. These models represent the response from the Resource Server’s endpoints (both public and protected). It contains a single Message property. It will be used in the actions to display the data (or messages) fetched from the Resource Server in the corresponding views.

namespace ClientApplicationOne.Models
{ 
    // Represents the response from the Resource Server endpoints (public/protected).
    public class DataResponseModel
    {
        public string Message { get; set; }
    }
}
AuthenticationService.cs

The following service will interact with the Authentication Server’s RESTful API to handle user Registration, Login, and SSO Token Validation. So, create a class file named AuthenticationService.cs within the Models folder and copy and paste the following code.

using System.Text;
using System.Text.Json;

namespace ClientApplicationOne.Models
{
    public class AuthenticationService
    {
        // Factory for creating HttpClient instances.
        private readonly IHttpClientFactory _httpClientFactory;

        // Provides access to configuration settings.
        private readonly IConfiguration _configuration;

        // Base URL for the Authentication Server (retrieved from configuration).
        private readonly string _authServerUrl;

        // Constructor injects the IHttpClientFactory and IConfiguration dependencies.
        public AuthenticationService(IHttpClientFactory httpClientFactory, IConfiguration configuration)
        {
            _httpClientFactory = httpClientFactory;
            _configuration = configuration;
            // Retrieve the Authentication Server URL from configuration; if not found, default to "https://localhost:7125".
            _authServerUrl = _configuration["AuthenticationServer:BaseUrl"] ?? "https://localhost:7125";
        }

        // Registers a new user by sending a POST request to the Authentication Server.
        public async Task<HttpResponseMessage> RegisterUserAsync(RegisterViewModel model)
        {
            // Create a new HttpClient instance.
            var client = _httpClientFactory.CreateClient();

            // Construct the URL for the register endpoint.
            var url = $"{_authServerUrl}/api/Authentication/Register";

            // Serialize the model to JSON format.
            var jsonContent = JsonSerializer.Serialize(model);

            // Create HTTP content with the JSON payload and proper headers.
            var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");

            // Send the POST request asynchronously and return the response.
            return await client.PostAsync(url, content);
        }

        // Logs in a user by sending a POST request with the credentials to the Authentication Server.
        public async Task<HttpResponseMessage> LoginUserAsync(LoginViewModel model)
        {
            // Create a new HttpClient instance.
            var client = _httpClientFactory.CreateClient();

            // Construct the URL for the login endpoint.
            var url = $"{_authServerUrl}/api/Authentication/Login";

            // Serialize the payload to JSON.
            var jsonContent = JsonSerializer.Serialize(model);

            // Create HTTP content with the JSON payload.
            var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");

            // Send the POST request asynchronously and return the response.
            return await client.PostAsync(url, content);
        }

        // Validates an SSO token by sending it to the Authentication Server.
        public async Task<HttpResponseMessage> ValidateSSOTokenAsync(ValidateSSOTokenModel model)
        {
            // Create a new HttpClient instance.
            var client = _httpClientFactory.CreateClient();

            // Construct the URL for the SSO token validation endpoint.
            var url = $"{_authServerUrl}/api/Authentication/ValidateSSOToken";

            // Serialize the payload to JSON.
            var jsonContent = JsonSerializer.Serialize(model);

            // Create HTTP content with the JSON payload.
            var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");

            // Send the POST request asynchronously and return the response.
            return await client.PostAsync(url, content);
        }
    }
}
Configure Http Client, Session and Authentication Services in Program.cs

As we will store the JWT Token in the Session, please ensure session management is configured in the Program.cs file. Also, add the AuthenticationService to the built-in dependency injection container. So, modify the Program.cs class file as follows:

using ClientApplicationOne.Models;
namespace ClientApplicationOne
{
    public class Program
    {
        public static void Main(string[] args)
        {
            // Create a WebApplicationBuilder, which provides a way to configure services and middleware.
            var builder = WebApplication.CreateBuilder(args);

            // Add services to the dependency injection container.
            // Add MVC services with support for views and controllers.
            builder.Services.AddControllersWithViews();

            // Add distributed memory cache and configure session management.
            builder.Services.AddDistributedMemoryCache();
            builder.Services.AddSession(options =>
            {
                options.IdleTimeout = TimeSpan.FromMinutes(30); // Session timeout period.
                options.Cookie.HttpOnly = true; // Prevents access to the cookie via client-side scripts.
                options.Cookie.IsEssential = true; // Marks the cookie as essential for GDPR compliance.
            });

            // Register the HttpClient factory and the AuthenticationService.
            builder.Services.AddHttpClient();
            builder.Services.AddScoped<AuthenticationService>();

            // Build the WebApplication object that will handle HTTP requests.
            var app = builder.Build();

            // Configure the HTTP request pipeline (middleware).
            // This part of the code defines how requests are processed by the application.
            if (!app.Environment.IsDevelopment())
            {
                app.UseExceptionHandler("/Home/Error"); 
                app.UseHsts(); 
            }

            // Enforce the use of HTTPS by redirecting all HTTP requests to HTTPS.
            app.UseHttpsRedirection();

            // Serve static files (CSS, JS, images) from the "wwwroot" folder.
            app.UseStaticFiles();

            // Enable routing, allowing the app to map incoming requests to route definitions (controllers/actions).
            app.UseRouting();

            // Enable session state for the application, allowing the app to store user data between requests.
            app.UseSession();

            // Enable authentication middleware to check if the user is authenticated.
            app.UseAuthentication();

            // Enable authorization middleware to enforce user role and permission-based access control.
            app.UseAuthorization();

            app.MapControllerRoute(
                name: "default", 
                pattern: "{controller=Home}/{action=Index}/{id?}"); 

            app.Run();
        }
    }
}
Authentication Service Methods:

Encapsulates all communication with the external Authentication Server.

  • RegisterUserAsync: Serializes a RegisterViewModel into JSON. Sends a POST request to the Authentication Server’s registration endpoint.
  • LoginUserAsync: Serializes a LoginViewModel and sends a POST request to log in to the user. Expect a response containing a JWT token.
  • ValidateSSOTokenAsync: Serializes a ValidateSSOTokenModel and sends it to the SSO token validation endpoint. Processes the response containing a token and user details.

Note: We will register the Authentication Service in the DI (dependency injection) container in Program.cs, and then it will be injected into the AccountController to perform authentication-related operations.

Creating Account Controller:

Next, create a new Empty MVC Controller named AccountController within the Controllers folder and copy and paste the following code. The following controller provides action methods to perform the User Registration, Login, and SSO Token Validation operations.

using Microsoft.AspNetCore.Mvc;
using ClientApplicationOne.Models;
using System.Text.Json;

namespace ClientApplicationOne.Controllers
{
    public class AccountController : Controller
    {
        private readonly AuthenticationService _authService;

        public AccountController(AuthenticationService authService)
        {
            _authService = authService;
        }

        // GET: /Account/Register
        [HttpGet]
        public IActionResult Register()
        {
            return View();
        }

        // POST: /Account/Register
        [HttpPost]
        public async Task<IActionResult> Register(RegisterViewModel model)
        {
            if (!ModelState.IsValid)
            {
                return View(model);
            }

            var response = await _authService.RegisterUserAsync(model);
            if (response.IsSuccessStatusCode)
            {
                return RedirectToAction("Login");
            }

            ModelState.AddModelError(string.Empty, "Registration Failed.");
            return View();
        }

        // GET: /Account/Login
        [HttpGet]
        public IActionResult Login()
        {
            return View();
        }

        // POST: /Account/Login
        [HttpPost]
        public async Task<IActionResult> Login(LoginViewModel model)
        {
            if (!ModelState.IsValid)
            {
                return View(model);
            }

            var response = await _authService.LoginUserAsync(model);
            if (response.IsSuccessStatusCode)
            {
                var responseContent = await response.Content.ReadAsStringAsync();
                try
                {
                    // Deserialize the response into a LoginResponseModel.
                    var loginResponse = JsonSerializer.Deserialize<LoginResponseModel>(responseContent);

                    if (loginResponse != null && !string.IsNullOrEmpty(loginResponse.Token))
                    {
                        // Store the token and username in session.
                        HttpContext.Session.SetString("JWT", loginResponse.Token);
                        HttpContext.Session.SetString("Username", model.Username);
                        return RedirectToAction("Index", "Home");
                    }
                    else
                    {
                        ModelState.AddModelError(string.Empty, "Token not found in the response.");
                        return View();
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Error parsing response: " + ex.Message);
                    ModelState.AddModelError(string.Empty, "Failed to parse the response.");
                    return View();
                }
            }

            ModelState.AddModelError(string.Empty, "Login failed.");
            return View();
        }

        // POST: /Account/Logout
        [HttpPost]
        public IActionResult Logout()
        {
            HttpContext.Session.Remove("JWT");
            HttpContext.Session.Remove("Username");
            return RedirectToAction("Index", "Home");
        }

        // GET: /validate-sso?ssoToken=...
        // Validates the SSO token received from another application.
        [HttpGet("validate-sso")]
        public async Task<IActionResult> ValidateSSO([FromQuery] string ssoToken)
        {
            if (string.IsNullOrEmpty(ssoToken))
            {
                return RedirectToAction("Login");
            }

            ValidateSSOTokenModel model = new ValidateSSOTokenModel()
            {
                SSOToken = ssoToken
            };
            var response = await _authService.ValidateSSOTokenAsync(model);
            if (response.IsSuccessStatusCode)
            {
                var responseContent = await response.Content.ReadAsStringAsync();
                try
                {
                    // Deserialize the response into a ValidateSSOResponseModel.
                    var ssoResponse = JsonSerializer.Deserialize<ValidateSSOResponseModel>(responseContent);

                    if (ssoResponse != null)
                    {
                        if (!string.IsNullOrEmpty(ssoResponse.Token))
                        {
                            HttpContext.Session.SetString("JWT", ssoResponse.Token);
                        }

                        if (ssoResponse.UserDetails != null && !string.IsNullOrEmpty(ssoResponse.UserDetails.Username))
                        {
                            HttpContext.Session.SetString("Username", ssoResponse.UserDetails.Username);
                        }

                        return RedirectToAction("Index", "Home");
                    }
                    else
                    {
                        return RedirectToAction("Login");
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Error parsing response: " + ex.Message);
                    return RedirectToAction("Login");
                }
            }

            return RedirectToAction("Login");
        }
    }
}
Account Controller Action Methods

The Account Controller Handles user account–related actions (registration, login, logout, SSO validation).

  • GET Register: Returns the registration view. Displays a form for new users to register.
  • POST Register: Validates the submitted RegisterViewModel. Calls AuthenticationService.RegisterUserAsync to send registration data to the Authentication Server. On success, it redirects to the login page; otherwise, it returns the same view with an error.
  • GET Login: Returns the login view. Displays a form where users can enter their credentials.
  • POST Login: Validates the submitted LoginViewModel. Calls AuthenticationService.LoginUserAsync to log in to the user. Deserializes the response to extract the JWT token. If successful, it stores the JWT and username in the session and redirects to the Home page; otherwise, it shows an error message.
  • POST Logout: Clears the session by removing the JWT and username. Redirects the user to the Home page.
  • GET ValidateSSO: Accepts an SSO token via a query parameter. Uses AuthenticationService.ValidateSSOTokenAsync to validate the token. On successful validation, the token and username are stored in the session and redirected to Home; if not, they are redirected to the login page.
Creating the Views:

Let us proceed and implement the Registration and Login Views, which we require to register a new user and allow a registered user to log into our application.

Register View (Views/Account/Register.cshtml)

Create a view named Register.cshtml within the Views/Account folder and copy and paste the following code. This View provides the HTML form for user registration. It has form fields for Username, Email, and Password (bound to RegisterViewModel). It will post the data to the Register action in the Account Controller on form submission.

@model ClientApplicationOne.Models.RegisterViewModel
@{
    ViewData["Title"] = "Register";
}

<h1 class="text-center">@ViewData["Title"]</h1>

<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-6">
            <form asp-action="Register" asp-controller="Account" method="post" class="needs-validation">
                <div class="form-group mb-3">
                    <label asp-for="Username" class="form-label"></label>
                    <input asp-for="Username" class="form-control" placeholder="Enter your username" required />
                    <span asp-validation-for="Username" class="text-danger"></span>
                </div>

                <div class="form-group mb-3">
                    <label asp-for="Email" class="form-label"></label>
                    <input asp-for="Email" class="form-control" placeholder="Enter your email" type="email" required />
                    <span asp-validation-for="Email" class="text-danger"></span>
                </div>

                <div class="form-group mb-3">
                    <label asp-for="Password" class="form-label"></label>
                    <input asp-for="Password" class="form-control" placeholder="Enter your password" type="password" required />
                    <span asp-validation-for="Password" class="text-danger"></span>
                </div>

                <div class="d-grid">
                    <button type="submit" class="btn btn-primary btn-block">Register</button>
                </div>
            </form>
        </div>
    </div>
</div>
Login View (Views/Account/Login.cshtml)

Create a view named Login.cshtml within the Views/Account folder and copy and paste the following code. This View provides the HTML form for user login. It has form fields for Username and Password (bound to LoginViewModel). This view displays validation messages and posts data to the login action method in the Account Controller when the form is submitted.

@model ClientApplicationOne.Models.LoginViewModel
@{
    ViewData["Title"] = "Login";
}

<h1 class="text-center">@ViewData["Title"]</h1>

<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-6">
            <form asp-action="Login" asp-controller="Account" method="post" class="needs-validation">
                <div class="form-group mb-3">
                    <label asp-for="Username" class="form-label"></label>
                    <input asp-for="Username" class="form-control" placeholder="Enter your username" required />
                    <span asp-validation-for="Username" class="text-danger"></span>
                </div>

                <div class="form-group mb-3">
                    <label asp-for="Password" class="form-label"></label>
                    <input asp-for="Password" class="form-control" placeholder="Enter your password" type="password" required />
                    <span asp-validation-for="Password" class="text-danger"></span>
                </div>

                <div class="d-grid">
                    <button type="submit" class="btn btn-primary btn-block">Login</button>
                </div>
            </form>
        </div>
    </div>
</div>
Modifying the Home Controller:

Let us modify the HomeController to include the Index, GetPublicData, and GetProtectedData action methods. This will call the Resource Server’s public and protected endpoints. So, please modify the Home Controller as follows. The Resource Server’s public API we can access without JWT Token and we need a JWT token to access the Protected endpoint.

using Microsoft.AspNetCore.Mvc;
using System.Text.Json;
using ClientApplicationOne.Models;

namespace ClientApplicationOne.Controllers
{
    public class HomeController : Controller
    {
        private readonly IConfiguration _configuration;
        private readonly string _resourceServerUrl;
        private readonly IHttpClientFactory _httpClientFactory;

        public HomeController(IConfiguration configuration, IHttpClientFactory httpClientFactory)
        {
            _configuration = configuration;
            _resourceServerUrl = _configuration["ResourceServer:BaseUrl"] ?? "https://localhost:7206";
            _httpClientFactory = httpClientFactory;
        }

        // GET: /Home/Index
        public IActionResult Index()
        {
            return View();
        }

        // GET: /Home/GetPublicData
        [HttpGet]
        public async Task<IActionResult> GetPublicData()
        {
            var client = _httpClientFactory.CreateClient();
            var url = $"{_resourceServerUrl}/api/demo/public-data";
            var response = await client.GetAsync(url);

            if (response.IsSuccessStatusCode)
            {
                var responseContent = await response.Content.ReadAsStringAsync();
                try
                {
                    // Deserialize the response into a DataResponseModel.
                    var dataResponse = JsonSerializer.Deserialize<DataResponseModel>(responseContent);

                    if (dataResponse != null)
                    {
                        ViewBag.PublicData = dataResponse.Message;
                    }
                    else
                    {
                        ViewBag.PublicData = "No message found in the response.";
                    }
                }
                catch (Exception ex)
                {
                    ViewBag.Error = "Error parsing the response: " + ex.Message;
                }
                return View();
            }

            ViewBag.Error = "Failed to retrieve public data.";
            return View();
        }

        // GET: /Home/GetProtectedData
        [HttpGet]
        public async Task<IActionResult> GetProtectedData()
        {
            var token = HttpContext.Session.GetString("JWT");
            if (string.IsNullOrEmpty(token))
            {
                return RedirectToAction("Login", "Account");
            }

            var client = _httpClientFactory.CreateClient();
            var url = $"{_resourceServerUrl}/api/demo/protected-data";
            var request = new HttpRequestMessage(HttpMethod.Get, url);
            request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
            var response = await client.SendAsync(request);

            if (response.IsSuccessStatusCode)
            {
                var responseContent = await response.Content.ReadAsStringAsync();
                try
                {
                    // Deserialize the response into a DataResponseModel.
                    var dataResponse = JsonSerializer.Deserialize<DataResponseModel>(responseContent);

                    if (dataResponse != null)
                    {
                        ViewBag.ProtectedData = dataResponse.Message;
                    }
                    else
                    {
                        ViewBag.ProtectedData = "No message found in the response.";
                    }
                }
                catch (Exception ex)
                {
                    ViewBag.Error = "Error parsing the response: " + ex.Message;
                }
                return View();
            }

            ViewBag.Error = "Failed to retrieve protected data or unauthorized.";
            return View();
        }
    }
}
Home Controller Action Methods:

The Home Controller handles actions related to displaying data and the main landing page.

  • Index: Returns the default landing (home) view. Serves as the main entry point of the application.
  • GetPublicData: It uses an HttpClient to send a GET request to the Resource Server’s public-data endpoint. Deserializes the response into a DataResponseModel. Passes the retrieved message (or error) to the view via ViewBag for display.
  • GetProtectedData: Checks for the existence of a JWT token in the session. If present, send a GET request with the token in the Authorization header to the Resource Server’s protected data endpoint. Deserializes the response into a DataResponseModel. Displays protected data in the view if the token is valid; otherwise, redirects to the login page.
Create Views for GetPublicData and GetProtectedData

Let us proceed and create the required view. We will keep the default Index View as expected.

Views/Home/GetPublicData.cshtml

Create a view named GetPublicData.cshtml within the Views/Home folder and copy and paste the following code. This view displays public data retrieved from the Resource Server. It uses ViewBag.PublicData to show a success message. Displays an error message if ViewBag.Error is set.

@{
    ViewData["Title"] = "Public Data";
}

<h2 class="text-center">@ViewData["Title"]</h2>

<div class="container mt-4">
    <div class="row justify-content-center">
        <div class="col-md-8">
            @if (ViewBag.PublicData != null)
            {
                <div class="alert alert-success" role="alert">
                    <p>@ViewBag.PublicData</p>
                </div>
            }
            else if (ViewBag.Error != null)
            {
                <div class="alert alert-danger" role="alert">
                    <p>@ViewBag.Error</p>
                </div>
            }
        </div>
    </div>
</div>
Views/Home/GetProtectedData.cshtml

Create a view named GetProtectedData.cshtml within the Views/Home folder, then copy and paste the following code. This view displays protected data fetched from the Resource Server. It uses ViewBag.ProtectedData to show the data. If there is an error (or if the user is unauthorized), it displays an error message.

@{
    ViewData["Title"] = "Protected Data";
}

<h2 class="text-center">@ViewData["Title"]</h2>

<div class="container mt-4">
    <div class="row justify-content-center">
        <div class="col-md-8">
            @if (ViewBag.ProtectedData != null)
            {
                <div class="alert alert-success" role="alert">
                    <p>@ViewBag.ProtectedData</p>
                </div>
            }
            else if (ViewBag.Error != null)
            {
                <div class="alert alert-danger" role="alert">
                    <p>@ViewBag.Error</p>
                </div>
            }
        </div>
    </div>
</div>
Modifying the Layout Page:

We need to modify the Layout file with the following Requirements:

  • Public Data Link: Always visible, linked to the GetPublicData action of the Home Controller.
  • Protected Data Link: Visible only when the user is logged in and linked to the GetProtectedData action in the Home Controller.
  • Login and Register Links: Visible when the user is not logged in.
  • Logout Button and Username: Visible when the user is logged in, showing the registered User Name and providing a logout button.

So, modify the _Layout.cshtml file as follows. This layout ensures that our application dynamically shows or hides navigation options based on the user’s authentication status.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - ClientApplicationOne</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
    <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
</head>
<body>
    <header>
        <nav class="navbar navbar-expand-sm navbar-light bg-white border-bottom box-shadow mb-3">
            <div class="container-fluid">
                <a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">ClientApplicationOne</a>
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
                    <ul class="navbar-nav flex-grow-1">
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="GetPublicData">Public Data</a>
                        </li>
                        @if (Context.Session.GetString("Username") != null)
                        {
                            <li class="nav-item">
                                <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="GetProtectedData">Protected Data</a>
                            </li>
                        }
                    </ul>
                    <ul class="navbar-nav">
                        @if (Context.Session.GetString("Username") != null)
                        {
                            <li class="nav-item">
                                <span class="nav-link text-dark">Hello, @Context.Session.GetString("Username")</span>
                            </li>
                            <li class="nav-item">
                                <form asp-area="" asp-controller="Account" asp-action="Logout" method="post">
                                    <button type="submit" class="nav-link btn btn-link text-dark">Logout</button>
                                </form>
                            </li>
                        }
                        else
                        {
                            <li class="nav-item">
                                <a class="nav-link text-dark" asp-area="" asp-controller="Account" asp-action="Login">Login</a>
                            </li>
                            <li class="nav-item">
                                <a class="nav-link text-dark" asp-area="" asp-controller="Account" asp-action="Register">Register</a>
                            </li>
                        }
                    </ul>
                </div>
            </div>
        </nav>
    </header>
    <div class="container">
        <main role="main" class="pb-3">
            @RenderBody()
        </main>
    </div>

    <footer class="border-top footer text-muted">
        <div class="container">
            &copy; 2025 - ClientApplicationOne - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
        </div>
    </footer>
    <script src="~/lib/jquery/dist/jquery.min.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>
    @await RenderSectionAsync("Scripts", required: false)
</body>
</html>
Testing the Application:

First, run the Authentication and Authorization Service Application and then the Client Application. Then, test the functionality, and it should work as expected.

That’s it. We are done with our First Client Application Implementation using the ASP.NET Core MVC project. In the next article, I will discuss how to Implement the Second Client Application using the ASP.NET Core MVC Project. I hope you enjoy this article.

Leave a Reply

Your email address will not be published. Required fields are marked *