Skip to content

Commit

Permalink
Refactored out a method in the IServiceCollection extension class tha…
Browse files Browse the repository at this point in the history
…t allows adding JWT Bearer auth without cookies for WebAPIs. Also, added a sample API implementation showing Policy based claims/roles check
  • Loading branch information
explorer14 committed May 4, 2018
1 parent a646811 commit 50f69f4
Show file tree
Hide file tree
Showing 16 changed files with 347 additions and 8 deletions.
8 changes: 7 additions & 1 deletion JwtAuthenticationHelper.sln
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ VisualStudioVersion = 15.0.27110.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JwtAuthenticationHelper", "JwtAuthenticationHelper\JwtAuthenticationHelper.csproj", "{031FC838-6C97-4463-8564-751A132366C3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JwtAuthenticationHelpers.Tests", "JwtAuthenticationHelpers.Tests\JwtAuthenticationHelpers.Tests.csproj", "{6446CCCD-4EE2-4049-AD4B-7D82A3A00D01}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JwtAuthenticationHelpers.Tests", "JwtAuthenticationHelpers.Tests\JwtAuthenticationHelpers.Tests.csproj", "{6446CCCD-4EE2-4049-AD4B-7D82A3A00D01}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JwtTokenAuthRefImplementation.Web", "JwtTokenAuthRefImplementation.Web\JwtTokenAuthRefImplementation.Web.csproj", "{7FA98FBA-EB33-44E4-B779-982DE00A6C30}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JwtTokenAuthRefImplementation.API", "JwtTokenAuthRefImplementation.API\JwtTokenAuthRefImplementation.API.csproj", "{1CD67DFB-9CF2-4BBF-87B5-FFCFBE16E7B1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -27,6 +29,10 @@ Global
{7FA98FBA-EB33-44E4-B779-982DE00A6C30}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7FA98FBA-EB33-44E4-B779-982DE00A6C30}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7FA98FBA-EB33-44E4-B779-982DE00A6C30}.Release|Any CPU.Build.0 = Release|Any CPU
{1CD67DFB-9CF2-4BBF-87B5-FFCFBE16E7B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1CD67DFB-9CF2-4BBF-87B5-FFCFBE16E7B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1CD67DFB-9CF2-4BBF-87B5-FFCFBE16E7B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1CD67DFB-9CF2-4BBF-87B5-FFCFBE16E7B1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
29 changes: 29 additions & 0 deletions JwtAuthenticationHelper/Extensions/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using JwtAuthenticationHelper.Abstractions;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
Expand Down Expand Up @@ -100,6 +101,34 @@ public static IServiceCollection AddJwtAuthenticationWithProtectedCookie(this IS

return services;
}

public static IServiceCollection AddJwtAuthenticationForAPI(this IServiceCollection services,
TokenValidationParameters tokenValidationParams)
{
if (tokenValidationParams == null)
{
throw new ArgumentNullException(
$"{nameof(tokenValidationParams)} is a required parameter. " +
$"Please make sure you've provided a valid instance with the appropriate values configured.");
}

services.AddScoped<IJwtTokenGenerator, JwtTokenGenerator>(serviceProvider =>
new JwtTokenGenerator(tokenValidationParams.ToTokenOptions(1)));

services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultSignInScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).
AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.SaveToken = true;
options.TokenValidationParameters = tokenValidationParams;
});

return services;
}
}

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion JwtAuthenticationHelper/JwtAuthenticationHelper.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="2.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="2.0.3" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.0.0" />
</ItemGroup>

Expand Down
79 changes: 79 additions & 0 deletions JwtTokenAuthRefImplementation.API/Controllers/AuthController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using JwtAuthenticationHelper.Abstractions;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

