Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cookie Authentication #896

Open
wants to merge 142 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
142 commits
Select commit Hold shift + click to select a range
89b61ff
cookie set up and trial
handesirikci0rso Feb 22, 2024
7490241
testing
handesirikci0rso Feb 23, 2024
53c8777
sending cookies working version
handesirikci0rso Feb 23, 2024
690f12d
unnecessary files removed
handesirikci0rso Feb 23, 2024
0088a30
prettier
handesirikci0rso Feb 23, 2024
5c548da
remove log messages
handesirikci0rso Feb 23, 2024
f6411e4
message
handesirikci0rso Feb 23, 2024
770ca3f
message
handesirikci0rso Feb 23, 2024
20bf098
message
handesirikci0rso Feb 23, 2024
9e79a6c
message
handesirikci0rso Feb 23, 2024
c848a28
message
handesirikci0rso Feb 23, 2024
9a46389
message
handesirikci0rso Feb 23, 2024
4e73803
message
handesirikci0rso Feb 23, 2024
4c1b9a2
message
handesirikci0rso Feb 23, 2024
4c4ae1b
message
handesirikci0rso Feb 23, 2024
ec24467
message
handesirikci0rso Feb 23, 2024
f103ff9
message
handesirikci0rso Feb 23, 2024
37ebb8b
ignore
handesirikci0rso Feb 23, 2024
d250b74
updated gitignore
handesirikci0rso Feb 23, 2024
41ecf9c
undo reormatting
handesirikci0rso Feb 23, 2024
8447ffd
undo formatting
handesirikci0rso Feb 23, 2024
a755850
refactor startup
handesirikci0rso Feb 23, 2024
0f0fdb5
move bool var to app settings
handesirikci0rso Feb 23, 2024
93f943c
mes
handesirikci0rso Feb 23, 2024
0e7fcaf
var made priv
handesirikci0rso Feb 25, 2024
5f8505a
add claims before creating user
handesirikci0rso Feb 26, 2024
b4f6f70
customclaimfactory added
handesirikci0rso Feb 26, 2024
b2c3f5f
logs removed
handesirikci0rso Feb 26, 2024
4fce7ec
claims
handesirikci0rso Feb 27, 2024
aae87e9
sign in with cookies used
handesirikci0rso Feb 28, 2024
dbf5e81
lockedout case
handesirikci0rso Feb 29, 2024
79a3058
always cookie usage
handesirikci0rso Mar 1, 2024
f073ebf
expiry time added
handesirikci0rso Mar 3, 2024
db7ddbf
waiting for task complete
handesirikci0rso Mar 3, 2024
d842a29
refresh enabled by cookies
handesirikci0rso Mar 5, 2024
f5d99b5
async wait changed
handesirikci0rso Mar 5, 2024
e67cdbc
interface implemented for testing purposes
handesirikci0rso Mar 7, 2024
bf39117
unit tests corrected
handesirikci0rso Mar 7, 2024
081c289
unused import removed
handesirikci0rso Mar 7, 2024
df62eff
dependency injection added
handesirikci0rso Mar 8, 2024
3c0cbe3
refactor
handesirikci0rso Mar 8, 2024
a5d4143
logout added
handesirikci0rso Mar 13, 2024
a850fb5
integration test
handesirikci0rso Mar 14, 2024
c50adb4
session cookie
handesirikci0rso Mar 14, 2024
4bce3d6
cookie set header
handesirikci0rso Mar 14, 2024
d89e329
refactor and endpoint changes
handesirikci0rso Mar 17, 2024
5f333d1
custom middleware for testing purposes
handesirikci0rso Mar 17, 2024
2ef137b
redirection responses changed
handesirikci0rso Mar 19, 2024
fb1e434
authentication integration tests fixed
handesirikci0rso Mar 19, 2024
759faae
integration tests auth with cookies
handesirikci0rso Mar 21, 2024
04a6887
return type changes
handesirikci0rso Mar 21, 2024
3959693
refactor
handesirikci0rso Mar 21, 2024
02fe347
cleanup
handesirikci0rso Mar 21, 2024
376f8de
token messages changed to cookie messages
handesirikci0rso Mar 21, 2024
291c8fa
translateions added
handesirikci0rso Mar 21, 2024
f97e4a9
jwt file removed
handesirikci0rso Mar 21, 2024
851b768
exception init
handesirikci0rso Mar 21, 2024
9028c5f
cleanup
handesirikci0rso Mar 21, 2024
a574ade
refactor
handesirikci0rso Mar 22, 2024
faa6d17
refactor
handesirikci0rso Mar 22, 2024
5c12052
unussed import removed
handesirikci0rso Mar 22, 2024
d1d0417
unussed var removed
handesirikci0rso Mar 22, 2024
37d3582
refactor
handesirikci0rso Mar 22, 2024
9a0f617
refactor
handesirikci0rso Mar 22, 2024
7f1833d
refactor
handesirikci0rso Mar 22, 2024
15b651b
refactor
handesirikci0rso Mar 22, 2024
05a2d60
cleanup
handesirikci0rso Mar 22, 2024
05ba504
cleanup
handesirikci0rso Mar 23, 2024
dc77a1c
Merge branch 'develop' into feature/auth-with-cookies
handesirikci0rso Mar 23, 2024
64df158
try
handesirikci0rso Mar 24, 2024
ad0bfb0
Merge branch 'feature/auth-with-cookies' of https://github.com/orso-c…
handesirikci0rso Mar 24, 2024
edb33b7
try
handesirikci0rso Mar 24, 2024
a1f72e6
try
handesirikci0rso Mar 24, 2024
ed1283b
try
handesirikci0rso Mar 24, 2024
33af4f4
try
handesirikci0rso Mar 24, 2024
47fc2c5
try
handesirikci0rso Mar 24, 2024
191ac77
try
handesirikci0rso Mar 24, 2024
54923d1
try
handesirikci0rso Mar 24, 2024
8eabd57
try
handesirikci0rso Mar 24, 2024
4385055
try
handesirikci0rso Mar 24, 2024
1a89ccc
try
handesirikci0rso Mar 24, 2024
1939fb2
try
handesirikci0rso Mar 24, 2024
31fbce4
should not change wrong password person changed
handesirikci0rso Mar 24, 2024
a5a5f05
set role try
handesirikci0rso Mar 24, 2024
222c75c
set role try
handesirikci0rso Mar 24, 2024
c52a502
set role try
handesirikci0rso Mar 24, 2024
bb0bcb1
try
handesirikci0rso Mar 24, 2024
34137bf
try
handesirikci0rso Mar 24, 2024
7989b6a
try
handesirikci0rso Mar 24, 2024
13de90a
token gen remove
handesirikci0rso Mar 25, 2024
905d9f6
sonar issue try
handesirikci0rso Mar 25, 2024
e9c8826
sonar issue either throw or log
handesirikci0rso Mar 25, 2024
ecdd117
log sonar issue
handesirikci0rso Mar 25, 2024
02651a3
log sonar issue
handesirikci0rso Mar 25, 2024
7af5095
improve unit tests
handesirikci0rso Mar 27, 2024
8673a65
improve unit tests
handesirikci0rso Mar 27, 2024
f70c1cc
improve unit tests
handesirikci0rso Mar 27, 2024
652fcdc
improve unit tests
handesirikci0rso Mar 27, 2024
adee82a
unit test fix
handesirikci0rso Mar 28, 2024
bdb8da9
unit test fix
handesirikci0rso Mar 28, 2024
ac3b12c
unit test fix
handesirikci0rso Mar 28, 2024
9ac4f31
unit test fix
handesirikci0rso Mar 28, 2024
3f3cdaf
unit test fix
handesirikci0rso Mar 28, 2024
d2c0f7f
unit test fix
handesirikci0rso Mar 28, 2024
0417187
unit test fix
handesirikci0rso Mar 28, 2024
c5ffab5
new tests
handesirikci0rso Mar 28, 2024
4655e91
new tests
handesirikci0rso Mar 28, 2024
5ae27db
new tests
handesirikci0rso Mar 28, 2024
4f0a33a
new tests
handesirikci0rso Mar 28, 2024
d30e24e
new tests
handesirikci0rso Mar 28, 2024
0e421da
new tests
handesirikci0rso Mar 28, 2024
7326f36
new tests
handesirikci0rso Mar 28, 2024
bf06073
new tests
handesirikci0rso Mar 28, 2024
d1213fd
new tests
handesirikci0rso Mar 28, 2024
83779e0
gitignore clear
handesirikci0rso Apr 3, 2024
0501814
unused response type removed
handesirikci0rso Apr 3, 2024
bde5a73
auth controller simplified
handesirikci0rso Apr 3, 2024
bcc2473
file place change
handesirikci0rso Apr 5, 2024
9f53762
service call replaced
handesirikci0rso Apr 5, 2024
05a6dfd
config
handesirikci0rso Apr 6, 2024
34f5674
config
handesirikci0rso Apr 6, 2024
b9f5d02
config
handesirikci0rso Apr 6, 2024
8a508e8
config
handesirikci0rso Apr 6, 2024
4d1f447
config
handesirikci0rso Apr 7, 2024
88c1beb
config
handesirikci0rso Apr 7, 2024
593c033
config
handesirikci0rso Apr 7, 2024
7022db9
config
handesirikci0rso Apr 7, 2024
cb14af5
config
handesirikci0rso Apr 7, 2024
66fdf29
config
handesirikci0rso Apr 7, 2024
885383e
config
handesirikci0rso Apr 10, 2024
50bee65
config
handesirikci0rso Apr 11, 2024
b571a48
config
handesirikci0rso Apr 15, 2024
2467d0f
config
handesirikci0rso Apr 15, 2024
30234ca
config
handesirikci0rso Apr 15, 2024
c6b09c5
config
handesirikci0rso Apr 15, 2024
92052a1
config
handesirikci0rso Apr 17, 2024
9b7330b
config
handesirikci0rso Apr 17, 2024
2ab6677
config
handesirikci0rso Apr 17, 2024
fd1b4f1
config
handesirikci0rso Apr 18, 2024
7d55998
diff
handesirikci0rso Apr 23, 2024
aedc58d
diff
handesirikci0rso Apr 23, 2024
02ef089
diff
handesirikci0rso Apr 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -357,4 +357,4 @@ healthchecksdb
c:azurite

