diff --git a/SignalR.sln b/SignalR.sln
index 2456b41517..c9f1d748fd 100644
--- a/SignalR.sln
+++ b/SignalR.sln
@@ -91,6 +91,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Signal
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JwtSample", "samples\JwtSample\JwtSample.csproj", "{6A7491D3-3C97-49BD-A71C-433AED657F30}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JwtClientSample", "samples\JwtClientSample\JwtClientSample.csproj", "{1A953296-E869-4DE2-A693-FD5FCDE27057}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -201,6 +203,10 @@ Global
{6A7491D3-3C97-49BD-A71C-433AED657F30}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6A7491D3-3C97-49BD-A71C-433AED657F30}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6A7491D3-3C97-49BD-A71C-433AED657F30}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1A953296-E869-4DE2-A693-FD5FCDE27057}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1A953296-E869-4DE2-A693-FD5FCDE27057}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1A953296-E869-4DE2-A693-FD5FCDE27057}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1A953296-E869-4DE2-A693-FD5FCDE27057}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -233,6 +239,7 @@ Global
{BE982591-F4BB-42D9-ABD4-A5D44C65971E} = {DA69F624-5398-4884-87E4-B816698CDE65}
{0B083AE6-86CA-4E0B-AE02-59154D1FD005} = {6A35B453-52EC-48AF-89CA-D4A69800F131}
{6A7491D3-3C97-49BD-A71C-433AED657F30} = {C4BC9889-B49F-41B6-806B-F84941B2549B}
+ {1A953296-E869-4DE2-A693-FD5FCDE27057} = {C4BC9889-B49F-41B6-806B-F84941B2549B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {7945A4E4-ACDB-4F6E-95CA-6AC6E7C2CD59}
diff --git a/samples/JwtClientSample/JwtClientSample.csproj b/samples/JwtClientSample/JwtClientSample.csproj
new file mode 100644
index 0000000000..c6aea92edb
--- /dev/null
+++ b/samples/JwtClientSample/JwtClientSample.csproj
@@ -0,0 +1,14 @@
+
+
+
+ netcoreapp2.0
+
+ false
+ Exe
+
+
+
+
+
+
+
diff --git a/samples/JwtClientSample/Program.cs b/samples/JwtClientSample/Program.cs
new file mode 100644
index 0000000000..8358f9c3a3
--- /dev/null
+++ b/samples/JwtClientSample/Program.cs
@@ -0,0 +1,80 @@
+using System;
+using System.Collections.Concurrent;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.SignalR.Client;
+using Microsoft.AspNetCore.Sockets;
+
+namespace JwtClientSample
+{
+ class Program
+ {
+ static async Task Main(string[] args)
+ {
+ var app = new Program();
+ await Task.WhenAll(
+ app.RunConnection(TransportType.WebSockets),
+ app.RunConnection(TransportType.ServerSentEvents),
+ app.RunConnection(TransportType.LongPolling));
+ }
+
+ private const string ServerUrl = "http://localhost:54543";
+
+ private readonly ConcurrentDictionary _tokens = new ConcurrentDictionary();
+ private readonly Random _random = new Random();
+
+ private async Task RunConnection(TransportType transportType)
+ {
+ var userId = "C#" + transportType.ToString();
+ _tokens[userId] = await GetJwtToken(userId);
+
+ var hubConnection = new HubConnectionBuilder()
+ .WithUrl(ServerUrl + "/broadcast")
+ .WithTransport(transportType)
+ .WithJwtBearer(() => _tokens[userId])
+ .Build();
+
+ hubConnection.On("Message", (sender, message) => Console.WriteLine($"[{userId}] {sender}: {message}"));
+ await hubConnection.StartAsync();
+ Console.WriteLine($"[{userId}] Connection Started");
+
+ var ticks = 0;
+ var nextMsgAt = 3;
+
+ try
+ {
+ while (!hubConnection.Closed.IsCompleted)
+ {
+ await Task.Delay(1000);
+ ticks++;
+ if (ticks % 15 == 0)
+ {
+ // no need to refresh the token for websockets
+ if (transportType != TransportType.WebSockets)
+ {
+ _tokens[userId] = await GetJwtToken(userId);
+ Console.WriteLine($"[{userId}] Token refreshed");
+ }
+ }
+
+ if (ticks % nextMsgAt == 0)
+ {
+ await hubConnection.SendAsync("Broadcast", userId, $"Hello at {DateTime.Now.ToString()}");
+ nextMsgAt = _random.Next(2, 5);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"[{userId}] Connection terminated with error: {ex}");
+ }
+ }
+
+ private async Task GetJwtToken(string userId)
+ {
+ var httpResponse = await new HttpClient().GetAsync(ServerUrl + $"/generatetoken?user={userId}");
+ httpResponse.EnsureSuccessStatusCode();
+ return await httpResponse.Content.ReadAsStringAsync();
+ }
+ }
+}
diff --git a/samples/JwtSample/Startup.cs b/samples/JwtSample/Startup.cs
index 46f9598cfb..63887aa920 100644
--- a/samples/JwtSample/Startup.cs
+++ b/samples/JwtSample/Startup.cs
@@ -38,6 +38,7 @@ namespace JwtSample
options.TokenValidationParameters =
new TokenValidationParameters
{
+ LifetimeValidator = (before, expires, token, parameters) => expires > DateTime.UtcNow,
ValidateAudience = false,
ValidateIssuer = false,
ValidateActor = false,
@@ -76,7 +77,7 @@ namespace JwtSample
{
var claims = new[] { new Claim(ClaimTypes.NameIdentifier, httpContext.Request.Query["user"]) };
var credentials = new SigningCredentials(SecurityKey, SecurityAlgorithms.HmacSha256);
- var token = new JwtSecurityToken("SignalRTestServer", "SignalRTests", claims, expires: DateTime.Now.AddSeconds(30), signingCredentials: credentials);
+ var token = new JwtSecurityToken("SignalRTestServer", "SignalRTests", claims, expires: DateTime.UtcNow.AddSeconds(30), signingCredentials: credentials);
return JwtTokenHandler.WriteToken(token);
}
}
diff --git a/test/Microsoft.AspNetCore.SignalR.Client.FunctionalTests/Startup.cs b/test/Microsoft.AspNetCore.SignalR.Client.FunctionalTests/Startup.cs
index e0d9d09e5e..fe5aa3cdc8 100644
--- a/test/Microsoft.AspNetCore.SignalR.Client.FunctionalTests/Startup.cs
+++ b/test/Microsoft.AspNetCore.SignalR.Client.FunctionalTests/Startup.cs
@@ -63,7 +63,6 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests
return;
}
});
-
}
private string GenerateJwtToken()