Skip to content

Commit

Permalink
Add authentication + authorization.
Browse files Browse the repository at this point in the history
  • Loading branch information
protectedvoid21 committed Oct 12, 2024
1 parent 053399f commit 56de9ed
Show file tree
Hide file tree
Showing 7 changed files with 190 additions and 6 deletions.
2 changes: 2 additions & 0 deletions src/BHC24.Api/BHC24.Api.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.10" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.10" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.8" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
Expand All @@ -25,6 +26,7 @@
<PackageReference Include="Serilog.Sinks.Async" Version="2.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.1.2" />
</ItemGroup>

</Project>
70 changes: 70 additions & 0 deletions src/BHC24.Api/Controllers/AuthController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using BHC24.Api.Persistence.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using JwtRegisteredClaimNames = Microsoft.IdentityModel.JsonWebTokens.JwtRegisteredClaimNames;

namespace BHC24.Api.Controllers;

[Route("api/[controller]")]
[ApiController]
public class AuthController : ControllerBase
{
private readonly UserManager<AppUser> _userManager;
private readonly IConfiguration _configuration;

public AuthController(UserManager<AppUser> userManager, IConfiguration configuration)
{
_userManager = userManager;
_configuration = configuration;
}

[HttpPost("register")]
public async Task<IActionResult> Register([FromBody] RegisterRequest request)
{
var user = new AppUser { UserName = request.Email, Email = request.Email };
var result = await _userManager.CreateAsync(user, request.Password);
if (result.Succeeded)
{
return Ok();
}
return BadRequest(result.Errors);
}

[HttpPost("login")]
public async Task<IActionResult> Login([FromBody] LoginRequest request)
{
var user = await _userManager.FindByEmailAsync(request.Email);
if (user != null && await _userManager.CheckPasswordAsync(user, request.Password))
{
var token = GenerateJwtToken(user);
return Ok(new { Token = token });
}
return Unauthorized();
}

private string GenerateJwtToken(AppUser user)
{
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString())
};

var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

var token = new JwtSecurityToken(
issuer: _configuration["Jwt:Issuer"],
audience: _configuration["Jwt:Audience"],
claims: claims,
expires: DateTime.Now.AddDays(1),
signingCredentials: creds);

return new JwtSecurityTokenHandler().WriteToken(token);
}
}
7 changes: 7 additions & 0 deletions src/BHC24.Api/Models/LoginRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace BHC24.Api.Controllers;

public class LoginRequest
{
public string Email { get; set; }

Check warning on line 5 in src/BHC24.Api/Models/LoginRequest.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'Email' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
public string Password { get; set; }

Check warning on line 6 in src/BHC24.Api/Models/LoginRequest.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'Password' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
}
7 changes: 7 additions & 0 deletions src/BHC24.Api/Models/RegisterRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace BHC24.Api.Controllers;

public class RegisterRequest
{
public string Email { get; set; }

Check warning on line 5 in src/BHC24.Api/Models/RegisterRequest.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'Email' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
public string Password { get; set; }

Check warning on line 6 in src/BHC24.Api/Models/RegisterRequest.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'Password' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
}
69 changes: 64 additions & 5 deletions src/BHC24.Api/Program.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
using System.Text;
using BHC24.Api.Extensions;
using BHC24.Api.Persistence;
using BHC24.Api.Persistence.Models;
using BHC24.Api.Services;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using Serilog;

var builder = WebApplication.CreateBuilder(args);
Expand All @@ -11,7 +16,35 @@
// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Open Project Platform", Version = "v1" });

c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
In = ParameterLocation.Header,
Description = "Please enter a valid token",
Name = "Authorization",
Type = SecuritySchemeType.Http,
Scheme = "bearer",
BearerFormat = "JWT"
});

c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
new string[] { }
}
});
});

builder.Services.AddDbContext<BhcDbContext>();

Expand All @@ -35,6 +68,32 @@
.AllowAnyHeader();
}));

builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]))

Check warning on line 86 in src/BHC24.Api/Program.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 's' in 'byte[] Encoding.GetBytes(string s)'.
};
});

builder.Services.AddHttpContextAccessor();
builder.Services.AddAuthorization();


builder.Services
.AddScoped<AuthUserProvider>();

builder.Services.AddControllers();

builder.Services.AddAntiforgery();
Expand All @@ -55,12 +114,12 @@
app.Map("/", () => Results.Redirect("/swagger"));

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

app.UseAntiforgery();
app.UseCors("MyPolicy");
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });

using var scope = app.Services.CreateScope();
var context = scope.ServiceProvider.GetService<BhcDbContext>();
Expand Down
34 changes: 34 additions & 0 deletions src/BHC24.Api/Services/AuthUserProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System.Security.Claims;
using BHC24.Api.Persistence.Models;
using Microsoft.AspNetCore.Identity;

namespace BHC24.Api.Services;

public class AuthUserProvider
{
private readonly UserManager<AppUser> _userManager;
private readonly IHttpContextAccessor _httpContextAccessor;

public AuthUserProvider(UserManager<AppUser> userManager, IHttpContextAccessor httpContextAccessor)
{
_userManager = userManager;
_httpContextAccessor = httpContextAccessor;
}

public async Task<AppUser> GetAsync()
{
string? userEmail = _httpContextAccessor.HttpContext?.User.FindFirstValue(ClaimTypes.NameIdentifier);
if (string.IsNullOrEmpty(userEmail))
{
throw new InvalidOperationException("User is not authenticated");
}

AppUser? user = await _userManager.FindByEmailAsync(userEmail);
if (user == null)
{
throw new InvalidOperationException("User not found");
}

return user;
}
}
7 changes: 6 additions & 1 deletion src/BHC24.Api/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,10 @@
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
"AllowedHosts": "*",
"Jwt": {
"Key": "MIHcAgEBBEIBFAXHNII96JvdSLTE9TVt0OTV3GQTTJPWX82FcjglFtZDGmRX6e+y\nzJlEGJkw32s4V4eEYnMgpR7rZ59Acnwt2CegBwYFK4EEACOhgYkDgYYABAGeOCFo\nGy0W4mMZWH402I8CVPGbzjoFz88cv6I2HQQGiVtnaXQcuERUuC1+e0SfDqNuYfCt\nv45wTwAttS0cLT1jbQGUPmSljatd/TO6oQbRgB8ko8b/09pWjCCylz99GN1tzam4\n5D8F2J4ePFHLFE59cpMRXkv0aRAHRetGIt9rAF/1lg==",
"Issuer": "YourIssuer",
"Audience": "YourAudience"
}
}

0 comments on commit 56de9ed

Please sign in to comment.