#snyk
.dccache
.dccache
16 changes: 9 additions & 7 deletions Orso.Arpa.Api/Controllers/AuthController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,14 @@ public AuthController(IAuthService authService)
/// <response code="422">If validation fails</response>
[AllowAnonymous]
[HttpPost("login")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status422UnprocessableEntity)]
public async Task<ActionResult<TokenDto>> Login([FromBody] LoginDto loginDto)
public async Task<ActionResult> Login([FromBody] LoginDto loginDto)
{
return await _authService.LoginAsync(loginDto, RemoteIpAddress);
await _authService.LoginAsync(loginDto, RemoteIpAddress);
return NoContent();
}

/// <summary>
Expand Down Expand Up @@ -183,14 +184,15 @@ public async Task<ActionResult> CreateNewEmailConfirmationToken(
/// <returns>A new access token. Sets new refresh token cookie</returns>
[AllowAnonymous]
[HttpPost("refreshtoken")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status403Forbidden)]
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status422UnprocessableEntity)]
public async Task<ActionResult<TokenDto>> RefreshAccessToken()
public async Task<ActionResult> RefreshAccessToken()
{
return await _authService.RefreshAccessTokenAsync(RefreshToken, RemoteIpAddress);
await _authService.RefreshAccessTokenAsync(RefreshToken, RemoteIpAddress);
return NoContent();
}

