Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 3 additions & 2 deletions .github/workflows/sonarcloud-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ jobs:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
shell: powershell
run: |
.\.sonar\scanner\dotnet-sonarscanner begin /k:"web-eid_web-eid-authtoken-validation-dotnet" /o:"web-eid" /d:sonar.cs.opencover.reportsPaths="**/TestResults/**/coverage.opencover.xml" -d:sonar.cs.vstest.reportsPaths="**/TestResults/*.trx" /d:sonar.verbose=true /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io"
.\.sonar\scanner\dotnet-sonarscanner begin /k:"web-eid_web-eid-authtoken-validation-dotnet" /o:"web-eid" /d:sonar.cs.opencover.reportsPaths="**/TestResults/**/coverage.opencover.xml" /d:sonar.cs.vstest.reportsPaths="**/TestResults/*.trx" /d:sonar.verbose=true /d:sonar.token="$env:SONAR_TOKEN" /d:sonar.host.url="https://sonarcloud.io"
dotnet build --configuration Release --no-restore src/WebEid.Security.sln
dotnet format src/WebEid.Security.sln --verify-no-changes --no-restore
dotnet test src/WebEid.Security.sln --logger trx --collect:"XPlat Code Coverage" -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencover --results-directory "TestResults"
.\.sonar\scanner\dotnet-sonarscanner end /d:sonar.login="${{ secrets.SONAR_TOKEN }}"
.\.sonar\scanner\dotnet-sonarscanner end /d:sonar.token="$env:SONAR_TOKEN"
Comment thread
mrts marked this conversation as resolved.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,16 @@ ChallengeNonce challengeNonce = nonceGenerator.GenerateAndStoreNonce(timeToLive)

The `GenerateAndStoreNonce(TimeSpan ttl)` method both generates the nonce and stores it in the store. The `ttl` parameter defines nonce time-to-live duration. When the time-to-live passes, the nonce is considered to be expired.

# Code formatting

The project uses `.editorconfig` for .NET code formatting rules.

To format the library code, run:

```bash
dotnet format src/WebEid.Security.sln --no-restore
```

## Feedback

