Skip to content

Commit 06a6248

Browse files
author
Oyvind Timian Dokk Husveg
committed
Added a check that verifies that a id is in the database on authorized API calls
1 parent 7b6b6d0 commit 06a6248

File tree

7 files changed

+161
-5
lines changed

7 files changed

+161
-5
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using Microsoft.AspNetCore.Authorization;
2+
3+
namespace exercise.wwwapi.Authorization.Attributes
4+
{
5+
/// <summary>
6+
/// Requires authentication and validates that the user exists in the database
7+
/// </summary>
8+
public class AuthorizeUserExistsAttribute : AuthorizeAttribute
9+
{
10+
public AuthorizeUserExistsAttribute() : base("UserExists")
11+
{
12+
}
13+
}
14+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using exercise.wwwapi.DTOs;
2+
using Microsoft.AspNetCore.Authorization;
3+
using Microsoft.AspNetCore.Authorization.Policy;
4+
5+
namespace exercise.wwwapi.Authorization
6+
{
7+
public class CustomAuthorizationResultHandler : IAuthorizationMiddlewareResultHandler
8+
{
9+
private readonly AuthorizationMiddlewareResultHandler _defaultHandler = new();
10+
11+
public async Task HandleAsync(
12+
RequestDelegate next,
13+
HttpContext context,
14+
AuthorizationPolicy policy,
15+
PolicyAuthorizationResult authorizeResult)
16+
{
17+
if (!authorizeResult.Succeeded)
18+
{
19+
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
20+
await context.Response.WriteAsJsonAsync(new ResponseDTO<Object>()
21+
{
22+
Message = "Authorization failed"
23+
});
24+
return;
25+
}
26+
27+
await _defaultHandler.HandleAsync(next, context, policy, authorizeResult);
28+
}
29+
}
30+
31+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using exercise.wwwapi.Authorization.Handlers;
2+
using exercise.wwwapi.Authorization.Requirements;
3+
using Microsoft.AspNetCore.Authorization;
4+
5+
namespace exercise.wwwapi.Authorization.Extensions
6+
{
7+
public static class AuthorizationServiceExtensions
8+
{
9+
public static IServiceCollection AddCustomAuthorization(this IServiceCollection services)
10+
{
11+
services.AddAuthorization(options =>
12+
{
13+
// Create policy that requires user to exist in database
14+
options.AddPolicy("UserExists", policy =>
15+
{
16+
policy.RequireAuthenticatedUser();
17+
policy.AddRequirements(new UserExistsRequirement());
18+
});
19+
20+
// Make this the default policy [Authorize]
21+
options.DefaultPolicy = options.GetPolicy("UserExists")!;
22+
});
23+
24+
// Register the handlers
25+
services.AddScoped<IAuthorizationHandler, UserExistsHandler>();
26+
27+
return services;
28+
}
29+
}
30+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
using exercise.wwwapi.Authorization.Requirements;
2+
using exercise.wwwapi.Data;
3+
using Microsoft.AspNetCore.Authorization;
4+
using Microsoft.EntityFrameworkCore;
5+
using System.Security.Claims;
6+
7+
namespace exercise.wwwapi.Authorization.Handlers
8+
{
9+
public class UserExistsHandler : AuthorizationHandler<UserExistsRequirement>
10+
{
11+
private readonly IServiceProvider _serviceProvider;
12+
private readonly ILogger<UserExistsHandler> _logger;
13+
14+
public UserExistsHandler(IServiceProvider serviceProvider, ILogger<UserExistsHandler> logger)
15+
{
16+
_serviceProvider = serviceProvider;
17+
_logger = logger;
18+
}
19+
20+
protected override async Task HandleRequirementAsync(
21+
AuthorizationHandlerContext context,
22+
UserExistsRequirement requirement)
23+
{
24+
//_logger.LogWarning("Starting user existence validation for authorization");
25+
26+
//var claims = context.User.Claims.Select(c => $"{c.Type}: {c.Value}").ToList();
27+
//_logger.LogWarning("Available claims in token: {Claims}", string.Join(", ", claims));
28+
29+
// Get user ID from claims
30+
var userIdClaim = context.User.FindFirst(ClaimTypes.NameIdentifier)
31+
?? context.User.FindFirst(ClaimTypes.Sid);
32+
33+
34+
if (userIdClaim == null || !int.TryParse(userIdClaim.Value, out int userId))
35+
{
36+
//_logger.LogWarning("No valid user ID found in token claims");
37+
context.Fail();
38+
return;
39+
}
40+
//_logger.LogWarning("Found user ID claim: {ClaimType} = {ClaimValue}", userIdClaim.Type, userIdClaim.Value);
41+
42+
try
43+
{
44+
//_logger.LogWarning("Checking if user {UserId} exists in database...", userId);
45+
// Check if user exists in database
46+
using var scope = _serviceProvider.CreateScope();
47+
var dbContext = scope.ServiceProvider.GetRequiredService<DataContext>();
48+
49+
var userExists = await dbContext.Users.AnyAsync(u => u.Id == userId);
50+
51+
if (userExists) context.Succeed(requirement);
52+
else
53+
{
54+
_logger.LogWarning("User {UserId} not found in database", userId);
55+
context.Fail();
56+
}
57+
}
58+
catch (Exception ex)
59+
{
60+
_logger.LogError(ex, "Error checking if user {UserId} exists", userId);
61+
context.Fail();
62+
}
63+
//_logger.LogWarning("Completed user existence validation for user {UserId}", userId);
64+
}
65+
}
66+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using Microsoft.AspNetCore.Authorization;
2+
3+
namespace exercise.wwwapi.Authorization.Requirements
4+
{
5+
public class UserExistsRequirement : IAuthorizationRequirement
6+
{
7+
// supposed to be empty apparently
8+
}
9+
}

exercise.wwwapi/Endpoints/PostEndpoints.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,7 @@ public static IResult CreatePost(
4747
CreatePostDTO request
4848
)
4949
{
50-
int? userid = user.UserRealId();
51-
if ( userid == null ) return Results.NotFound(new ResponseDTO<Object> { Message = "UserId missing" });
52-
User? dbUser = userservice.GetById(userid);
50+
User? dbUser = userservice.GetById(user.UserRealId());
5351
if (dbUser == null) return Results.NotFound(new ResponseDTO<Object> { Message = "Invalid userID" });
5452

5553
if (string.IsNullOrWhiteSpace(request.Content))

exercise.wwwapi/Program.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
using exercise.wwwapi.Authorization;
2+
using exercise.wwwapi.Authorization.Extensions;
13
using exercise.wwwapi.Configuration;
24
using exercise.wwwapi.Data;
35
using exercise.wwwapi.Endpoints;
46
using exercise.wwwapi.EndPoints;
57
using exercise.wwwapi.Models;
68
using exercise.wwwapi.Repository;
79
using Microsoft.AspNetCore.Authentication.JwtBearer;
10+
using Microsoft.AspNetCore.Authorization;
811
using Microsoft.EntityFrameworkCore;
912
using Microsoft.IdentityModel.Tokens;
1013
using Microsoft.OpenApi.Models;
@@ -28,10 +31,11 @@
2831
builder.Services.AddScoped<IRepository<CohortCourseUser>, Repository<CohortCourseUser>>();
2932
builder.Services.AddScoped<ILogger, Logger<string>>();
3033
builder.Services.AddAutoMapper(typeof(Program));
34+
builder.Services.AddSingleton<IAuthorizationMiddlewareResultHandler, CustomAuthorizationResultHandler>();
35+
3136

3237
builder.Services.AddDbContext<DataContext>(options =>
3338
{
34-
3539
//options.UseNpgsql(builder.Configuration.GetConnectionString("LocalDatabase"));
3640
options.UseNpgsql(builder.Configuration.GetConnectionString("LocalDatabase"));
3741
options.LogTo(message => Debug.WriteLine(message));
@@ -91,7 +95,9 @@
9195
});
9296

9397
});
94-
builder.Services.AddAuthorization();
98+
//builder.Services.AddAuthorization();
99+
builder.Services.AddCustomAuthorization();
100+
95101

96102
builder.Services.AddControllers();
97103
builder.Services.AddEndpointsApiExplorer();
@@ -124,6 +130,8 @@
124130

125131
app.UseAuthentication();
126132

133+
134+
127135
app.UseAuthorization();
128136

129137
app.MapControllers();

0 commit comments

Comments
 (0)