/// <summary>
Expand All @@ -205,7 +207,7 @@ public async Task<ActionResult<TokenDto>> RefreshAccessToken()
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status422UnprocessableEntity)]
public async Task<ActionResult> Logout()
{
await _authService.RevokeRefreshTokenAsync(RefreshToken, RemoteIpAddress);
await _authService.SignOut(RefreshToken, RemoteIpAddress);
return NoContent();
}
}
Expand Down
80 changes: 0 additions & 80 deletions Orso.Arpa.Api/Extensions/JwtBearerConfiguration.cs

This file was deleted.

67 changes: 51 additions & 16 deletions Orso.Arpa.Api/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
using HotChocolate.Types.Pagination;
using MediatR;
using MicroElements.Swashbuckle.FluentValidation.AspNetCore;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
Expand Down Expand Up @@ -101,7 +101,6 @@
using Orso.Arpa.Domain.UserDomain.Enums;
using Orso.Arpa.Domain.UserDomain.Model;
using Orso.Arpa.Domain.UserDomain.Repositories;
using Orso.Arpa.Domain.VenueDomain.Model;
using Orso.Arpa.Infrastructure.Authentication;
using Orso.Arpa.Infrastructure.Authorization;
using Orso.Arpa.Infrastructure.Authorization.AuthorizationRequirements;
Expand All @@ -119,6 +118,7 @@
using SixLabors.ImageSharp.Web.Providers;
using Yoh.Text.Json.NamingPolicies;
using User = Orso.Arpa.Domain.UserDomain.Model.User;
using Microsoft.AspNetCore.Http;

