Skip to content

Commit

Permalink
Completed basic JWT authentication middleware and Authorize filter.
Browse files Browse the repository at this point in the history
dicky authored and dicky committed Jul 29, 2017
1 parent bc72916 commit 5a5c41c
Showing 17 changed files with 290 additions and 154 deletions.
12 changes: 8 additions & 4 deletions DotNetifyLib.SignalR/DotNetifyHub.cs
Original file line number Diff line number Diff line change
@@ -19,6 +19,7 @@ limitations under the License.
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Security.Principal;
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Caching.Memory;
@@ -43,6 +44,8 @@ public class DotNetifyHub : Hub
private const string JTOKEN_VMARG = "$vmArg";
private const string JTOKEN_HEADERS = "$headers";

private IPrincipal _principal;

/// <summary>
/// View model controller associated with the current connection.
/// </summary>
@@ -51,7 +54,7 @@ private VMController VMController
get
{
if (_principalAccessor is HubPrincipalAccessor)
(_principalAccessor as HubPrincipalAccessor).Principal = Context.User;
(_principalAccessor as HubPrincipalAccessor).Principal = _principal ?? Context.User;

var vmController = _vmControllerFactory.GetInstance(Context.ConnectionId);
vmController.RequestingVM = RunRequestingVMFilters;
@@ -263,8 +266,9 @@ private T RunMiddlewares<T>(string callType, string vmId, T data, bool exception
{
try
{
var hubContext = new DotNetifyHubContext(Context, callType, vmId, data, Headers);
var hubContext = new DotNetifyHubContext(Context, callType, vmId, data, Headers, _principal);
_middlewareFactories?.ToList().ForEach(factory => factory().Invoke(hubContext));
_principal = hubContext.Principal;
return hubContext.Data as T;
}
catch (Exception ex)
@@ -302,7 +306,7 @@ private void RunVMFilters<T>(string callType, string vmId, BaseVM vm, ref T data
{
try
{
var vmContext = new VMContext(new DotNetifyHubContext(Context, callType, vmId, data, Headers), vm);
var vmContext = new VMContext(new DotNetifyHubContext(Context, callType, vmId, data, Headers, _principal), vm);
foreach (var attr in vm.GetType().GetTypeInfo().GetCustomAttributes())
{
var vmFilterType = typeof(IVMFilter<>).MakeGenericType(attr.GetType());
@@ -316,7 +320,7 @@ private void RunVMFilters<T>(string callType, string vmId, BaseVM vm, ref T data
}
catch (Exception ex)
{
Response_VM(Context.ConnectionId, vmId, SerializeException(ex));
Response_VM(Context.ConnectionId, vmId, SerializeException(ex.InnerException));
throw new OperationCanceledException($"Filter threw {ex.GetType().Name}: {ex.Message}", ex);
}
}
5 changes: 4 additions & 1 deletion DotNetifyLib.SignalR/DotNetifyHubContext.cs
Original file line number Diff line number Diff line change
@@ -15,6 +15,7 @@ limitations under the License.
*/

using Microsoft.AspNetCore.SignalR.Hubs;
using System.Security.Principal;

namespace DotNetify
{
@@ -28,14 +29,16 @@ public class DotNetifyHubContext
public string VMId { get; }
public object Data { get; }
public object Headers { get; }
public IPrincipal Principal { get; set; }

internal DotNetifyHubContext(HubCallerContext callerContext, string callType, string vmId, object data, object headers)
internal DotNetifyHubContext(HubCallerContext callerContext, string callType, string vmId, object data, object headers, IPrincipal principal)
{
CallerContext = callerContext;
CallType = callType;
VMId = vmId;
Data = data;
Headers = headers;
Principal = principal;
}
}
}
1 change: 1 addition & 0 deletions DotNetifyLib.SignalR/DotNetifyLib.SignalR.csproj
Original file line number Diff line number Diff line change
@@ -21,6 +21,7 @@
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="1.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.1" />
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="1.0.1" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="5.1.4" />
</ItemGroup>

</Project>
12 changes: 6 additions & 6 deletions DotNetifyLib.SignalR/Extensions/AppBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -25,8 +25,8 @@ namespace DotNetify
{
public static class AppBuilderExtensions
{
private static List<Type> _middlewareTypes = new List<Type>();
private static List<Type> _filterTypes = new List<Type>();
private readonly static List<Tuple<Type, object[]>> _middlewareTypes = new List<Tuple<Type, object[]>>();
private readonly static List<Tuple<Type, object[]>> _filterTypes = new List<Tuple<Type, object[]>>();

public static IApplicationBuilder UseDotNetify(this IApplicationBuilder appBuilder, Action<IDotNetifyConfiguration> config = null)
{
@@ -69,16 +69,16 @@ public static IApplicationBuilder UseDotNetify(this IApplicationBuilder appBuild

// Add middleware factories to the hub.
var middlewareFactories = provider.GetService<IList<Func<IMiddleware>>>();
_middlewareTypes.ForEach(t => middlewareFactories?.Add(() => (IMiddleware)factoryMethod(t, null)));
_middlewareTypes.ForEach(t => middlewareFactories?.Add(() => (IMiddleware)factoryMethod(t.Item1, t.Item2)));

// Add filter factories to the hub.
var filterFactories = provider.GetService<IDictionary<Type, Func<IVMFilter>>>();
_filterTypes.ForEach(t => filterFactories?.Add(t, () => (IVMFilter)factoryMethod(t, null)));
_filterTypes.ForEach(t => filterFactories?.Add(t.Item1, () => (IVMFilter)factoryMethod(t.Item1, t.Item2)));

return appBuilder;
}

public static void UseMiddleware<T>(this IDotNetifyConfiguration dotNetifyConfig) where T : IMiddleware => _middlewareTypes.Add(typeof(T));
public static void UseFilters<T>(this IDotNetifyConfiguration dotNetifyConfig) where T : IVMFilter => _filterTypes.Add(typeof(T));
public static void UseMiddleware<T>(this IDotNetifyConfiguration dotNetifyConfig, params object[] args) where T : IMiddleware => _middlewareTypes.Add(Tuple.Create(typeof(T), args));
public static void UseFilters<T>(this IDotNetifyConfiguration dotNetifyConfig, params object[] args) where T : IVMFilter => _filterTypes.Add(Tuple.Create(typeof(T), args));
}
}
47 changes: 47 additions & 0 deletions DotNetifyLib.SignalR/Security/AuthorizeFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
Copyright 2017 Dicky Suryadi
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

using DotNetify;
using DotNetify.Security;
using System;
using System.Security.Claims;

namespace DotNetify.Security
{
/// <summary>
/// View model filter to perform authorization check.
/// </summary>
public class AuthorizeFilter : IVMFilter<AuthorizeAttribute>
{
public void Invoke(AuthorizeAttribute auth, VMContext context)
{
var principal = context.HubContext.Principal;

bool authd = principal?.Identity?.IsAuthenticated == true;
if (authd)
{
if (!string.IsNullOrEmpty(auth.Role))
authd &= principal.IsInRole(auth.Role);

if (!string.IsNullOrEmpty(auth.ClaimType))
authd &= principal is ClaimsPrincipal ? (principal as ClaimsPrincipal).HasClaim(auth.ClaimType, auth.ClaimValue) : false;
}

if (!authd)
throw new UnauthorizedAccessException();
}
}
}
72 changes: 72 additions & 0 deletions DotNetifyLib.SignalR/Security/JwtBearerAuthenticationMiddleware.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
Copyright 2017 Dicky Suryadi
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

using System;
using System.Diagnostics;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using Newtonsoft.Json.Linq;

namespace DotNetify.Security
{
/// <summary>
/// Middleware to handle JWT bearer token authentication.
/// </summary>
public class JwtBearerAuthenticationMiddleware : IMiddleware
{
private class HeaderData
{
public string Authorization { get; set; }
}

private readonly TokenValidationParameters _tokenValidationParameters;

public JwtBearerAuthenticationMiddleware(TokenValidationParameters tokenValidationParameters)
{
_tokenValidationParameters = tokenValidationParameters;
}

public void Invoke(DotNetifyHubContext hubContext)
{
try
{
var headers = ParseHeaders<HeaderData>(hubContext);
if (headers?.Authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase) == true)
{
var token = headers.Authorization.Substring("Bearer ".Length).Trim();
hubContext.Principal = new JwtSecurityTokenHandler().ValidateToken(token, _tokenValidationParameters, out SecurityToken validatedToken);
}
}
catch (Exception ex)
{
Trace.WriteLine(ex.Message);
}
}

private T ParseHeaders<T>(DotNetifyHubContext context) => context.Headers is JObject ? (context.Headers as JObject).ToObject<T>() : default(T);
}

/// <summary>
/// Method extension to specify parameter type for the middleware.
/// </summary>
public static class JwtBearerAuthenticationMiddlewareExtensions
{
public static void UseJwtBearerAuthentication(this IDotNetifyConfiguration config, TokenValidationParameters tokenValidationParameters)
{
config.UseMiddleware<JwtBearerAuthenticationMiddleware>(tokenValidationParameters);
}
}
}
2 changes: 1 addition & 1 deletion WebApplication.Core.React/AuthServer.cs
Original file line number Diff line number Diff line change
@@ -84,7 +84,7 @@ private async Task GenerateToken(HttpContext context)
return;
}

var now = DateTimeOffset.UtcNow;
var now = DateTimeOffset.Now;

// Specifically add the jti (random nonce), iat (issued timestamp), and sub (subject/user) claims.
// You can add other claims here, if you want:
15 changes: 15 additions & 0 deletions WebApplication.Core.React/MiddlewareExample.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using DotNetify;
using Newtonsoft.Json;
using System.Diagnostics;

namespace WebApplication.Core.React
{
public class LogRequestMiddleware : IMiddleware
{
public void Invoke(DotNetifyHubContext hubContext)
{
Trace.WriteLine($"{hubContext.CallType} {hubContext.VMId}, {JsonConvert.SerializeObject(hubContext.Data)}");
}
}

}
70 changes: 0 additions & 70 deletions WebApplication.Core.React/MiddlewareExamples.cs

This file was deleted.

20 changes: 18 additions & 2 deletions WebApplication.Core.React/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
using Microsoft.AspNetCore.Builder;
using System;
using System.Text;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using DotNetify;
using DotNetify.Security;

namespace WebApplication.Core.React
{
@@ -32,8 +36,20 @@ public void Configure( IApplicationBuilder app, IHostingEnvironment env, ILogger
app.UseSignalR(); // Required by dotNetify.
app.UseDotNetify(config =>
{
string secretKey = "dotnetifydemo_secretkey_123!";
var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(secretKey));

config.UseMiddleware<LogRequestMiddleware>();
config.UseMiddleware<JwtBearerAuthenticationMiddleware>();
config.UseJwtBearerAuthentication(new TokenValidationParameters
{
IssuerSigningKey = signingKey,
ValidAudience = "DotNetifyDemoApp",
ValidIssuer = "DotNetifyDemoServer",
ValidateIssuerSigningKey = true,
ValidateAudience = true,
ValidateIssuer = true,
ValidateLifetime = true
});

config.UseFilters<AuthorizeFilter>();
});
Loading
Oops, something went wrong.

0 comments on commit 5a5c41c

Please sign in to comment.