Add API key authorisation.

This commit is contained in:
Robert Marshall 2021-05-08 15:15:02 +01:00
parent f8e8774d8c
commit c041c42dcf
8 changed files with 124 additions and 5 deletions

View file

@ -18,10 +18,16 @@ steps:
environment: environment:
ConnectionString: ConnectionString:
from_secret: ConnectionString from_secret: ConnectionString
AuthEndpoint:
from_secret: AuthEndpoint
AuthApiKey:
from_secret: AuthApiKey
commands: commands:
- chmod +x ./build.sh - chmod +x ./build.sh
- ./build.sh - ./build.sh
- sed -i "s/<DatabaseConnectionString>/$ConnectionString/g" output/appsettings.json - sed -i "s/<DatabaseConnectionString>/$ConnectionString/g" output/appsettings.json
- sed -i "s/<AuthEndpoint>/$AuthEndpoint/g" output/appsettings.json
- sed -i "s/<AuthApiKey>/$AuthApiKey/g" output/appsettings.json
- cp api.blog.service output/ - cp api.blog.service output/
- cp -r ./output/* /output - cp -r ./output/* /output
- name: restart service - name: restart service

View file

@ -0,0 +1,59 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
namespace Robware.Api.Blog.Authentication {
public class ApiKeyAuthenticationHandler : AuthenticationHandler<ApiKeyAuthenticationOptions> {
private const string ApiKeyHeaderName = "X-Api-Key";
private readonly ApiKeyValidator _apiKeyValidator;
public ApiKeyAuthenticationHandler(IOptionsMonitor<ApiKeyAuthenticationOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock,
ApiKeyValidator apiKeyValidator)
: base(options, logger, encoder, clock) {
_apiKeyValidator = apiKeyValidator;
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync() {
if (!Request.Headers.TryGetValue(ApiKeyHeaderName, out var apiKeyHeaderValues)) {
return AuthenticateResult.NoResult();
}
var apiKey = apiKeyHeaderValues.FirstOrDefault();
if (apiKeyHeaderValues.Count == 0 || string.IsNullOrWhiteSpace(apiKey)) {
return AuthenticateResult.NoResult();
}
if (await _apiKeyValidator.Validate(apiKey)) {
var claims = new List<Claim>
{
new Claim(ApiKeyHeaderName, apiKey)
};
var identity = new ClaimsIdentity(claims, Options.AuthenticationType);
var principal = new ClaimsPrincipal(new[] { identity });
var ticket = new AuthenticationTicket(principal, Options.Scheme);
return AuthenticateResult.Success(ticket);
}
return AuthenticateResult.Fail("Invalid API Key provided.");
}
protected override async Task HandleChallengeAsync(AuthenticationProperties properties) {
Response.StatusCode = 401;
}
protected override async Task HandleForbiddenAsync(AuthenticationProperties properties) {
Response.StatusCode = 403;
}
}
}

View file

@ -0,0 +1,9 @@
using Microsoft.AspNetCore.Authentication;
namespace Robware.Api.Blog.Authentication {
public class ApiKeyAuthenticationOptions : AuthenticationSchemeOptions {
public const string DefaultScheme = "API Key";
public string Scheme => DefaultScheme;
public string AuthenticationType = DefaultScheme;
}
}

View file

@ -0,0 +1,18 @@
using System.Net.Http;
using System.Threading.Tasks;
namespace Robware.Api.Blog.Authentication
{
public class ApiKeyValidator {
private readonly HttpClient _httpClient;
public ApiKeyValidator(HttpClient httpClient) {
_httpClient = httpClient;
}
public async Task<bool> Validate(string apiKey) {
var response = await _httpClient.GetAsync("api/validate?key=" + apiKey);
return response.IsSuccessStatusCode;
}
}
}

View file

@ -0,0 +1,10 @@
using Microsoft.AspNetCore.Authentication;
using System;
namespace Robware.Api.Blog.Authentication {
public static class AuthenticationBuilderExtensions {
public static AuthenticationBuilder AddApiKeySupport(this AuthenticationBuilder authenticationBuilder, Action<ApiKeyAuthenticationOptions> options) {
return authenticationBuilder.AddScheme<ApiKeyAuthenticationOptions, ApiKeyAuthenticationHandler>(ApiKeyAuthenticationOptions.DefaultScheme, options);
}
}
}

View file

@ -6,7 +6,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="MongoDB.Driver" Version="2.10.4" /> <PackageReference Include="MongoDB.Driver" Version="2.10.4" />
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -4,8 +4,10 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using MongoDB.Driver; using MongoDB.Driver;
using Robware.Api.Blog.Authentication;
using Robware.Blog; using Robware.Blog;
using Robware.Data; using Robware.Data;
using System;
namespace Robware.Api.Blog { namespace Robware.Api.Blog {
public class Startup { public class Startup {
@ -22,6 +24,17 @@ namespace Robware.Api.Blog {
services services
.AddSingleton<IMongoDatabase>(SetupMongoDatabase()) .AddSingleton<IMongoDatabase>(SetupMongoDatabase())
.AddSingleton<IBlogRepository, BlogRepository>(); .AddSingleton<IBlogRepository, BlogRepository>();
services.AddHttpClient<ApiKeyValidator>(client => {
client.BaseAddress = new Uri(Configuration["authEndpoint"]);
client.DefaultRequestHeaders.Add("x-api-key", new[] {Configuration["authApiKey"]});
});
services.AddAuthentication(options => {
options.DefaultAuthenticateScheme = ApiKeyAuthenticationOptions.DefaultScheme;
options.DefaultChallengeScheme = ApiKeyAuthenticationOptions.DefaultScheme;
})
.AddApiKeySupport(options => { });
} }
private IMongoDatabase SetupMongoDatabase() { private IMongoDatabase SetupMongoDatabase() {
@ -39,10 +52,12 @@ namespace Robware.Api.Blog {
app.UseRouting(); app.UseRouting();
app.UseAuthentication();
app.UseAuthorization(); app.UseAuthorization();
app.UseEndpoints(endpoints => { app.UseEndpoints(endpoints => {
endpoints.MapControllers(); endpoints.MapControllers().RequireAuthorization();
}); });
} }
} }

View file

@ -16,5 +16,7 @@
}, },
"ConnectionStrings": { "ConnectionStrings": {
"database": "<DatabaseConnectionString>" "database": "<DatabaseConnectionString>"
} },
"authEndpoint": "<AuthEndpoint>",
"authApiKey": "<AuthApiKey>"
} }