Skip to content
This repository has been archived by the owner on Oct 23, 2021. It is now read-only.

JWT handler for mobile development #433

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
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
2 changes: 2 additions & 0 deletions .github/workflows/test_backend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ jobs:
Authentication__Google__ClientSecret: ${{ secrets.Authentication__Google__ClientSecret }}
Authentication__Twitter__ConsumerKey: ${{ secrets.Authentication__Twitter__ConsumerKey }}
Authentication__Twitter__ConsumerSecret: ${{ secrets.Authentication__Twitter__ConsumerSecret }}
Authentication__Jwt__Certificate: ${{ secrets.Authentication__Jwt__Certificate }}
Authentication__Jwt__CertificatePassword: ${{ secrets.Authentication__Jwt__CertificatePassword }}

services:
postgres:
Expand Down
6 changes: 3 additions & 3 deletions CollAction.Tests/CollAction.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="5.0.8" />
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="5.0.9" />
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="5.0.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="Verify.Xunit" Version="11.20.3" />
<PackageReference Include="Verify.Xunit" Version="11.24.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.console" Version="2.4.1">
<PrivateAssets>all</PrivateAssets>
Expand Down
39 changes: 38 additions & 1 deletion CollAction.Tests/Integration/Endpoint/GraphQlTests.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
using CollAction.Models;
using CollAction.Data;
using CollAction.Models;
using CollAction.Services;
using CollAction.Services.Crowdactions;
using CollAction.Services.Crowdactions.Models;
using CollAction.Services.Email;
using Microsoft.AspNetCore.TestHost;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using Moq;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Security.Cryptography.X509Certificates;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
Expand All @@ -23,12 +31,16 @@ namespace CollAction.Tests.Integration.Endpoint
public sealed class GraphQlTests : IntegrationTestBase
{
private readonly ICrowdactionService crowdactionService;
private readonly ApplicationDbContext context;
private readonly SeedOptions seedOptions;
private readonly IConfiguration configuration;

public GraphQlTests()
{
crowdactionService = Scope.ServiceProvider.GetRequiredService<ICrowdactionService>();
context = Scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
seedOptions = Scope.ServiceProvider.GetRequiredService<IOptions<SeedOptions>>().Value;
configuration = Scope.ServiceProvider.GetRequiredService<IConfiguration>();
}

[Fact]
Expand Down Expand Up @@ -114,6 +126,31 @@ public async Task TestAuthorization()
Assert.True(response.IsSuccessStatusCode, content);
result = JsonDocument.Parse(content);
Assert.Throws<KeyNotFoundException>(() => result.RootElement.GetProperty("errors"));

// Retry call as jwt admin
var user = await context.Users.FirstAsync();
var authSection = configuration.GetSection("Authentication");
var jwtSection = authSection.GetSection("Jwt");
using X509Certificate2 certificate = new(Convert.FromBase64String(jwtSection["Certificate"]), jwtSection["CertificatePassword"]);
var securityToken = new JwtSecurityToken(
"CollAction",
"CollAction",
new Claim[]
{
new Claim("sub", user.Id),
new Claim("role", "admin"),
new Claim("name", user.FullName),
},
expires: DateTime.Now.AddDays(1),
signingCredentials: new SigningCredentials(new X509SecurityKey(certificate), "RS256"));
string httpToken = new JwtSecurityTokenHandler().WriteToken(securityToken);
httpClient.DefaultRequestHeaders.Clear();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", httpToken);
response = await PerformGraphQlQuery(httpClient, QueryCrowdactions, null);
content = await response.Content.ReadAsStringAsync();
Assert.True(response.IsSuccessStatusCode, content);
result = JsonDocument.Parse(content);
Assert.Throws<KeyNotFoundException>(() => result.RootElement.GetProperty("errors"));
}

[Fact]
Expand Down
29 changes: 15 additions & 14 deletions CollAction/CollAction.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -35,34 +35,35 @@

