Skip to content

Commit

Permalink
Merge pull request #23 from dof-dss/JwtAuthentication
Browse files Browse the repository at this point in the history
Added jwt authentication
  • Loading branch information
MichaelStevenson2207 authored Sep 14, 2021
2 parents 114329f + edbf8e2 commit 1bd9116
Show file tree
Hide file tree
Showing 8 changed files with 356 additions and 2 deletions.
15 changes: 15 additions & 0 deletions de-institutions-api-core/Entities/ConsumingApplication.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;

namespace de_institutions_api_core.Entities
{
public class ConsumingApplication
{
public int Id { get; set; }
public string ApplicationName { get; set; }
public string ApplicationDescription { get; set; }
public string ApiKey { get; set; }
public string SecretKey { get; set; }
public DateTime DateEntered { get; set; }
public bool IsDisabled { get; set; }
}
}
91 changes: 91 additions & 0 deletions de-institutions-api/Authentication/JwtHelpers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
using de_institutions_api_core.Entities;
using de_institutions_infrastructure.Data;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Text;

namespace de_institutions_api.Authentication
{
public class JwtHelpers
{
private readonly IServiceScopeFactory _serviceScopeFactory;

private const double ISSUED_WITHIN_SECONDS = 30;

public JwtHelpers(IServiceScopeFactory serviceScopeFactory)
{
_serviceScopeFactory = serviceScopeFactory;
}

public IEnumerable<SecurityKey> ResolveSigningKey(string token, SecurityToken securityToken, string kid, TokenValidationParameters validationParameters)
{
var signingKey = new List<SecurityKey>();

var jwtToken = securityToken as JwtSecurityToken;

var kidFromJwt = "";

if (jwtToken.Payload.ContainsKey("kid"))
kidFromJwt = jwtToken.Payload["kid"].ToString();

if (string.IsNullOrEmpty(kidFromJwt)) return signingKey;

var secretKey = GetSigningKeyFromDatabase(kidFromJwt);

if (string.IsNullOrEmpty(secretKey)) return signingKey;

signingKey.Add(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey)));

return signingKey;
}

public bool LifetimeValidator(DateTime? notBefore, DateTime? expires, SecurityToken securityToken, TokenValidationParameters validationParameters)
{
var jwtToken = securityToken as JwtSecurityToken;

return IssuedWithin(jwtToken);
}

private static bool IssuedWithin(JwtSecurityToken jwtToken)
{
var iatValid = false;

if (jwtToken.Payload.ContainsKey("iat"))
{
var issuedString = jwtToken.Payload["iat"].ToString();

var dateTimeOffset = DateTimeOffset.FromUnixTimeSeconds(long.Parse(issuedString));

var issuedDate = dateTimeOffset.DateTime;

var diff = DateTime.UtcNow.Subtract(issuedDate);

if (diff.TotalSeconds < ISSUED_WITHIN_SECONDS) iatValid = true;
}

return iatValid;
}

private string GetSigningKeyFromDatabase(string kidFromJwt)
{
ConsumingApplication consumingApplication = null;

using (var scope = _serviceScopeFactory.CreateScope())
{
var dbContext = scope.ServiceProvider.GetService<InstituitonContext>();

consumingApplication = dbContext.ConsumingApplication.FirstOrDefault(x => x.ApiKey == kidFromJwt && !x.IsDisabled);
}

if (consumingApplication != null && consumingApplication.SecretKey != null)
{
return consumingApplication.SecretKey;
}
else return "";
}
}
}
2 changes: 2 additions & 0 deletions de-institutions-api/Controllers/InstitutionController.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using de_institutions_infrastructure.Features.Institution.Queries;
using dss_common.Extensions;
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;

Expand All @@ -9,6 +10,7 @@ namespace de_institutions_api.Controllers
[ApiVersion("1.0")]
[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
[Authorize]
public class InstitutionController : ControllerBase
{
private readonly IMediator _mediator;
Expand Down
54 changes: 53 additions & 1 deletion de-institutions-api/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using de_institutions_api.Authentication;
using de_institutions_infrastructure.Data;
using de_institutions_infrastructure.Features.Common;
using de_institutions_infrastructure.Features.Institution.Validation;
using FluentValidation.AspNetCore;
using MediatR;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
Expand All @@ -12,6 +14,7 @@
using Microsoft.OpenApi.Models;
using Steeltoe.CloudFoundry.Connector.MySql.EFCore;
using Steeltoe.Extensions.Configuration.CloudFoundry;
using System;
using System.Reflection;

namespace de_institutions_api
Expand All @@ -25,6 +28,7 @@ public Startup(IConfiguration configuration)

public IConfiguration Configuration { get; }
protected CloudFoundryServicesOptions CloudFoundryServicesOptions;
private JwtHelpers _jwtHelpers;

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
Expand Down Expand Up @@ -59,6 +63,31 @@ public void ConfigureServices(IServiceCollection services)
Email = "Michael.Stevenson@finance-ni.gov.uk"
}
});

c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Name = "Authorization",
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer",
BearerFormat = "JWT",
In = ParameterLocation.Header,
Description = "JWT Authorization header using the Bearer scheme."
});

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

services.AddControllers().AddFluentValidation(fv => fv.RegisterValidatorsFromAssemblyContaining<GetAllInstitutionsQueryValidator>());
Expand All @@ -70,11 +99,34 @@ public void ConfigureServices(IServiceCollection services)
services.AddMediatR(typeof(Startup).GetTypeInfo().Assembly, typeof(InstituitonContext).GetTypeInfo().Assembly);

services.AddSingleton<IUriService, UriService>();

services.AddTransient<JwtHelpers>();

services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}
).AddJwtBearer(o =>
{
o.RequireHttpsMetadata = true;
o.TokenValidationParameters.RequireSignedTokens = true;
o.TokenValidationParameters.ValidateAudience = false;
o.TokenValidationParameters.ValidateIssuer = false;
o.TokenValidationParameters.RequireExpirationTime = false;
o.TokenValidationParameters.ValidateIssuerSigningKey = true;
o.TokenValidationParameters.IssuerSigningKeyResolver = _jwtHelpers.ResolveSigningKey;
o.TokenValidationParameters.ValidateLifetime = true;
o.TokenValidationParameters.LifetimeValidator = _jwtHelpers.LifetimeValidator;
o.Validate();
});
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider services)
{
_jwtHelpers = services.GetRequiredService<JwtHelpers>();

// Enable middleware to serve generated Swagger as a JSON endpoint.
app.UseSwagger();

Expand Down
2 changes: 1 addition & 1 deletion de-institutions-infrastructure/Data/InstituitonContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ public InstituitonContext(DbContextOptions<InstituitonContext> options)
}

public DbSet<Institution> Institution { get; set; }

public DbSet<ConsumingApplication> ConsumingApplication { get; set; }
}
}

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using System;

namespace de_institutions_infrastructure.Migrations
{
public partial class AddConsumingAppTable : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "ConsumingApplication",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
ApplicationName = table.Column<string>(nullable: true),
ApplicationDescription = table.Column<string>(nullable: true),
ApiKey = table.Column<string>(nullable: true),
SecretKey = table.Column<string>(nullable: true),
DateEntered = table.Column<DateTime>(nullable: false),
IsDisabled = table.Column<bool>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ConsumingApplication", x => x.Id);
});
}

protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ConsumingApplication");
}
}
}
Loading

0 comments on commit 1bd9116

Please sign in to comment.