.NET MVC C# Integration Guide

Complete guide for integrating .NET MVC applications with CAS SSO

Setup time: 10 minutes Difficulty: Intermediate .NET 6+

1. Project Setup

NuGet Packages

<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />

Package Manager Console

Install-Package System.IdentityModel.Tokens.Jwt
Install-Package Microsoft.AspNetCore.Authentication.JwtBearer
Install-Package Newtonsoft.Json

2. Configuration

appsettings.json

{
  "CasSettings": {
    "ServerUrl": "http://localhost:5000",
    "ClientId": "your_client_id",
    "ClientUsername": "your_client_username",
    "ClientPassword": "your_client_password",
    "SignatureSecret": "your_signature_secret",
    "CallbackUrl": "http://yourapp.com/cas/callback",
    "TokenExpiration": 120
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  }
}

Configuration Model

// Models/CasSettings.cs
public class CasSettings
{
    public string ServerUrl { get; set; }
    public string ClientId { get; set; }
    public string ClientUsername { get; set; }
    public string ClientPassword { get; set; }
    public string SignatureSecret { get; set; }
    public string CallbackUrl { get; set; }
    public int TokenExpiration { get; set; } = 120;
}

3. CAS Client Service

// Services/CasClient.cs
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;

public class CasClient
{
    private readonly CasSettings _settings;
    private readonly HttpClient _httpClient;
    private readonly ILogger<CasClient> _logger;

    public CasClient(CasSettings settings, HttpClient httpClient, ILogger<CasClient> logger)
    {
        _settings = settings;
        _httpClient = httpClient;
        _logger = logger;
    }

    public string GetLoginUrl(string returnUrl)
    {
        var loginUrl = $"{_settings.ServerUrl}/auth/login";
        var callbackUrl = $"{_settings.CallbackUrl}?return_url={Uri.EscapeDataString(returnUrl)}";
        
        return $"{loginUrl}?callback_url={Uri.EscapeDataString(callbackUrl)}";
    }

    public async Task<CasUser> ValidateTokenAsync(string token)
    {
        try
        {
            var tokenHandler = new JwtSecurityTokenHandler();
            var key = Encoding.ASCII.GetBytes(_settings.SignatureSecret);
            
            var validationParameters = new TokenValidationParameters
            {
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = new SymmetricSecurityKey(key),
                ValidateIssuer = false,
                ValidateAudience = false,
                ValidateLifetime = true,
                ClockSkew = TimeSpan.Zero
            };

            var principal = tokenHandler.ValidateToken(token, validationParameters, out var validatedToken);
            
            // Extract user information from claims
            var user = new CasUser
            {
                Username = principal.FindFirst(ClaimTypes.Name)?.Value,
                Email = principal.FindFirst(ClaimTypes.Email)?.Value,
                Role = principal.FindFirst(ClaimTypes.Role)?.Value,
                FirstName = principal.FindFirst("first_name")?.Value,
                LastName = principal.FindFirst("last_name")?.Value
            };

            return user;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Token validation failed");
            throw new UnauthorizedAccessException("Invalid token");
        }
    }

    public async Task<AuthResult> AuthenticateAsync(string username, string password)
    {
        var loginData = new
        {
            username,
            password,
            client_id = _settings.ClientId,
            client_username = _settings.ClientUsername,
            client_password = _settings.ClientPassword
        };

        var json = JsonConvert.SerializeObject(loginData);
        var content = new StringContent(json, Encoding.UTF8, "application/json");

        var response = await _httpClient.PostAsync($"{_settings.ServerUrl}/api/sso/token", content);
        
        if (response.IsSuccessStatusCode)
        {
            var responseContent = await response.Content.ReadAsStringAsync();
            var result = JsonConvert.DeserializeObject<AuthResult>(responseContent);
            return result;
        }
        
        throw new UnauthorizedAccessException("Authentication failed");
    }
}

4. Models

// Models/CasUser.cs
public class CasUser
{
    public string Username { get; set; }
    public string Email { get; set; }
    public string Role { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    
    public string FullName => $"{FirstName} {LastName}";
    
    public bool IsAdmin => Role == "admin";
    
    public bool HasRole(string role) => Role == role;
}

// Models/AuthResult.cs
public class AuthResult
{
    public string Token { get; set; }
    public CasUser User { get; set; }
    public DateTime ExpiresAt { get; set; }
}

5. Authentication Attribute

// Attributes/CasAuthAttribute.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;

public class CasAuthAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        var casUser = context.HttpContext.Session.GetString("CasUser");
        var casToken = context.HttpContext.Session.GetString("CasToken");
        
        if (string.IsNullOrEmpty(casUser) || string.IsNullOrEmpty(casToken))
        {
            var returnUrl = context.HttpContext.Request.Path + context.HttpContext.Request.QueryString;
            var casClient = context.HttpContext.RequestServices.GetService<CasClient>();
            var loginUrl = casClient.GetLoginUrl(returnUrl);
            
            context.Result = new RedirectResult(loginUrl);
            return;
        }
        
        // Validate token if needed
        var user = Newtonsoft.Json.JsonConvert.DeserializeObject<CasUser>(casUser);
        context.HttpContext.Items["CasUser"] = user;
        