<ItemGroup>
<PackageReference Include="AspNetCore.IServiceCollection.AddIUrlHelper" Version="1.1.0" />
<PackageReference Include="AWSSDK.S3" Version="3.7.1.16" />
<PackageReference Include="AWSSDK.SimpleEmail" Version="3.7.0.44" />
<PackageReference Include="AWSSDK.S3" Version="3.7.1.28" />
<PackageReference Include="AWSSDK.SimpleEmail" Version="3.7.0.56" />
<PackageReference Include="Faker.Net" Version="1.5.138" />
<PackageReference Include="GraphiQL" Version="2.0.0" />
<PackageReference Include="GraphQL" Version="2.4.0" />
<PackageReference Include="GraphQL.Authorization" Version="2.1.29" />
<PackageReference Include="GraphQL.EntityFramework" Version="9.4.0" />
<PackageReference Include="Hangfire.AspNetCore" Version="1.7.24" />
<PackageReference Include="Hangfire.PostgreSql" Version="1.8.5.4" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.34" />
<PackageReference Include="Hangfire.PostgreSql" Version="1.8.6" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.36" />
<PackageReference Include="HtmlSanitizer" Version="5.0.404" />
<PackageReference Include="MailChimp.Net.V3" Version="5.2.0" />
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.17.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.Facebook" Version="5.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.Google" Version="5.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.Twitter" Version="5.0.8" />
<PackageReference Include="Microsoft.AspNetCore.DataProtection.EntityFrameworkCore" Version="5.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="5.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="5.0.8" />
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.18.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.Facebook" Version="5.0.9" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.Google" Version="5.0.9" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.9" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.Twitter" Version="5.0.9" />
<PackageReference Include="Microsoft.AspNetCore.DataProtection.EntityFrameworkCore" Version="5.0.9" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="5.0.9" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="5.0.9" />
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="5.0.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.8" PrivateAssets="all">
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.9" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.9" PrivateAssets="all">
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="5.0.8" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="5.0.9" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="5.0.2" PrivateAssets="All" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="5.0.7" />
<PackageReference Include="RazorLight" Version="2.0.0-beta9" />
Expand Down
3 changes: 3 additions & 0 deletions CollAction/Controllers/GraphQlController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using GraphQL.Types;
using GraphQL.Validation;
using GraphQL.Validation.Complexity;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Caching.Memory;
Expand All @@ -22,6 +23,8 @@ namespace CollAction.Controllers
{
[Route("graphql")]
[ApiController]
[Authorize(AuthenticationSchemes = "Identity.Application,Identity.External,Identity.TwoFactorRememberMe,Identity.TwoFactorUserId,Facebook,Twitter,Google,Bearer")]
[AllowAnonymous]
public sealed class GraphQlController : Controller
{
private readonly IDocumentExecuter executer;
Expand Down
64 changes: 42 additions & 22 deletions CollAction/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@
using GraphiQl;
using Hangfire;
using Hangfire.PostgreSql;
using MailChimp.Net;
using MailChimp.Net.Interfaces;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Hosting;
Expand All @@ -30,11 +29,13 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
using RazorLight;
using RazorLight.Extensions;
using Serilog;
using Stripe;
using System;
using System.Security.Cryptography.X509Certificates;

namespace CollAction
{
Expand Down Expand Up @@ -76,24 +77,6 @@ public void ConfigureServices(IServiceCollection services)
o.Cookie.SecurePolicy = CookieSecurePolicy.Always;
});

IConfigurationSection authSection = configuration.GetSection("Authentication");
services.AddAuthentication()
.AddFacebook(options =>
{
authSection.GetSection("Facebook").Bind(options);
options.Scope.Add("email");
})
.AddTwitter(options =>
{
authSection.GetSection("Twitter").Bind(options);
options.RetrieveUserDetails = true;
})
.AddGoogle(options =>
{
authSection.GetSection("Google").Bind(options);
options.Scope.Add("email");
});

services.AddApplicationInsightsTelemetry(configuration);

services.AddMvc()
Expand Down Expand Up @@ -183,6 +166,42 @@ public void ConfigureServices(IServiceCollection services)
options.Password.RequireNonAlphanumeric = false;
options.Password.RequiredLength = 8;
}).ValidateDataAnnotations();

IConfigurationSection authSection = configuration.GetSection("Authentication");
var authenticationBuilder = services.AddAuthentication()
.AddFacebook(options =>
{
authSection.GetSection("Facebook").Bind(options);
options.Scope.Add("email");
})
.AddTwitter(options =>
{
authSection.GetSection("Twitter").Bind(options);
options.RetrieveUserDetails = true;
})
.AddGoogle(options =>
{
authSection.GetSection("Google").Bind(options);
options.Scope.Add("email");
})
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.RequireHttpsMetadata = true;
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidIssuer = "CollAction",
ValidateAudience = true,
ValidAudience = "CollAction",
RequireAudience = true,
RequireExpirationTime = true,
RequireSignedTokens = true,
};

var jwtSection = authSection.GetSection("Jwt");
X509Certificate2 certificate = new(Convert.FromBase64String(jwtSection["Certificate"]), jwtSection["CertificatePassword"]);
options.TokenValidationParameters.IssuerSigningKey = new X509SecurityKey(certificate);
});
}

public void Configure(IApplicationBuilder app, IHostApplicationLifetime applicationLifetime)
Expand All @@ -205,6 +224,9 @@ public void Configure(IApplicationBuilder app, IHostApplicationLifetime applicat
app.UseForwardedHeaders(forwardedHeaderOptions);
}

app.UseAuthentication();
app.UseAuthorization();

if (environment.IsProduction())
{
app.UseHsts();
Expand All @@ -217,8 +239,6 @@ public void Configure(IApplicationBuilder app, IHostApplicationLifetime applicat
app.UseGraphiQl("/graphiql", "/graphql");
}

app.UseAuthentication();
app.UseAuthorization();
app.UseHangfireServer(new BackgroundJobServerOptions() { WorkerCount = 1 });
app.UseEndpoints(endpoints =>
{
Expand Down
2 changes: 2 additions & 0 deletions env_collaction.template
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ MailChimpTestListId=1a035c45ca
MailChimpNewsletterListId=1a035c45ca
MailChimpKey=
[email protected]
Authentication:Jwt:Certificate=
Authentication:Jwt:CertificatePassword=
Authentication:Twitter:ConsumerSecret=
Authentication:Twitter:ConsumerKey=
Authentication:Google:ClientSecret=
Expand Down