namespace Orso.Arpa.Api
{
Expand Down Expand Up @@ -154,6 +154,7 @@ public void ConfigureServices(IServiceCollection services)
_ = services.AddApplicationInsightsTelemetry();
}
_ = services.AddMediatR(typeof(LoginUser.Handler).Assembly);

_ = services.AddGenericMediatorHandlers();
_ = services.AddAutoMapper(
typeof(LoginDtoMappingProfile).Assembly,
Expand Down Expand Up @@ -373,6 +374,7 @@ private void ConfigureSwagger(IServiceCollection services)
private void RegisterServices(IServiceCollection services)
{
_ = services.AddScoped<IJwtGenerator, JwtGenerator>();
_ = services.AddScoped<CustomCookieAuthenticationEvents>();
_ = services.AddScoped<IUserAccessor, UserAccessor>();
_ = services.AddScoped<ITokenAccessor, TokenAccessor>();
_ = services.AddScoped<IDataSeeder, DataSeeder>();
Expand Down Expand Up @@ -408,6 +410,7 @@ private void RegisterServices(IServiceCollection services)
_ = services.AddScoped<IMyProjectService, MyProjectService>();
_ = services.AddScoped<INewsService, NewsService>();
_ = services.AddScoped<IClubService, ClubService>();
_ = services.AddScoped<ICookieSignIn, CookieSignIn>();
services.AddScoped<IRoomService, RoomService>();
services.AddScoped<IRoomEquipmentService, RoomEquipmentService>();
services.AddScoped<IRoomSectionService, RoomSectionService>();
Expand Down Expand Up @@ -452,6 +455,34 @@ private void ConfigureAuthentication(IServiceCollection services)
.AddRoleManager<RoleManager<Role>>()
.AddUserManager<ArpaUserManager>();

JwtConfiguration jwtConfig = AddConfiguration<JwtConfiguration>(services);


_ = identityBuilder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme;
options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
options.DefaultSignOutScheme = IdentityConstants.ExternalScheme;
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.EventsType = typeof(CustomCookieAuthenticationEvents);
options.Cookie.Name = "sessionCookie";
options.Cookie.Path = "/";
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(jwtConfig.AccessTokenExpiryInMinutes);
options.Cookie.MaxAge = TimeSpan.FromMinutes(jwtConfig.AccessTokenExpiryInMinutes);
options.ReturnUrlParameter = CookieAuthenticationDefaults.ReturnUrlParameter;
})
.AddIdentityCookies();

services.ConfigureApplicationCookie(o =>
{
o.EventsType = typeof(CustomCookieAuthenticationEvents);
});

IdentityConfiguration identityConfig = AddConfiguration<IdentityConfiguration>(services);

_ = services.Configure<IdentityOptions>(opts =>
Expand All @@ -469,11 +500,15 @@ private void ConfigureAuthentication(IServiceCollection services)
_ = services.Configure<EmailConfirmationTokenProviderOptions>(opt =>
opt.TokenLifespan = TimeSpan.FromDays(identityConfig.EmailConfirmationTokenExpiryInDays));

JwtConfiguration jwtConfig = AddConfiguration<JwtConfiguration>(services);
_ = services.Configure<CookiePolicyOptions>(options =>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could it be helpful to have the whole cookie config collected in one place (own method?)

{
options.MinimumSameSitePolicy = SameSiteMode.Strict;
options.ConsentCookie.IsEssential = true;
options.CheckConsentNeeded = context => false;
options.Secure = _hostingEnvironment.IsDevelopment()
? CookieSecurePolicy.None : CookieSecurePolicy.Always;
});

_ = services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearerConfiguration(jwtConfig);
}

private void ConfigureCors(IServiceCollection services)
Expand Down Expand Up @@ -530,20 +565,20 @@ public virtual void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
_ = app.UseIpRateLimiting();

_ = app.UseRouting();

_ = app.UseRequestLocalization();

_ = app.UseErrorResponseLocalizationMiddleware();
_ = app.UseCookiePolicy();

_ = app.UseMiddleware<ErrorHandlingMiddleware>();
_ = app.UseErrorResponseLocalizationMiddleware();

_ = app.UseMiddleware<EnableRequestBodyRewindMiddleware>();

_ = app.UseMiddleware<SecurityHeaderMiddleware>();

ConfigureSecurityHeaders(app, env);

_ = app.UseRouting();

_ = app.UseCors("CorsPolicy");

_ = app.UseHealthChecks("/health");
Expand All @@ -557,6 +592,8 @@ public virtual void Configure(IApplicationBuilder app, IWebHostEnvironment env)
_ = app.UseDefaultFiles(); // use index.html
_ = app.UseStaticFiles();

