Client Application One using ASP.NET Core MVC

Implementing Client Application One using ASP.NET Core MVC:

In this article, I will discuss the implementation of 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.

Client Application One using ASP.NET Core MVC

Now, we will build the first client application using ASP.NET Core MVC that interacts with an external Authentication Server and a Resource Server to enable user management and secure data access. The client application supports user registration, login, logout, and Single Sign-On (SSO) token validation. It securely stores JWT tokens issued by the Authentication Server and uses them to access protected resources.

Overview of the Client Application

The Client Application serves as a frontend interface for users to:

  • Register a new account by sending registration details to the Authentication Server.
  • Log in by authenticating credentials and obtaining a JWT token.
  • Log out by clearing the stored JWT token.
  • Validate SSO tokens received from other trusted applications for seamless login.
  • Access public or protected data on the Resource Server; some require authentication, while others don’t.

Once logged in, the JWT token is saved in the user’s session storage to authenticate requests to protected endpoints on the Resource Server. This token-based approach ensures that users can securely access APIs without repeatedly re-entering their credentials.

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 the Authentication Server and the Resource Server. So, instead of hardcoding URLs for external services, store the base URLs for the Authentication Server and Resource Server in appsettings.json. This makes the application flexible and easier to configure for different environments. 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 are the classes that define the data structures used by the application for capturing user input in forms and receiving responses from authentication and resource servers.

RegisterViewModel

Create a class file named RegisterViewModel.cs within the Models folder and 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 (ErrorMessage = "Username is Required")]
        public string Username { get; set; } = null!;
        [Required(ErrorMessage = "Email is Required")]
        public string Email { get; set; } = null!;
        [Required(ErrorMessage = "Password is Required")]
        [DataType(DataType.Password)]
        public string Password { get; set; } = null!;
        [Required(ErrorMessage = "Please confirm your password")]
        [DataType(DataType.Password)]
        [Compare("Password", ErrorMessage = "Password and confirmation password do not match.")]
        public string ConfirmPassword { get; set; } = null!;
    }
}
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(ErrorMessage = "Username is Required")]
        public string Username { get; set; } = null!;
        [Required(ErrorMessage = "Password is Required")]
        [DataType(DataType.Password)]
        public string Password { get; set; } = null!;
    }
}
ValidateSSOTokenModel

Create a class file named ValidateSSOTokenModel.cs within the Models folder and then copy and paste the following code. This model represents the SSO token sent by another application for validation.

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

These classes represent responses received from Authentication and Resource Servers.

LoginResponseModel

Create a new file named LoginResponseModel.cs within the Models folder, then copy and paste the following code. This model contains the JWT token received after a successful login. It includes a Token property that holds the JWT (JSON Web Token).

namespace ClientApplicationOne.Models
{
    public class LoginResponseModel
    {
        public string? Token { get; set; }
    }
}
UserDetailsModel

Create a new file named UserDetailsModel.cs within the Models folder, then copy and paste the following code. This model represents detailed user information returned during SSO token validation.

namespace ClientApplicationOne.Models
{
    public class UserDetailsModel
    {
        public string UserId { get; set; } = null!;
        public string Username { get; set; } = null!;
        public string Email { get; set; } = null!;
    }
}
ValidateSSOResponseModel

Create a new file named ValidateSSOResponseModel.cs within the Models folder, then copy and paste the following code. This model combines a JWT token and user details after successful SSO token validation.

namespace ClientApplicationOne.Models
{
    public class ValidateSSOResponseModel
    {
        public string? Token { get; set; }
        public UserDetailsModel? UserDetails { get; set; }
    }
}
DataResponseModel

Create a new file named DataResponseModel.cs within the Models folder, then copy and paste the following code. This model is used to capture messages or data from the Resource Server for both public and protected endpoints.

namespace ClientApplicationOne.Models
{
    public class DataResponseModel
    {
        public string? Message { get; set; }
    }
}
Create User Service

This service handles all communication with the Authentication Server via RESTful APIs for registration, login, and SSO token validation. Create a class file named UserService.cs within the Models folder and copy and paste the following code into it.

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

namespace ClientApplicationOne.Models
{
    public class UserService
    {
        private readonly IHttpClientFactory _httpClientFactory;
        private readonly IConfiguration _configuration;
        private readonly string _authServerUrl;

        public UserService(IHttpClientFactory httpClientFactory, IConfiguration configuration)
        {
            _httpClientFactory = httpClientFactory;
            _configuration = configuration;
            _authServerUrl = _configuration["AuthenticationServer:BaseUrl"] ?? "https://localhost:7125";
        }

        public async Task<HttpResponseMessage> RegisterUserAsync(RegisterViewModel model)
        {
            var client = _httpClientFactory.CreateClient();
            var url = $"{_authServerUrl}/api/Authentication/Register";
            var jsonContent = JsonSerializer.Serialize(model);
            var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
            return await client.PostAsync(url, content);
        }

