Skip to content

Commit

Permalink
Closes #16
Browse files Browse the repository at this point in the history
Backend integration with Keycloak tokens.
  • Loading branch information
Sojusan committed Feb 16, 2024
1 parent 37d1737 commit d3ae3cd
Show file tree
Hide file tree
Showing 24 changed files with 552 additions and 627 deletions.
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,23 @@ To run this application locally it is recommended to have the following installe
- dotnet sdk
- entity framework tools

Firstly there is a need to configure environment variables:

1. Copy `.env.example` as `.env` and populate the environment variables.
1. Copy `appsettings.json` as `appsettings.Development.json` and populate the variables.

Next install dev-certs to use https in powershell

```powershell
dotnet dev-certs https -ep ".aspnet\https\aspnetapp.pfx" -p devcertpasswd --trust
```

or in bash/zsh

```bash
dotnet dev-certs https -ep .aspnet/https/aspnetapp.pfx -p devcertpasswd --trust
```

Next go to the `scripts` directory and run `apply_migrations.ps1`
Next You should go back to the main directory and run `docker compose up --build`
This can be done with the following snippet.
Expand Down
2 changes: 1 addition & 1 deletion backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.

FROM mcr.microsoft.com/dotnet/aspnet:7.0.2-alpine3.17-amd64 AS base
FROM mcr.microsoft.com/dotnet/aspnet:7.0.10-alpine3.18 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
Expand Down
6 changes: 6 additions & 0 deletions backend/src/api/Constants/ErrorMessages.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace api.Constants;

public static class ErrorMessages
{
public const string UserAlreadyExists = "User already exists";
}
92 changes: 35 additions & 57 deletions backend/src/api/Controllers/AuthenticationController.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using api.Data.DTO;
using api.Constants;
using api.Data.DTO;
using api.Services.Users;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.IdentityModel.Tokens.Jwt;

namespace api.Controllers;

