Skip to content

Commit

Permalink
Add AspNetCoreMvc sample
Browse files Browse the repository at this point in the history
  • Loading branch information
akunzai committed Sep 28, 2024
1 parent 4c541e2 commit c5c35d5
Show file tree
Hide file tree
Showing 18 changed files with 428 additions and 3 deletions.
7 changes: 7 additions & 0 deletions CAS.sln
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OwinSample", "samples\OwinS
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlazorSample", "samples\BlazorSample\BlazorSample.csproj", "{35775C99-782F-4502-B31D-B13E966D0A90}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNetCoreMvcSample", "samples\AspNetCoreMvcSample\AspNetCoreMvcSample.csproj", "{F98E8D5E-DB3C-4716-A440-392457A5784B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -105,6 +107,10 @@ Global
{35775C99-782F-4502-B31D-B13E966D0A90}.Debug|Any CPU.Build.0 = Debug|Any CPU
{35775C99-782F-4502-B31D-B13E966D0A90}.Release|Any CPU.ActiveCfg = Release|Any CPU
{35775C99-782F-4502-B31D-B13E966D0A90}.Release|Any CPU.Build.0 = Release|Any CPU
{F98E8D5E-DB3C-4716-A440-392457A5784B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F98E8D5E-DB3C-4716-A440-392457A5784B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F98E8D5E-DB3C-4716-A440-392457A5784B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F98E8D5E-DB3C-4716-A440-392457A5784B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -122,6 +128,7 @@ Global
{9E34C625-CE58-4630-AF42-C5237660AE27} = {EF050CD3-59F8-432E-9E77-7CF8A5F5CD91}
{BB89E35B-F73F-426F-8E66-456D6A49FDF3} = {EF050CD3-59F8-432E-9E77-7CF8A5F5CD91}
{35775C99-782F-4502-B31D-B13E966D0A90} = {EF050CD3-59F8-432E-9E77-7CF8A5F5CD91}
{F98E8D5E-DB3C-4716-A440-392457A5784B} = {EF050CD3-59F8-432E-9E77-7CF8A5F5CD91}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C211A196-2432-4E8E-88F4-EBF50079001C}
Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# GSS.Authentication.CAS

[![Build Status][build-badge]][build] [![Code Coverage][codecov-badge]][codecov] [![Lint][lint-badge]][lint]
[![Build Status][build-badge]][build] [![Lint][lint-badge]][lint] [![Code Coverage][codecov-badge]][codecov]

[build]: https://github.com/akunzai/GSS.Authentication.CAS/actions/workflows/build.yml
[build-badge]: https://github.com/akunzai/GSS.Authentication.CAS/actions/workflows/build.yml/badge.svg
[codecov]: https://codecov.io/gh/akunzai/GSS.Authentication.CAS
[codecov-badge]: https://codecov.io/gh/akunzai/GSS.Authentication.CAS/branch/main/graph/badge.svg?token=JGG7Y07SR0
[lint]: https://github.com/akunzai/GSS.Authentication.CAS/actions/workflows/lint.yml
[lint-badge]: https://github.com/akunzai/GSS.Authentication.CAS/actions/workflows/lint.yml/badge.svg
[codecov]: https://codecov.io/gh/akunzai/GSS.Authentication.CAS
[codecov-badge]: https://codecov.io/gh/akunzai/GSS.Authentication.CAS/branch/main/graph/badge.svg?token=JGG7Y07SR0

CAS Authentication Middleware for OWIN & ASP.NET Core

Expand Down Expand Up @@ -40,6 +40,7 @@ Check out these [samples](./samples/) to learn the basics and key features.
- [ASP.NET Core with React.js](./samples/AspNetCoreReactSample/)
- [ASP.NET Core Identity](./samples/AspNetCoreIdentitySample/)
- [ASP.NET Core Blazor](./samples/BlazorSample/)
- [ASP.NET Core MVC](./samples/AspNetCoreMvcSample/)
- [OWIN](./samples/OwinSample/)

## FAQ
Expand Down
29 changes: 29 additions & 0 deletions samples/AspNetCoreMvcSample/AspNetCoreMvcSample.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<Content Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Update="appsettings.*.json" CopyToPublishDirectory="Never">
<DependentUpon>appsettings.json</DependentUpon>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" />
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" />
<PackageReference Include="StackExchange.Redis" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\GSS.Authentication.CAS.AspNetCore\GSS.Authentication.CAS.AspNetCore.csproj" />
</ItemGroup>

</Project>
38 changes: 38 additions & 0 deletions samples/AspNetCoreMvcSample/Controllers/AccountController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace AspNetCoreMvcSample.Controllers;

[AllowAnonymous]
public class AccountController : Controller
{
// GET /Account/Login
[HttpGet]
public ActionResult Login(string scheme)
{
if (string.IsNullOrWhiteSpace(scheme))
{
return View();
}

return Challenge(new AuthenticationProperties { RedirectUri = "/" }, scheme);
}

// GET /Account/Logout
[HttpGet]
public async Task Logout(string redirectUrl)
{
if (string.IsNullOrWhiteSpace(redirectUrl))
{
redirectUrl = "/";
}
var properties = new AuthenticationProperties { RedirectUri = redirectUrl };
await HttpContext.SignOutAsync(properties);
var authScheme = User.Claims.FirstOrDefault(x => string.Equals(x.Type, "auth_scheme"))?.Value;
if (!string.IsNullOrWhiteSpace(authScheme))
{
await HttpContext.SignOutAsync(authScheme, properties);
}
}
}
21 changes: 21 additions & 0 deletions samples/AspNetCoreMvcSample/Controllers/HomeController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using AspNetCoreMvcSample.Models;
using Microsoft.AspNetCore.Authorization;

namespace AspNetCoreMvcSample.Controllers;

[AllowAnonymous]
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
8 changes: 8 additions & 0 deletions samples/AspNetCoreMvcSample/Models/ErrorViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace AspNetCoreMvcSample.Models;

public class ErrorViewModel
{
public string RequestId { get; set; }

public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
}
16 changes: 16 additions & 0 deletions samples/AspNetCoreMvcSample/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace AspNetCoreMvcSample;

public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}