        public async Task<HttpResponseMessage> LoginUserAsync(LoginViewModel model)
        {
            var client = _httpClientFactory.CreateClient();
            var url = $"{_authServerUrl}/api/Authentication/Login";
            var jsonContent = JsonSerializer.Serialize(model);
            var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
            return await client.PostAsync(url, content);
        }

        public async Task<HttpResponseMessage> ValidateSSOTokenAsync(ValidateSSOTokenModel model)
        {
            var client = _httpClientFactory.CreateClient();
            var url = $"{_authServerUrl}/api/Authentication/ValidateSSOToken";
            var jsonContent = JsonSerializer.Serialize(model);
            var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
            return await client.PostAsync(url, content);
        }
    }
}
Configure Http Client, Session, Distributed Caching, User Services in Program.cs

As we will be storing the JWT Token in the session, please ensure that session management is configured in the Program.cs file. So, modify the Program.cs class file as follows:

using ClientApplicationOne.Models;
namespace ClientApplicationOne
{
    public class Program
    {
        public static void Main(string[] args)
        {
            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(); // Required for session storage
            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 UserService.
            builder.Services.AddHttpClient();
            builder.Services.AddScoped<UserService>();

            var app = builder.Build();

            // Configure the HTTP request pipeline (middleware).
            if (!app.Environment.IsDevelopment())
            {
                app.UseExceptionHandler("/Home/Error");
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();
            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();
        }
    }
}
Why is using IHttpClientFactory recommended in modern ASP.NET Core apps?

When you directly instantiate HttpClient in your code (for example, using new HttpClient() inside controllers or services), you can run into significant problems, especially in high-traffic or long-running applications. The main issue is socket exhaustion. Every time you create a new HttpClient, it opens a new socket connection, and if these objects aren’t disposed of correctly, you quickly run out of available sockets. Even when you do dispose of them, frequent instantiation can lead to underlying TCP connections in the TIME_WAIT state, which still consume resources. This can degrade performance and cause unpredictable errors, making your application unreliable over time.

IHttpClientFactory was introduced in ASP.NET Core to solve these problems by providing a centralized, managed way to create and reuse HttpClient instances. Instead of creating a new HttpClient every time, you request one from the factory, which manages the underlying connections efficiently and pools them appropriately. This helps prevent socket exhaustion, ensures optimal connection reuse, and makes it easier to configure settings such as default headers, timeouts, and policies (like retries or logging) in one place. As a result, using IHttpClientFactory leads to more reliable, scalable, and maintainable HTTP calls in your applications.

Creating Account Controller:

Next, create a new Empty MVC Controller named AccountController within the Controllers folder and then copy and paste the following code. The following controller provides action methods that enable us to perform 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 UserService _userService;

        public AccountController(UserService userService)
        {
            _userService = userService;
        }

        // 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 _userService.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 _userService.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 _userService.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");
        }
    }
}
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 in to our application.

Register View (Views/Account/Register.cshtml)

Create a view named Register.cshtml within the Views/Account folder and then 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).

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

<div class="row justify-content-center">
    <div class="col-md-7 col-lg-6">
        <div class="card shadow-sm p-4 rounded-3">
            <h2 class="text-center mb-4 fw-bold text-primary">@ViewData["Title"]</h2>

            <form asp-action="Register" method="post" novalidate>
                <div asp-validation-summary="ModelOnly" class="alert alert-danger"></div>

                <div class="row mb-3 align-items-center">
                    <label asp-for="Username" class="col-sm-4 col-form-label"></label>
                    <div class="col-sm-8">
                        <input asp-for="Username" class="form-control" placeholder="Enter your username" />
                        <span asp-validation-for="Username" class="text-danger small"></span>
                    </div>
                </div>

                <div class="row mb-3 align-items-center">
                    <label asp-for="Email" class="col-sm-4 col-form-label"></label>
                    <div class="col-sm-8">
                        <input asp-for="Email" class="form-control" placeholder="Enter your email" />
                        <span asp-validation-for="Email" class="text-danger small"></span>
                    </div>
                </div>

                <div class="row mb-3 align-items-center">
                    <label asp-for="Password" class="col-sm-4 col-form-label"></label>
                    <div class="col-sm-8">
                        <input asp-for="Password" class="form-control" placeholder="Enter your password" />
                        <span asp-validation-for="Password" class="text-danger small"></span>
                    </div>
                </div>

                <div class="row mb-4 align-items-center">
                    <label asp-for="ConfirmPassword" class="col-sm-4 col-form-label"></label>
                    <div class="col-sm-8">
                        <input asp-for="ConfirmPassword" class="form-control" placeholder="Confirm your password" />
                        <span asp-validation-for="ConfirmPassword" class="text-danger small"></span>
                    </div>
                </div>

                <div class="text-center">
                    <button type="submit" class="btn btn-primary px-5">Register</button>
                </div>
            </form>

            <div class="text-center mt-3">
                Already have an account? <a asp-controller="Account" asp-action="Login" class="text-primary fw-semibold">Login here</a>.
            </div>
        </div>
    </div>