Expand All @@ -10,77 +12,53 @@ namespace api.Controllers;
public class AuthenticationController : ControllerBase
{
private readonly IUserService _userService;
private readonly ILogger<AuthenticationController> _logger;

public AuthenticationController(IUserService userService)
public AuthenticationController(IUserService userService, ILogger<AuthenticationController> logger)
{
_userService = userService;
_logger = logger;
}

[HttpPost("register")]
[ProducesResponseType(200)]
[ProducesResponseType(400)]
public async Task<IActionResult> Register([FromBody] UserDTO user)
[HttpGet("create-user")]
[Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> CreateUser()
{
if (!ModelState.IsValid) { return BadRequest("Invalid data provided!"); }
var accessToken = Request.Headers["Authorization"].ToString().Replace("Bearer ", "");
var jsonTokenData = new JwtSecurityTokenHandler().ReadJwtToken(accessToken);
var keycloakUuid = jsonTokenData.Subject;
var userEmail = jsonTokenData.Claims.FirstOrDefault(claim => claim.Type == "email")?.Value;

var (IsSuccess, Error) = await _userService.Register(user);
if (IsSuccess)
if (userEmail == null)
{
return Ok("User created");
}
else
{
return BadRequest(Error);
_logger.LogInformation("User with keycloakUuid={keycloakUuid} tried to log in without email.", keycloakUuid);
return BadRequest("Required data missing");
}
}

[HttpPost("login")]
[ProducesResponseType(200)]
[ProducesResponseType(400)]
[ProducesResponseType(401)]
public async Task<IActionResult> Login([FromBody] UserDTO user)
{
if (!ModelState.IsValid) { return BadRequest("Please provide login credentials!"); }
var (isSuccess, error) = await _userService.CreateKeycloakUser(
new UserDto
{
KeycloakUuid = keycloakUuid,
Email = userEmail
}
);

var (IsSuccess, AuthResult, Error) = await _userService.Login(user);
if (IsSuccess)
if (isSuccess)
{
return Ok(AuthResult);
}
else
{
return Unauthorized(Error);
}
}

[HttpPost("refresh-token")]
[ProducesResponseType(200)]
[ProducesResponseType(400)]
[ProducesResponseType(401)]
public async Task<IActionResult> RefreshToken([FromBody] TokenRequestDTO tokenRequestDTO)
{
if (!ModelState.IsValid) { return BadRequest("Invalid token request."); }
var (IsSuccess, AuthResult, Error) = await _userService.RefreshLogin(tokenRequestDTO);
if (IsSuccess)
{
return Ok(AuthResult);
_logger.LogInformation("The user {email} has been created successfully.", userEmail);
return Ok("User created");
}
else
{
return Unauthorized(Error);
}
}

[HttpPost("logout")]
[ProducesResponseType(200)]
[Authorize()]
public async Task<IActionResult> Logout()
{
if (HttpContext.User?.Identity?.Name is not null)
{
await _userService.Logout(HttpContext.User.Identity.Name);
if (error == ErrorMessages.UserAlreadyExists)
{
_logger.LogInformation("The user {email} has already been created.", userEmail);
return Ok("User is already created");
}
_logger.LogInformation("Failure during creation of {email} user.", userEmail);
return BadRequest(error);
}
return Ok("The user has been logged out.");
}

}
4 changes: 2 additions & 2 deletions backend/src/api/Controllers/GreetingsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ public class GreetingsController : ControllerBase
{
[HttpGet]
[Authorize]
[ProducesResponseType(200)]
[ProducesResponseType(StatusCodes.Status200OK)]
[Route("user")]
public IActionResult Greet()
{
return Ok($"Hello {HttpContext?.User?.Identity?.Name ?? "World" }");
}

[HttpGet]
[ProducesResponseType(200)]
[ProducesResponseType(StatusCodes.Status200OK)]
[Route("HelloWorld")]
public IActionResult HelloWorld()
{
Expand Down
19 changes: 0 additions & 19 deletions backend/src/api/Data/DAO/RefreshToken.cs

This file was deleted.

9 changes: 7 additions & 2 deletions backend/src/api/Data/DAO/UserModel.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
using Microsoft.AspNetCore.Identity;
using System.ComponentModel.DataAnnotations;

namespace api.Data.DAO;

public class UserModel : IdentityUser
public class UserModel
{
[Key]
public int Id { get; set; }
public required string KeycloakUuid { get; set; }
[EmailAddress]
public required string Email { get; set; }
}
10 changes: 0 additions & 10 deletions backend/src/api/Data/DTO/AuthResultDTO.cs

This file was deleted.

12 changes: 0 additions & 12 deletions backend/src/api/Data/DTO/TokenRequestDTO.cs

This file was deleted.

9 changes: 4 additions & 5 deletions backend/src/api/Data/DTO/UserDTO.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@

namespace api.Data.DTO;

public class UserDTO
public class UserDto
{
[Required]
[EmailAddress]
public string Email { get; set; }

public required string KeycloakUuid { get; set; }
[Required]
public string Password { get; set; }
[EmailAddress]
public required string Email { get; set; }
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
namespace api.Data;
public class JwtOptions
public class KeycloakJwtOptions
{
public string? Issuer { get; set; }
public string? Audience { get; set; }
Expand Down
4 changes: 2 additions & 2 deletions backend/src/api/Data/UsersDbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@

namespace api.Data;

public class UsersDbContext : IdentityDbContext<UserModel>
public class UsersDbContext : DbContext
{
public UsersDbContext(DbContextOptions<UsersDbContext> options) : base(options)
{
}
public DbSet<RefreshToken> RefreshTokens { get; set; }
public DbSet<UserModel> Users { get; set; }
}
45 changes: 45 additions & 0 deletions backend/src/api/LoggingExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using Serilog;
using Serilog.Configuration;
using Serilog.Core;
using Serilog.Events;

namespace api;

public static class LoggingExtensions
{
public static LoggerConfiguration WithCorrelationId(this LoggerEnrichmentConfiguration config)
=> config.With(new CorrelationIdEnricher());
}

public class CorrelationIdEnricher : ILogEventEnricher
{
private const string _propertyName = "CorelationId";
private readonly IHttpContextAccessor _contextAccessor;

public CorrelationIdEnricher()
{
_contextAccessor = new HttpContextAccessor();
}

public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{
var httpContext = _contextAccessor.HttpContext;
if (httpContext == null)
{
return;
}

if (httpContext.Items[_propertyName] is LogEventProperty logEventProperty)
{
logEvent.AddPropertyIfAbsent(logEventProperty);
return;
}

var correlationId = Guid.NewGuid().ToString();

var correlationIdProperty = new LogEventProperty(_propertyName, new ScalarValue(correlationId));
logEvent.AddOrUpdateProperty(correlationIdProperty);

httpContext.Items.Add(_propertyName, correlationIdProperty);
}
}
50 changes: 50 additions & 0 deletions backend/src/api/Migrations/20240214220927_KeycloakUser.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit d3ae3cd

Please sign in to comment.