Skip to content

Commit

Permalink
add deny all policy
Browse files Browse the repository at this point in the history
  • Loading branch information
josxha committed Oct 22, 2023
1 parent 6b64b2e commit eaf3aca
Show file tree
Hide file tree
Showing 14 changed files with 185 additions and 10 deletions.
2 changes: 2 additions & 0 deletions KratosSelfService/Controllers/ErrorController.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
using KratosSelfService.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace KratosSelfService.Controllers;

public class ErrorController(ApiService api) : Controller
{
[HttpGet("error")]
[AllowAnonymous]
public async Task<IActionResult> Error([FromQuery(Name = "id")] Guid? flowId)
{
var error = await api.Frontend.GetFlowErrorAsync(flowId.ToString());
Expand Down
5 changes: 4 additions & 1 deletion KratosSelfService/Controllers/HealthController.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace KratosSelfService.Controllers;

[Route("health")]
public class HealthController : Controller
{
[HttpGet("alive")]
[AllowAnonymous]
public string Alive()
{
return "ok";
}

[HttpGet("ready")]
[AllowAnonymous]
public string Ready()
{
return "ok";
Expand Down
2 changes: 2 additions & 0 deletions KratosSelfService/Controllers/HomeController.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace KratosSelfService.Controllers;

public class HomeController(ILogger<HomeController> logger) : Controller
{
[HttpGet("")]
[AllowAnonymous]
public IActionResult Home()
{
return View("Home");
Expand Down
2 changes: 2 additions & 0 deletions KratosSelfService/Controllers/LoginController.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using KratosSelfService.Extensions;
using KratosSelfService.Models;
using KratosSelfService.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Net.Http.Headers;
using Ory.Kratos.Client.Client;
Expand All @@ -11,6 +12,7 @@ namespace KratosSelfService.Controllers;
public class LoginController(ILogger<LoginController> logger, ApiService api) : Controller
{
[HttpGet("login")]
[AllowAnonymous]
public async Task<IActionResult> Login(
[FromQuery(Name = "flow")] Guid? flowId,
[FromQuery] string? aal,
Expand Down
7 changes: 4 additions & 3 deletions KratosSelfService/Controllers/LogoutController.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using KratosSelfService.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Net.Http.Headers;
using Ory.Kratos.Client.Client;
using Ory.Kratos.Client.Model;

Expand All @@ -9,6 +9,7 @@ namespace KratosSelfService.Controllers;
public class LogoutController(ILogger<LogoutController> logger, ApiService api) : Controller
{
[HttpGet("logout")]
[AllowAnonymous]
public async Task<IActionResult> Logout()
{
KratosLogoutFlow flow;
Expand All @@ -18,8 +19,8 @@ public async Task<IActionResult> Logout()
}
catch (ApiException exception)
{
Console.WriteLine(exception.Message);
throw;
logger.LogDebug("Could not get logout flow: {Message}", exception.Message);
return Redirect("/");
}

return Redirect(flow.LogoutUrl);
Expand Down
8 changes: 5 additions & 3 deletions KratosSelfService/Controllers/RecoveryController.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using KratosSelfService.Models;
using KratosSelfService.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Ory.Kratos.Client.Client;
using Ory.Kratos.Client.Model;
Expand All @@ -9,14 +10,15 @@ namespace KratosSelfService.Controllers;
public class RecoveryController(ILogger<RecoveryController> logger, ApiService api) : Controller
{
[HttpGet("recovery")]
[AllowAnonymous]
public async Task<IActionResult> Recovery(
[FromQuery(Name = "flow")] Guid? flowId,
[FromQuery(Name = "return_to")] string? returnTo)
{
if (flowId == null)
{
logger.LogDebug("No flow ID found in URL query initializing login flow");
return Redirect(api.GetUrlForBrowserFlow("recovery", new Dictionary<string, string?>()
return Redirect(api.GetUrlForBrowserFlow("recovery", new Dictionary<string, string?>
{
["return_to"] = returnTo
}));
Expand All @@ -31,13 +33,13 @@ public async Task<IActionResult> Recovery(
{
logger.LogError(exception.Message);
// restart flow
return Redirect(api.GetUrlForBrowserFlow("recovery", new Dictionary<string, string?>()
return Redirect(api.GetUrlForBrowserFlow("recovery", new Dictionary<string, string?>
{
["return_to"] = returnTo
}));
}

var loginUrl = api.GetUrlForBrowserFlow("login", new Dictionary<string, string?>()
var loginUrl = api.GetUrlForBrowserFlow("login", new Dictionary<string, string?>
{
["return_to"] = returnTo
});
Expand Down
2 changes: 2 additions & 0 deletions KratosSelfService/Controllers/RegistrationController.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using KratosSelfService.Models;
using KratosSelfService.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Ory.Kratos.Client.Client;
using Ory.Kratos.Client.Model;
Expand All @@ -9,6 +10,7 @@ namespace KratosSelfService.Controllers;
public class RegistrationController(ILogger<RegistrationController> logger, ApiService api) : Controller
{
[HttpGet("registration")]
[AllowAnonymous]
public async Task<IActionResult> Registration(
[FromQuery(Name = "flow")] Guid? flowId,
[FromQuery(Name = "return_to")] string? returnTo,
Expand Down
5 changes: 3 additions & 2 deletions KratosSelfService/Controllers/SessionsController.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using KratosSelfService.Models;
using KratosSelfService.Extensions;
using KratosSelfService.Models;
using KratosSelfService.Services;
using Microsoft.AspNetCore.Mvc;
using Ory.Kratos.Client.Model;
Expand All @@ -10,7 +11,7 @@ public class SessionsController(ApiService api) : Controller
[HttpGet("sessions")]
public async Task<IActionResult> Sessions()
{
var currentSession = await api.Frontend.ToSessionAsync(cookie: Request.Headers.Cookie);
var currentSession = HttpContext.GetSession()!;
// retrieve all other active sessions
var otherSessions = await api.Frontend
.ListMySessionsAsync(cookie: Request.Headers.Cookie) ?? new List<KratosSession>();
Expand Down
2 changes: 2 additions & 0 deletions KratosSelfService/Controllers/VerificationController.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using KratosSelfService.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Ory.Kratos.Client.Client;
Expand All @@ -9,6 +10,7 @@ namespace KratosSelfService.Controllers;
public class VerificationController(ILogger<VerificationController> logger, ApiService api) : Controller
{
[HttpGet("verification")]
[AllowAnonymous]
public async Task<IActionResult> Verification(
[FromQuery(Name = "flow")] Guid? flowId,
[FromQuery(Name = "return_to")] string? returnTo,
Expand Down
2 changes: 2 additions & 0 deletions KratosSelfService/Controllers/WellknownController.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
using KratosSelfService.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace KratosSelfService.Controllers;

public class WellknownController(ILogger<WellknownController> logger, ApiService api) : Controller
{
[HttpGet("/.well-known/ory/webauthn.js")]
[AllowAnonymous]
public async Task<IActionResult> Webauthn()
{
var script = await api.Frontend.GetWebAuthnJavaScriptAsync();
Expand Down
11 changes: 11 additions & 0 deletions KratosSelfService/Extensions/HttpContextExt.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Ory.Kratos.Client.Model;

namespace KratosSelfService.Extensions;

public static class HttpContextExt
{
public static KratosSession? GetSession(this HttpContext httpContext)
{
return (KratosSession?)httpContext.Items[typeof(KratosSession)];
}
}
26 changes: 25 additions & 1 deletion KratosSelfService/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using System.Globalization;
using System.Security.Claims;
using KratosSelfService.Services;
using KratosSelfService.Utils;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Localization;

namespace KratosSelfService;
Expand All @@ -8,9 +11,29 @@ public class Startup(IConfigurationRoot config, IWebHostEnvironment env)
{
public void ConfigureServices(IServiceCollection services)
{
// Add services to the container.
services.AddControllersWithViews();

// authentication and authorisation
services.AddAuthentication(options =>
{
options.AddScheme<AuthenticationHandler>("DefaultScheme", "Default authentication scheme");
options.DefaultChallengeScheme = "DefaultScheme"; // 401 Unauthorized
options.DefaultForbidScheme = "DefaultScheme"; // 403 Forbid
});
services.AddAuthorization(options =>
{
options.AddPolicy("LoggedIn", policyBuilder => { policyBuilder.RequireClaim(ClaimTypes.NameIdentifier); });
// The fallback authorization policy requires all users to be authenticated, except for controllers or action
// methods with an authorization attribute. For example, controllers or action methods with [AllowAnonymous] or
// [Authorize(PolicyName="MyPolicy")] use the applied authorization attribute rather than the fallback
// authorization policy.
// The fallback authorization policy is applied to all requests that don't explicitly specify an authorization
// policy.
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});

// localisation
services.AddLocalization(options => options.ResourcesPath = "Resources");
services.AddSingleton<ICustomTranslator, CustomTranslator>();
Expand Down Expand Up @@ -44,6 +67,7 @@ public void Configure(WebApplication app)

app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
}
Expand Down
33 changes: 33 additions & 0 deletions KratosSelfService/Utils/AuthenticationAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using KratosSelfService.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Ory.Kratos.Client.Client;
using Ory.Kratos.Client.Model;

namespace KratosSelfService.Utils;

[AttributeUsage(AttributeTargets.Method)]
public class AuthenticationAttribute : Attribute,
IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<AuthenticationAttribute>>();
var api = context.HttpContext.RequestServices.GetRequiredService<ApiService>();
var cookie = context.HttpContext.Request.Headers.Cookie;
KratosSession session;
try
{
session = await api.Frontend.ToSessionAsync(cookie: cookie);
}
catch (ApiException exception)
{
logger.LogDebug("Could not get session: {Message}", exception.Message);
context.Result = new UnauthorizedResult();
return;
}

context.HttpContext.Items[typeof(KratosSession)] = session;
await next();
}
}
88 changes: 88 additions & 0 deletions KratosSelfService/Utils/AuthenticationHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using System.Security.Claims;
using KratosSelfService.Services;
using Microsoft.AspNetCore.Authentication;
using Ory.Kratos.Client.Client;
using Ory.Kratos.Client.Model;

namespace KratosSelfService.Utils;

/**
* Created per request to handle authentication for a particular scheme.
*/
public class AuthenticationHandler(ApiService api) : IAuthenticationHandler
{
private HttpContext _context = null!;

/**
* Initialize the authentication handler. The handler should initialize anything it needs
* from the request and scheme as part of this method.
*/
public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
{
_context = context;
return Task.CompletedTask;
}

/**
* An authentication scheme's authenticate action is responsible for constructing the user's
* identity based on request context. It returns an AuthenticateResult indicating whether
* authentication was successful and, if so, the user's identity in an authentication ticket.
*/
public async Task<AuthenticateResult> AuthenticateAsync()
{
var cookies = _context.Request.Headers.Cookie;
KratosSession session;
try
{
session = await api.Frontend.ToSessionAsync(cookie: cookies);
}
catch (ApiException exception)
{
return AuthenticateResult.Fail("No valid session.");
}

if (!session.Active)
return AuthenticateResult.Fail("The session is inactive.");

_context.Items[typeof(KratosSession)] = session;

// Construct AuthenticationTicket objects representing the user's identity
// if authentication is successful
// https://andrewlock.net/introduction-to-authentication-with-asp-net-core/
var claims = new List<Claim>
{
new(ClaimTypes.NameIdentifier, session.Identity.Id, ClaimValueTypes.String)
};
var identity = new ClaimsIdentity(claims, "KratosIdentity");
var principal = new ClaimsPrincipal(identity);
var authTicket = new AuthenticationTicket(principal, "LoggedIn");
return AuthenticateResult.Success(authTicket);
}

/**
* An authentication challenge is invoked by Authorization when an unauthenticated user requests an endpoint
* that requires authentication. An authentication challenge is issued, for example, when an anonymous user
* requests a restricted resource or follows a login link. Authorization invokes a challenge using the specified
* authentication scheme(s), or the default if none is specified.
* A challenge action should let the user know what authentication mechanism to use to access
* the requested resource.
*/
public Task ChallengeAsync(AuthenticationProperties? properties)
{
// redirect to login page
_context.Response.Redirect("login");
return Task.CompletedTask;
}

/**
* An authentication scheme's forbid action is called by Authorization when an authenticated
* user attempts to access a resource they're not permitted to access.
*/
public Task ForbidAsync(AuthenticationProperties? properties)
{
// redirect to a page where the user can request the
// authorisations but return 403 for now
_context.Response.StatusCode = StatusCodes.Status403Forbidden;
return Task.CompletedTask;
}
}

0 comments on commit eaf3aca

Please sign in to comment.