</div>

@section Scripts {
    <partial name="_ValidationScriptsPartial" />
}
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).

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

<div class="row justify-content-center">
    <div class="col-md-7 col-lg-6">
        <div class="card shadow-sm p-4 rounded-3">
            <h2 class="text-center mb-4 fw-bold text-primary">@ViewData["Title"]</h2>

            <form asp-action="Login" method="post" novalidate>
                <div asp-validation-summary="ModelOnly" class="alert alert-danger"></div>

                <div class="row mb-3 align-items-center">
                    <label asp-for="Username" class="col-sm-4 col-form-label"></label>
                    <div class="col-sm-8">
                        <input asp-for="Username" class="form-control" placeholder="Enter your username" />
                        <span asp-validation-for="Username" class="text-danger small"></span>
                    </div>
                </div>

                <div class="row mb-4 align-items-center">
                    <label asp-for="Password" class="col-sm-4 col-form-label"></label>
                    <div class="col-sm-8">
                        <input asp-for="Password" class="form-control" placeholder="Enter your password" />
                        <span asp-validation-for="Password" class="text-danger small"></span>
                    </div>
                </div>

                <div class="text-center">
                    <button type="submit" class="btn btn-primary px-5">Login</button>
                </div>
            </form>

            <div class="text-center mt-3">
                Don't have an account? <a asp-controller="Account" asp-action="Register" class="text-primary fw-semibold">Register here</a>.
            </div>
        </div>
    </div>
</div>

@section Scripts {
    <partial name="_ValidationScriptsPartial" />
}
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 can be accessed without a 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();
        }
    }
}
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 an error occurs (or if the user is unauthorized), an error message is displayed.

@{
    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, 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, displaying the registered Username 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" />
    <title>@ViewData["Title"] - ClientApplicationOne</title>

    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" />

    <link href="~/css/site.css" rel="stylesheet" asp-append-version="true" />
</head>
<body>
    <header>
        <nav class="navbar navbar-expand-lg navbar-light bg-light shadow-sm sticky-top">
            <div class="container">
                <a class="navbar-brand fw-bold text-primary" asp-controller="Home" asp-action="Index">ClientApplicationOne</a>
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent"
                        aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>

                <div class="collapse navbar-collapse" id="navbarSupportedContent">
                    <ul class="navbar-nav me-auto mb-2 mb-lg-0">
                        <li class="nav-item">
                            <a class="nav-link" asp-controller="Home" asp-action="Index">Home</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link" asp-controller="Home" asp-action="GetPublicData">Public Data</a>
                        </li>
                        @if (Context.Session.GetString("Username") != null)
                        {
                            <li class="nav-item">
                                <a class="nav-link" asp-controller="Home" asp-action="GetProtectedData">Protected Data</a>
                            </li>
                        }
                    </ul>

                    <ul class="navbar-nav ms-auto mb-2 mb-lg-0 align-items-center">
                        @if (Context.Session.GetString("Username") != null)
                        {
                            <li class="nav-item me-3">
                                <span class="text-muted">Hello, <strong>@Context.Session.GetString("Username")</strong></span>
                            </li>
                            <li class="nav-item">
                                <form asp-controller="Account" asp-action="Logout" method="post" class="d-flex">
                                    <button type="submit" class="btn btn-outline-primary btn-sm px-3">Logout</button>
                                </form>
                            </li>
                        }
                        else
                        {
                            <li class="nav-item">
                                <a class="nav-link" asp-controller="Account" asp-action="Login">Login</a>
                            </li>
                            <li class="nav-item ms-2">
                                <a class="btn btn-primary btn-sm px-4" asp-controller="Account" asp-action="Register">Register</a>
                            </li>
                        }
                    </ul>
                </div>
            </div>
        </nav>
    </header>

    <main class="container my-5">
        @RenderBody()
    </main>

    <footer class="text-center text-muted py-3 bg-light mt-auto">
        &copy; 2025 ClientApplicationOne - <a asp-controller="Home" asp-action="Privacy">Privacy</a>
    </footer>

    <!-- jQuery loaded globally -->
    <script src="~/lib/jquery/dist/jquery.min.js"></script>

    <!-- Bootstrap Bundle JS -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>

    <!-- Optional Scripts section for page-specific scripts -->
    @RenderSection("Scripts", required: false)
</body>
</html>
Testing the Application:

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

That’s it. We have completed the implementation of our first client application 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.

Registration Open – Microservices with ASP.NET Core Web API

New Batch Starts: 7th July 2025
Session Time: 6:30 AM – 8:00 AM IST

Advance your career with our expert-led, hands-on live training program. Get complete course details, the syllabus, registration, and Zoom credentials for demo sessions via the links below.

Contact: +91 70218 01173 (Call / WhatsApp)

Leave a Reply

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