For technical support or to report issues, please submit a [support ticket](https://github.com/web-eid/web-eid-authtoken-validation-dotnet/issues) or contact our support team at [help@ria.ee](mailto:help@ria.ee).
10 changes: 10 additions & 0 deletions example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -266,3 +266,13 @@ app.UseForwardedHeaders(new ForwardedHeadersOptions
By default, this middleware is already enabled in the application.

A Docker Compose configuration file `docker-compose.yml` is available in the `src` directory for running the Docker image `web-eid-asp-dotnet-example` on port 8480 behind a reverse proxy.

# Code formatting

The project uses `.editorconfig` for .NET code formatting rules.

To format the library code, run:

```bash
dotnet format example/src/WebEid.AspNetCore.Example.sln --no-restore
```
453 changes: 453 additions & 0 deletions example/src/.editorconfig

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

namespace WebEid.AspNetCore.Example.Certificates
namespace WebEid.AspNetCore.Example.Certificates
{
using System.Collections.Generic;
using System.IO;
Expand All @@ -26,33 +26,19 @@

internal static class CertificateLoader
{
public static X509Certificate2[] LoadTrustedCaCertificatesFromDisk(bool isTest = false)
{
return new FileReader(GetCertPath(isTest), "*.cer").ReadFiles()
.Select(file => new X509Certificate2(file))
.ToArray();
}
public static X509Certificate2[] LoadTrustedCaCertificatesFromDisk(bool isTest = false) => [.. new FileReader(GetCertPath(isTest), "*.cer").ReadFiles().Select(X509CertificateLoader.LoadCertificate)];

private static string GetCertPath(bool isTest)
{
return isTest ? "Certificates/Dev" : "Certificates/Prod";
}
private static string GetCertPath(bool isTest) => isTest ? "Certificates/Dev" : "Certificates/Prod";
}

internal class FileReader
internal sealed class FileReader(string path, string? searchPattern = null)
{
private readonly string path;
private readonly string searchPattern;

public FileReader(string path, string searchPattern = null)
{
this.path = path;
this.searchPattern = searchPattern;
}
private readonly string path = path;
private readonly string? searchPattern = searchPattern;

public IEnumerable<byte[]> ReadFiles()
{
foreach (var file in Directory.EnumerateFiles(this.path, this.searchPattern))
foreach (var file in Directory.EnumerateFiles(path, searchPattern ?? "*"))
{
yield return File.ReadAllBytes(file);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,13 @@
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

namespace WebEid.AspNetCore.Example
namespace WebEid.AspNetCore.Example
{
using System.Linq;
using System.Security.Claims;

public static class ClaimsIdentityExtensions
{
public static string GetIdCode(this ClaimsIdentity identity)
{
return identity.Claims.SingleOrDefault(claim => claim.Type == ClaimTypes.NameIdentifier)?.Value;
}
public static string? GetIdCode(this ClaimsIdentity identity) => identity.Claims.SingleOrDefault(claim => claim.Type == ClaimTypes.NameIdentifier)?.Value;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,46 +19,38 @@

namespace WebEid.AspNetCore.Example.Controllers.Api
{
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
using Security.Util;
using Security.Validator;
using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Security.Challenge;
using Security.Util;
using Security.Validator;
using WebEid.AspNetCore.Example.Dto;
using WebEid.Security.AuthToken;
using System;

[Route("[controller]")]
[ApiController]
public class AuthController : BaseController
public class AuthController(IAuthTokenValidator authTokenValidator, IChallengeNonceStore challengeNonceStore) : BaseController
{
private readonly IAuthTokenValidator authTokenValidator;
private readonly IChallengeNonceStore challengeNonceStore;

public AuthController(IAuthTokenValidator authTokenValidator, IChallengeNonceStore challengeNonceStore)
{
this.authTokenValidator = authTokenValidator;
this.challengeNonceStore = challengeNonceStore;
}
private readonly IAuthTokenValidator authTokenValidator = authTokenValidator;
private readonly IChallengeNonceStore challengeNonceStore = challengeNonceStore;

[HttpPost("login")]
public async Task<IActionResult> Login([FromBody] AuthenticateRequestDto dto)
{
try
{
await SignInUser(dto?.AuthToken);
return Ok();
}
catch (ArgumentNullException)
if (dto?.AuthToken is null)
{
return BadRequest(new { error = "Missing auth_token" });
}

await SignInUser(dto.AuthToken);
return Ok();
}

[HttpPost("logout")]
Expand Down Expand Up @@ -108,13 +100,13 @@ await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(identity),
new AuthenticationProperties { AllowRefresh = true });

// Assign a unique ID within the session to enable the use of a unique temporary container name across successive requests.
// A unique temporary container name is required to facilitate simultaneous signing from multiple browsers.
SetUniqueIdInSession();
}

private static void AddNewClaimIfCertificateHasData(List<Claim> claims, string claimType, Func<string> dataGetter)
private static void AddNewClaimIfCertificateHasData(List<Claim> claims, string claimType, Func<string> dataGetter)
{
var claimData = dataGetter();
if (!string.IsNullOrEmpty(claimData))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,34 +17,23 @@
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

namespace WebEid.AspNetCore.Example.Controllers.Api
namespace WebEid.AspNetCore.Example.Controllers.Api
{
using System;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

public abstract class BaseController : ControllerBase
{
const string uniqueIdKey = "UniqueId";
private const string UniqueIdKey = "UniqueId";

protected void RemoveUserContainerFile()
{
System.IO.File.Delete(GetUserContainerName());
}
protected void RemoveUserContainerFile() => System.IO.File.Delete(GetUserContainerName());

protected void SetUniqueIdInSession()
{
HttpContext.Session.SetString(uniqueIdKey, Guid.NewGuid().ToString());
}
protected void SetUniqueIdInSession() => HttpContext.Session.SetString(UniqueIdKey, Guid.NewGuid().ToString());

private string GetUniqueIdFromSession()
{
return HttpContext.Session.GetString(uniqueIdKey);
}
private string GetUniqueIdFromSession() => HttpContext.Session.GetString(UniqueIdKey)
?? throw new InvalidOperationException("Unique ID not found in session.");

protected string GetUserContainerName()
{
return $"container_{GetUniqueIdFromSession()}";
}
protected string GetUserContainerName() => $"container_{GetUniqueIdFromSession()}";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,18 @@
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

namespace WebEid.AspNetCore.Example.Controllers.Api
namespace WebEid.AspNetCore.Example.Controllers.Api
{
using System;
using Microsoft.AspNetCore.Mvc;
using Security.Challenge;
using System;
using WebEid.AspNetCore.Example.Dto;

[Route("auth")]
[ApiController]
public class ChallengeController : BaseController
public class ChallengeController(IChallengeNonceGenerator challengeNonceGenerator) : BaseController
{
private readonly IChallengeNonceGenerator challengeNonceGenerator;

public ChallengeController(IChallengeNonceGenerator challengeNonceGenerator)
{
this.challengeNonceGenerator = challengeNonceGenerator;
}
private readonly IChallengeNonceGenerator challengeNonceGenerator = challengeNonceGenerator;

[HttpGet]
[Route("challenge")]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2025-2025 Estonian Information System Authority
// Copyright (c) 2025-2025 Estonian Information System Authority
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
Expand All @@ -21,13 +21,13 @@ namespace WebEid.AspNetCore.Example.Controllers.Api
{
using System;
using System.Text;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Options;
using Services;
using Security.Challenge;
using Services;

[ApiController]
[Route("auth/mobile")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,34 +22,30 @@ namespace WebEid.AspNetCore.Example.Controllers.Api
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Dto;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Dto;
using Services;
using Signing;

[Route("[controller]")]
[ApiController]
[Authorize(Policy = "LoggedInOnly")]
public class SignController : BaseController
public class SignController(SigningService signingService, MobileSigningService mobileSigningService, ILogger logger) : BaseController
{
private const string SignedFile = "example-for-signing.asice";
private readonly SigningService signingService;
private readonly MobileSigningService mobileSigningService;
private readonly ILogger logger;

public SignController(SigningService signingService, MobileSigningService mobileSigningService, ILogger logger)
{
this.signingService = signingService;
this.mobileSigningService = mobileSigningService;
this.logger = logger;
}
private readonly SigningService signingService = signingService;
private readonly MobileSigningService mobileSigningService = mobileSigningService;
private readonly ILogger logger = logger;

[HttpPost("prepare")]
public DigestDto Prepare([FromBody] CertificateDto data)
{
return signingService.PrepareContainer(data, (ClaimsIdentity)HttpContext.User.Identity, GetUserContainerName());
var identity = HttpContext.User.Identity as ClaimsIdentity
?? throw new InvalidOperationException("User identity is missing or invalid.");

return signingService.PrepareContainer(data, identity, GetUserContainerName());
}

[HttpPost("sign")]
Expand All @@ -62,15 +58,19 @@ public FileDto Sign([FromBody] SignatureDto data)
[HttpPost("mobile/init")]
public MobileSigningService.MobileInitRequest MobileInit()
{
var identity = (ClaimsIdentity)HttpContext.User.Identity;
var identity = HttpContext.User.Identity as ClaimsIdentity
?? throw new InvalidOperationException("User identity is missing or invalid.");

var container = GetUserContainerName();
return mobileSigningService.InitCertificateOrSigningRequest(identity, container);
}

[HttpPost("mobile/certificate")]
public MobileSigningService.MobileInitRequest CertificatePost([FromBody] CertificateDto certificateDto)
{
var identity = (ClaimsIdentity)HttpContext.User.Identity;
var identity = HttpContext.User.Identity as ClaimsIdentity
?? throw new InvalidOperationException("User identity is missing or invalid.");

var containerName = GetUserContainerName();

return mobileSigningService.InitSigningRequest(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,12 @@
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

namespace WebEid.AspNetCore.Example.Controllers
namespace WebEid.AspNetCore.Example.Controllers
{
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

public class WelcomeController : Controller
{
public IActionResult Index()
{
return View();
}
public IActionResult Index() => View();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

namespace WebEid.AspNetCore.Example.Dto
namespace WebEid.AspNetCore.Example.Dto
{
using System.Text.Json.Serialization;
using Security.AuthToken;

public class AuthenticateRequestDto
{
[JsonPropertyName("auth_token")]
public WebEidAuthToken AuthToken { get; set; }
public required WebEidAuthToken AuthToken { get; set; }
}
}
Loading
Loading