_ = app.UseMiddleware<ErrorHandlingMiddleware>();
VILLAN3LL3 marked this conversation as resolved.
Show resolved Hide resolved

AddSwagger(app);

_ = app.UseEndpoints(endpoints =>
Expand Down Expand Up @@ -622,10 +659,8 @@ protected virtual void EnsureDatabaseMigrations(IApplicationBuilder app)
IDataSeeder dataSeeder = services.GetRequiredService<IDataSeeder>();
dataSeeder.SeedDataAsync().Wait();
}
catch (Exception ex)
catch (Exception)
{
ILogger<Startup> logger = services.GetRequiredService<ILogger<Startup>>();
logger.LogError(ex, "An error occured during database migration");
throw;
}
}
Expand All @@ -639,12 +674,12 @@ protected void PreloadTranslationsFromDb(IApplicationBuilder app)
ILocalizerCache localizerCache = services.GetRequiredService<ILocalizerCache>();
_ = localizerCache.LoadTranslations();
}
catch (Exception ex)
catch (Exception)
{
ILogger<Startup> logger = services.GetRequiredService<ILogger<Startup>>();
logger.LogError(ex, "Error during localization of data");
throw;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what happens if the throw is removed when this error occurs?

}
}
}

}

Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace Orso.Arpa.Application.AuthApplication.Interfaces
{
public interface IAuthService
{
Task<TokenDto> LoginAsync(LoginDto loginDto, string remoteIpAddress);
Task LoginAsync(LoginDto loginDto, string remoteIpAddress);

Task RegisterAsync(UserRegisterDto registerDto);

Expand All @@ -21,8 +21,9 @@ public interface IAuthService

Task CreateNewEmailConfirmationTokenAsync(CreateEmailConfirmationTokenDto createEmailConfirmationTokenDto);

Task<TokenDto> RefreshAccessTokenAsync(string refreshToken, string remoteIpAddress);
Task RefreshAccessTokenAsync(string refreshToken, string remoteIpAddress);

Task RevokeRefreshTokenAsync(string refreshToken, string remoteIpAddress);
Task SignOut(string refreshToken, string remoteIpAddress);
}
}
19 changes: 12 additions & 7 deletions Orso.Arpa.Application/AuthApplication/Services/AuthService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,18 @@ public AuthService(IMediator mediator, IMapper mapper)
_mediator = mediator;
}

public async Task<TokenDto> LoginAsync(LoginDto loginDto, string remoteIpAddress)
public async Task LoginAsync(LoginDto loginDto, string remoteIpAddress)
{
LoginUser.Command command = _mapper.Map<LoginUser.Command>(loginDto);
command.RemoteIpAddress = remoteIpAddress;
var token = await _mediator.Send(command);
return _mapper.Map<TokenDto>(token);
await _mediator.Send(command);
}

public async Task RegisterAsync(UserRegisterDto registerDto)
{
RegisterUser.Command registerCommand = _mapper.Map<RegisterUser.Command>(registerDto);
await _mediator.Send(registerCommand);

CreateEmailConfirmationToken.Command command = _mapper.Map<CreateEmailConfirmationToken.Command>(registerDto);
await _mediator.Send(command);

Expand Down Expand Up @@ -97,18 +96,24 @@ public async Task CreateNewEmailConfirmationTokenAsync(CreateEmailConfirmationTo
await _mediator.Send(command);
}

public async Task<TokenDto> RefreshAccessTokenAsync(string refreshToken, string remoteIpAddress)
public async Task RefreshAccessTokenAsync(string refreshToken, string remoteIpAddress)
{
var command = new RefreshAccessToken.Command { RefreshToken = refreshToken, RemoteIpAddress = remoteIpAddress };
var token = await _mediator.Send(command);
await _mediator.Send(command);
await RevokeRefreshTokenAsync(refreshToken, remoteIpAddress);
return _mapper.Map<TokenDto>(token);
}

public async Task RevokeRefreshTokenAsync(string refreshToken, string remoteIpAddress)
{
var command = new RevokeRefreshToken.Command { RefreshToken = refreshToken, RemoteIpAddress = remoteIpAddress };
await _mediator.Send(command);
}

public async Task SignOut(string refreshToken, string remoteIpAddress)
{
await RevokeRefreshTokenAsync(refreshToken, remoteIpAddress);
var command = new SignOutUser.Command { };
await _mediator.Send(command);
}
}
}
Loading
Loading