public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
13 changes: 13 additions & 0 deletions samples/AspNetCoreMvcSample/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"profiles": {
"AspNetCoreMvcSample": {
"commandName": "Project",
"dotnetRunMessages": "true",
"launchBrowser": true,
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
143 changes: 143 additions & 0 deletions samples/AspNetCoreMvcSample/Startup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
using System.Security.Claims;
using GSS.Authentication.CAS;
using GSS.Authentication.CAS.AspNetCore;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;

namespace AspNetCoreMvcSample;

public class Startup
{
public Startup(IConfiguration configuration, IWebHostEnvironment hostEnvironment)
{
Configuration = configuration;
HostingEnvironment = hostEnvironment;
}

public IConfiguration Configuration { get; }

public IWebHostEnvironment HostingEnvironment { get; }

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
var singleLogout = Configuration.GetValue("CAS:SingleLogout", false);
if (singleLogout)
{
services.AddDistributedMemoryCache();
var redisConfiguration = Configuration.GetConnectionString("Redis");
if (!string.IsNullOrWhiteSpace(redisConfiguration))
{
services.AddStackExchangeRedisCache(options => options.Configuration = redisConfiguration);
}

services.AddSingleton<ITicketStore, DistributedCacheTicketStore>();
services.AddOptions<CookieAuthenticationOptions>(CookieAuthenticationDefaults.AuthenticationScheme)
.Configure<ITicketStore>((o, t) => o.SessionStore = t);
}
services.AddControllersWithViews();
services.AddAuthorization(options =>
{
// Globally Require Authenticated Users
options.FallbackPolicy = options.DefaultPolicy;
});
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie()
.AddCAS(options =>
{
options.CasServerUrlBase = Configuration["CAS:ServerUrlBase"]!;
// required for CasSingleLogoutMiddleware
options.SaveTokens = singleLogout || Configuration.GetValue("CAS:SaveTokens", false);
options.Events.OnCreatingTicket = context =>
{
if (context.Identity == null)
return Task.CompletedTask;
// Map claims from assertion
var assertion = context.Assertion;
context.Identity.AddClaim(new Claim("auth_scheme", CasDefaults.AuthenticationType));
context.Identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, assertion.PrincipalName));
if (assertion.Attributes.TryGetValue("display_name", out var displayName) &&
!string.IsNullOrWhiteSpace(displayName))
{
context.Identity.AddClaim(new Claim(ClaimTypes.Name, displayName!));
}

if (assertion.Attributes.TryGetValue("cn", out var fullName) &&
!string.IsNullOrWhiteSpace(fullName))
{
context.Identity.AddClaim(new Claim(ClaimTypes.Name, fullName!));
}

if (assertion.Attributes.TryGetValue("email", out var email) &&
!string.IsNullOrWhiteSpace(email))
{
context.Identity.AddClaim(new Claim(ClaimTypes.Email, email!));
}

return Task.CompletedTask;
};
})
.AddOpenIdConnect(options =>
{
options.ClientId = Configuration["OIDC:ClientId"];
options.ClientSecret = Configuration["OIDC:ClientSecret"];
options.Authority = Configuration["OIDC:Authority"];
options.RequireHttpsMetadata = !HostingEnvironment.IsDevelopment();
// required for single logout
options.SaveTokens = Configuration.GetValue("OIDC:SaveTokens", false);
options.ResponseType = OpenIdConnectResponseType.Code;
var scope = Configuration["OIDC:Scope"];
if (!string.IsNullOrWhiteSpace(scope))
{
scope.Split(" ", StringSplitOptions.RemoveEmptyEntries).ToList().ForEach(s => options.Scope.Add(s));
}
options.TokenValidationParameters.NameClaimType =
Configuration.GetValue("OIDC:NameClaimType", "name");
options.Events.OnTokenValidated = context =>
{
if (context.Principal?.Identity is ClaimsIdentity claimIdentity)
{
claimIdentity.AddClaim(new Claim("auth_scheme", OpenIdConnectDefaults.AuthenticationScheme));
}
return Task.CompletedTask;
};
});
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}

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

app.UseRouting();

var singleLogout = Configuration.GetValue("CAS:SingleLogout", false);
if (singleLogout)
{
app.UseCasSingleLogout();
}

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

app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}
18 changes: 18 additions & 0 deletions samples/AspNetCoreMvcSample/Views/Account/Login.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
@using Microsoft.Extensions.Options;
@using Microsoft.AspNetCore.Authentication;
@inject IOptions<AuthenticationOptions> AuthOptions;
@{
ViewData["Title"] = "Login";
}

<h1>Choose an authentication scheme</h1>

@foreach (var type in AuthOptions.Value.Schemes)
{
if (string.IsNullOrEmpty(type.DisplayName))
{
continue;
}

<a class="btn btn-outline-primary btn-lg" href="[email protected]" role="button">@type.DisplayName</a>
}
25 changes: 25 additions & 0 deletions samples/AspNetCoreMvcSample/Views/Home/Error.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
@model ErrorViewModel
@{
ViewData["Title"] = "Error";
}

<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>

@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}

<h3>Development Mode</h3>
<p>
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>
Loading

0 comments on commit c5c35d5

Please sign in to comment.