        base.OnActionExecuting(context);
    }
}

// Attributes/CasRoleAttribute.cs
public class CasRoleAttribute : CasAuthAttribute
{
    private readonly string[] _roles;
    
    public CasRoleAttribute(params string[] roles)
    {
        _roles = roles;
    }
    
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        base.OnActionExecuting(context);
        
        if (context.Result != null) return; // Already redirected
        
        var user = context.HttpContext.Items["CasUser"] as CasUser;
        
        if (user == null || !_roles.Contains(user.Role))
        {
            context.Result = new ForbidResult();
        }
    }
}

6. Controller Examples

Home Controller

// Controllers/HomeController.cs
using Microsoft.AspNetCore.Mvc;

public class HomeController : Controller
{
    private readonly CasClient _casClient;
    
    public HomeController(CasClient casClient)
    {
        _casClient = casClient;
    }
    
    [CasAuth]
    public IActionResult Index()
    {
        var user = HttpContext.Items["CasUser"] as CasUser;
        return View(user);
    }
    
    [CasRole("admin")]
    public IActionResult Admin()
    {
        var user = HttpContext.Items["CasUser"] as CasUser;
        return View(user);
    }
    
    public IActionResult Login(string returnUrl = "/")
    {
        var loginUrl = _casClient.GetLoginUrl(returnUrl);
        return Redirect(loginUrl);
    }
    
    public async Task<IActionResult> CasCallback(string token, string return_url = "/")
    {
        try
        {
            var user = await _casClient.ValidateTokenAsync(token);
            
            HttpContext.Session.SetString("CasUser", JsonConvert.SerializeObject(user));
            HttpContext.Session.SetString("CasToken", token);
            
            return Redirect(return_url);
        }
        catch
        {
            return RedirectToAction("Login");
        }
    }
    
    public IActionResult Logout()
    {
        HttpContext.Session.Clear();
        return RedirectToAction("Index");
    }
}

7. Startup Configuration

// Program.cs (.NET 6+)
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;

var builder = WebApplication.CreateBuilder(args);

// Add services
builder.Services.AddControllersWithViews();

// Configure CAS settings
var casSettings = new CasSettings();
builder.Configuration.GetSection("CasSettings").Bind(casSettings);
builder.Services.AddSingleton(casSettings);

// Add HTTP client
builder.Services.AddHttpClient<CasClient>();

// Add session
builder.Services.AddSession(options =>
{
    options.IdleTimeout = TimeSpan.FromMinutes(120);
    options.Cookie.HttpOnly = true;
    options.Cookie.IsEssential = true;
});

// Add JWT authentication (optional, for API endpoints)
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(casSettings.SignatureSecret)),
            ValidateIssuer = false,
            ValidateAudience = false,
            ValidateLifetime = true,
            ClockSkew = TimeSpan.Zero
        };
    });

var app = builder.Build();

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

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseSession();
app.UseAuthentication();
app.UseAuthorization();

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

app.Run();

8. View Examples

Dashboard View

@@* Views/Home/Index.cshtml *@@
@model CasUser

@@{
    ViewData["Title"] = "Dashboard";
}

<div class="container">
    <div class="row">
        <div class="col-md-12">
            <h1>Welcome, @Model.FullName!</h1>
            
            <div class="card">
                <div class="card-header">
                    <h3>User Information</h3>
                </div>
                <div class="card-body">
                    <p><strong>Username:</strong> @Model.Username</p>
                    <p><strong>Email:</strong> @Model.Email</p>
                    <p><strong>Role:</strong> @Model.Role</p>
                    <p><strong>First Name:</strong> @Model.FirstName</p>
                    <p><strong>Last Name:</strong> @Model.LastName</p>
                </div>
            </div>
            
            <div class="mt-4">
                @if(Model.IsAdmin)
                {
                    <a href="@Url.Action("Admin")" class="btn btn-primary">Admin Panel</a>
                }
                <a href="@Url.Action("Logout")" class="btn btn-secondary">Logout</a>
            </div>
        </div>
    </div>
</div>

Layout with Authentication

@@* Views/Shared/_Layout.cshtml *@@
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - CAS Demo</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
</head>
<body>
    <header>
        <nav class="navbar navbar-expand-sm navbar-toggleable-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">CAS Demo</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>
                    </ul>
                    <ul class="navbar-nav">
                        @@{
                            var casUser = Context.Session.GetString("CasUser");
                            if (!string.IsNullOrEmpty(casUser))
                            {
                                var user = Newtonsoft.Json.JsonConvert.DeserializeObject<CasUser>(casUser);
                                <li class="nav-item">
                                    <span class="nav-link">Welcome, @user.FirstName!</span>
                                </li>
                                <li class="nav-item">
                                    <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Logout">Logout</a>
                                </li>
                            }
                            else
                            {
                                <li class="nav-item">
                                    <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Login">Login</a>
                                </li>
                            }
                        }
                    </ul>
                </div>
            </div>
        </nav>
    </header>
    
    <div class="container">
        <main role="main" class="pb-3">
            @RenderBody()
        </main>
    </div>
    
    <script src="~/lib/jquery/dist/jquery.min.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>