namespace JwtTokenAuthRefImplementation.API.Controllers
{
[Produces("application/json")]
[Route("api/auth")]
[ApiController]
public class AuthController : Controller
{
private readonly IJwtTokenGenerator jwtTokenGenerator;

public AuthController(IJwtTokenGenerator jwtTokenGenerator)
{
this.jwtTokenGenerator = jwtTokenGenerator;
}

[HttpPost("login")]
[AllowAnonymous]
public async Task<IActionResult> Login([FromBody]UserCredentials userCredentials)
{
// Replace this with your custom authentication logic which will
// securely return the authenticated user's details including
// any role specific info
if (userCredentials.Username == "user1" && userCredentials.Password == "pass1")
{
var userInfo = new UserInfo
{
FirstName = "UserFName",
LastName = "UserLName",
HasAdminRights = true
};

var accessTokenResult = jwtTokenGenerator.GenerateAccessTokenWithClaimsPrincipal(
userCredentials.Username,
AddMyClaims(userInfo));

return Ok(accessTokenResult.AccessToken);
}
else
{
return Unauthorized();
}
}

private static IEnumerable<Claim> AddMyClaims(UserInfo authenticatedUser)
{
var myClaims = new List<Claim>
{
new Claim(ClaimTypes.GivenName, authenticatedUser.FirstName),
new Claim(ClaimTypes.Surname, authenticatedUser.LastName),
new Claim("HasAdminRights", authenticatedUser.HasAdminRights ? "Y" : "N")
};

return myClaims;
}
}

internal class UserInfo
{
public string FirstName { get; set; }
public string LastName { get; set; }
public bool HasAdminRights { get; set; }
}

public class UserCredentials
{
public string Username { get; set; }
public string Password { get; set; }
}
}
47 changes: 47 additions & 0 deletions JwtTokenAuthRefImplementation.API/Controllers/ValuesController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace JwtTokenAuthRefImplementation.API.Controllers
{
[Route("api/[controller]")]
[ApiController]
[Authorize(Policy = "RequiresAdmin")]
public class ValuesController : ControllerBase
{
// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}

// GET api/values/5
[HttpGet("{id}")]
public string Get(int id)
{
return "value";
}

// POST api/values
[HttpPost]
public void Post([FromBody] string value)
{
}

// PUT api/values/5
[HttpPut("{id}")]
public void Put(int id, [FromBody] string value)
{
}

// DELETE api/values/5
[HttpDelete("{id}")]
public void Delete(int id)
{
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>

<ItemGroup>
<Folder Include="wwwroot\" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.0-preview1-final" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.1.0-preview1-final" />
</ItemGroup>

<ItemGroup>
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.1.0-preview1-final" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\JwtAuthenticationHelper\JwtAuthenticationHelper.csproj" />
</ItemGroup>

</Project>
24 changes: 24 additions & 0 deletions JwtTokenAuthRefImplementation.API/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

namespace JwtTokenAuthRefImplementation.API
{
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
}
28 changes: 28 additions & 0 deletions JwtTokenAuthRefImplementation.API/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:49686",
"sslPort": 44391
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_HTTPS_PORT": "44391"
}
},
"JwtTokenAuthRefImplementation.API": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_URLS": "https://localhost:5001;http://localhost:5000"
}
}
}
}
70 changes: 70 additions & 0 deletions JwtTokenAuthRefImplementation.API/Startup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using JwtAuthenticationHelper.Extensions;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Text;

namespace JwtTokenAuthRefImplementation.API
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}

public IConfiguration Configuration { get; }

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// retrieve the configured token params and establish a TokenValidationParameters object,
// we are going to need this later.
var validationParams = new TokenValidationParameters
{
ClockSkew = TimeSpan.Zero,

ValidateAudience = true,
ValidAudience = Configuration["Token:Audience"],

ValidateIssuer = true,
ValidIssuer = Configuration["Token:Issuer"],

IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Configuration["Token:SigningKey"])),
ValidateIssuerSigningKey = true,

RequireExpirationTime = true,
ValidateLifetime = true
};

services.AddJwtAuthenticationForAPI(validationParams);
services.AddAuthorization(options =>
{
options.AddPolicy("RequiresAdmin", policy => policy.RequireClaim("HasAdminRights"));
});

services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseAuthentication();
app.UseMvc();
}
}
}
14 changes: 14 additions & 0 deletions JwtTokenAuthRefImplementation.API/appsettings.Development.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
},
"Token": {
"Issuer": "Token.WebAPI",
"Audience": "Token.WebAPI.Clients",
"SigningKey": "d739d787-c3b3-47e6-aaba-2814c17551ab"
}
}
7 changes: 7 additions & 0 deletions JwtTokenAuthRefImplementation.API/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
}
}
Loading

0 comments on commit 50f69f4

Please sign in to comment.