• R/O
  • SSH
  • HTTPS

grpcdotnet: Commit


Commit MetaInfo

Revision8 (tree)
Time2020-09-26 07:50:06
Authorsebastiandotnet

Log Message

add simple jwt poc

Change Summary

Incremental Difference

--- AutoUpdate/ReadMe.md (revision 7)
+++ AutoUpdate/ReadMe.md (revision 8)
@@ -42,7 +42,7 @@
4242
4343 Real World Issues)
4444
45-1.) json.settings files should store in App DataFolder instead of execution folder
45+1.) json.settings files should stored in AppData folder instead of execution folder
4646 because write access in execution folder is not guaranteed.
4747
4848 2.) Client does not verify the remote endpoint as safe location.
@@ -58,11 +58,21 @@
5858 This requires also a transactional rollback script/action if something goes wrong.
5959
6060 5.) Bootloader should check write access for target folder and its files first(if exists, or just test create folder works)
61-Otherwise its make no sense to go further because we know we run into an error after download when replacing target.
62-This is still no warranty everything is okay because a third party can lock the folder or one of its files in the meantime while we download, but its better than nothing. (There is a way in Win32 API to lock the folder but its too dangerous in my opinion.)
61+Otherwise its make no sense to go further because we know we run into an error after download when we try to replace the old files.
62+This is still no warranty everything is okay because a third party can lock the folder or one of its files in the meantime while we download,
63+but its better than nothing.
64+(There is a way in Win32 API to lock the folder but its dangerous in my opinion.)
6365
64-6.) Bootloader currently ignores any RPCException's by meaning its a connection issue. But its possible there is client/server incompatibility or SSL issue here which need a presentation to the user.
66+6.) Bootloader currently ignores any RPCException's by meaning its a connection issue,
67+but its possible there is a client/server incompatibility or SSL issue here which need a presentation to the user.
6568
69+7.) Deal with privilegs i.e. if Bootloader is started with evelevated permissions,
70+target may shouldnt start evelevated too and vice versa.
6671
72+8.) Filesystemstorage should ignore folders there not match a version signature.
73+Moreover Filesystemstorage dont handle the case of "1.0" and "1.0.0.0" which is
74+an invalid duplicate.
75+
76+
6777 Thats all folks
6878 If u see more real word issues or found and error, please tell me.
--- ServerInterceptor/ReadMe.md (revision 7)
+++ ServerInterceptor/ReadMe.md (revision 8)
@@ -1,12 +1,10 @@
11 Motivation)
22
33 This is a proof of concept to proof a service-side interceptor is able to manage call level security.
4-To simplify this poc, i dont use JWT as session token here,
5-but its possible to combine JWT with a server-side security interceptor
6-which is very powerful i guess.
4+To simplify this poc, i dont use JWT as session token here. This poc dont mean its a good idea
5+to use an interceptor for security.
76
87
9-
108 Ho it works)
119
1210 Services can annotated with ServiceAuthorizationAttribute to add roles which having access.
@@ -19,6 +17,7 @@
1917
2018 The client proxy use a custom call invoker to bypass the issue u cant add metadata permanently
2119 to a grpc client but we want to have it in order to add our custom session token from Logon.
20+(You can add credentials permanently to a channel of course.)
2221
2322 The Common project in the middle is not directly referenced. Its just a shared place
2423 for ouer proto files. The client console app is a slightly cheap integration test.
--- SimpleJwt/SL.JwtTokens.ConsoleClient/Program.cs (nonexistent)
+++ SimpleJwt/SL.JwtTokens.ConsoleClient/Program.cs (revision 8)
@@ -0,0 +1,17 @@
1+using System;
2+using System.Threading.Tasks;
3+
4+namespace SL.JwtTokens.ConsoleClient
5+{
6+ static class Program
7+ {
8+ static async Task Main(string[] args)
9+ {
10+ Console.WriteLine("SL.JwtTokens.ConsoleClient");
11+ await TokenTimeout.Start();
12+ await UseChannelCredentials.Start();
13+ Console.WriteLine("Press any key.");
14+ Console.ReadKey();
15+ }
16+ }
17+}
--- SimpleJwt/SL.JwtTokens.ConsoleClient/TokenTimeout.cs (nonexistent)
+++ SimpleJwt/SL.JwtTokens.ConsoleClient/TokenTimeout.cs (revision 8)
@@ -0,0 +1,81 @@
1+using Grpc.Core;
2+using Grpc.Net.Client;
3+using SL.JwtTokens.Service;
4+using System;
5+using System.IdentityModel.Tokens.Jwt;
6+using System.Text;
7+using System.Threading.Tasks;
8+using static SL.JwtTokens.Service.Authentication;
9+
10+namespace SL.JwtTokens.ConsoleClient
11+{
12+ public static class TokenTimeout
13+ {
14+ public static async Task Start()
15+ {
16+ Console.WriteLine($"{nameof(TokenTimeout)} {nameof(Start)}");
17+ using (var channel = GrpcChannel.ForAddress("https://localhost:5001"))
18+ {
19+ var logon = await channel.Logon();
20+ await channel.SayHelloWithToken(logon.Token, "Call 1");
21+ await channel.SayHelloWithoutToken("Call 2");
22+ await WaitForTokenTimeout();
23+ await channel.SayHelloWithToken(logon.Token, "Call 3");
24+ }
25+ }
26+
27+ public static async Task SayHelloWithToken(this GrpcChannel channel, string token, string name)
28+ {
29+ try
30+ {
31+ Console.WriteLine($"SayHelloWithToken {name}");
32+ var headers = new Metadata();
33+ headers.Add("Authorization", $"Bearer {token}");
34+ var greetClient = new Greeter.GreeterClient(channel);
35+ await greetClient.SayHelloAsync(new HelloRequest { Name = name }, headers: headers);
36+ }
37+ catch (Exception exception)
38+ {
39+ Console.WriteLine($"SayHelloWithToken failed. {exception.Message}");
40+ }
41+ }
42+
43+ public static async Task SayHelloWithoutToken(this GrpcChannel channel, string name)
44+ {
45+ try
46+ {
47+ Console.WriteLine($"SayHelloWithoutToken {name}");
48+ var greetClient = new Greeter.GreeterClient(channel);
49+ var result = await greetClient.SayHelloAsync(new HelloRequest { Name = name });
50+ }
51+ catch (Exception exception)
52+ {
53+ Console.WriteLine($"SayHelloWithToken failed. {exception.Message}");
54+ }
55+ }
56+
57+ public static async Task<LogonReply> Logon(this GrpcChannel channel)
58+ {
59+ try
60+ {
61+ var logonClient = new AuthenticationClient(channel);
62+ var result = await logonClient.LogonAsync(new LogonRequest { Name = "User1" });
63+ var handler = new JwtSecurityTokenHandler();
64+ var token = handler.ReadJwtToken(result.Token);
65+ Console.WriteLine($"Token {token}");
66+ return result;
67+ }
68+ catch (Exception exception)
69+ {
70+ Console.WriteLine($"Logon failed. {exception.Message}");
71+ throw;
72+ }
73+ }
74+
75+ public static async Task WaitForTokenTimeout()
76+ {
77+ Console.WriteLine("Wait a minute.");
78+ await Task.Delay(61000);
79+ }
80+ }
81+}
--- SimpleJwt/SL.JwtTokens.ConsoleClient/UseChannelCredentials.cs (nonexistent)
+++ SimpleJwt/SL.JwtTokens.ConsoleClient/UseChannelCredentials.cs (revision 8)
@@ -0,0 +1,71 @@
1+using Grpc.Core;
2+using Grpc.Net.Client;
3+using SL.JwtTokens.Service;
4+using System;
5+using System.IdentityModel.Tokens.Jwt;
6+using System.Threading.Tasks;
7+using static SL.JwtTokens.Service.Authentication;
8+
9+namespace SL.JwtTokens.ConsoleClient
10+{
11+ public static class UseChannelCredentials
12+ {
13+ public static async Task Start()
14+ {
15+ var logon = await Logon();
16+ using (var channel = CreateAuthenticatedChannel("https://localhost:5001", logon.Token))
17+ {
18+ await channel.SayHello("Channel Call 1");
19+ }
20+ }
21+
22+ public static async Task SayHello(this GrpcChannel channel, string name)
23+ {
24+ try
25+ {
26+ Console.WriteLine($"SayHelloWithToken {name}");
27+ var greetClient = new Greeter.GreeterClient(channel);
28+ await greetClient.SayHelloAsync(new HelloRequest { Name = name });
29+ }
30+ catch (Exception exception)
31+ {
32+ Console.WriteLine($"SayHelloWithToken failed. {exception.Message}");
33+ }
34+ }
35+
36+ public static async Task<LogonReply> Logon()
37+ {
38+ try
39+ {
40+ using (var channel = GrpcChannel.ForAddress("https://localhost:5001"))
41+ {
42+ var logonClient = new AuthenticationClient(channel);
43+ var result = await logonClient.LogonAsync(new LogonRequest { Name = "User1" });
44+ var handler = new JwtSecurityTokenHandler();
45+ var token = handler.ReadJwtToken(result.Token);
46+ Console.WriteLine($"Token {token}");
47+ return result;
48+ }
49+ }
50+ catch (Exception exception)
51+ {
52+ Console.WriteLine($"Logon failed. {exception.Message}");
53+ throw;
54+ }
55+ }
56+ private static GrpcChannel CreateAuthenticatedChannel(string address, string token)
57+ {
58+ var credentials = CallCredentials.FromInterceptor((context, metadata) =>
59+ {
60+ metadata.Add("Authorization", $"Bearer {token}");
61+ return Task.CompletedTask;
62+ });
63+ var channel = GrpcChannel.ForAddress(address, new GrpcChannelOptions
64+ {
65+ Credentials = ChannelCredentials.Create(new SslCredentials(), credentials)
66+ });
67+ return channel;
68+ }
69+
70+ }
71+}
--- SimpleJwt/SL.JwtTokens.Service/Model/Role.cs (nonexistent)
+++ SimpleJwt/SL.JwtTokens.Service/Model/Role.cs (revision 8)
@@ -0,0 +1,14 @@
1+using System;
2+using System.Collections.Generic;
3+using System.Linq;
4+using System.Threading.Tasks;
5+
6+namespace SL.JwtTokens.Service.Model
7+{
8+ public enum Role
9+ {
10+ Customer = 0,
11+ Employee = 1,
12+ Administrator = 3
13+ }
14+}
--- SimpleJwt/SL.JwtTokens.Service/Model/User.cs (nonexistent)
+++ SimpleJwt/SL.JwtTokens.Service/Model/User.cs (revision 8)
@@ -0,0 +1,25 @@
1+using System;
2+using System.Collections.Generic;
3+using System.Linq;
4+using System.Threading.Tasks;
5+
6+namespace SL.JwtTokens.Service.Model
7+{
8+ public class User
9+ {
10+ public User(Guid id, string name, params Role[] roles)
11+ {
12+ Id = id;
13+ Name = name;
14+ Roles = roles;
15+ }
16+
17+ public Guid Id { get; private set; }
18+
19+ public string Name { get; private set; }
20+
21+ public string Session { get; internal set; }
22+
23+ public Role[] Roles { get; private set; }
24+ }
25+}
--- SimpleJwt/SL.JwtTokens.Service/Properties/launchSettings.json (nonexistent)
+++ SimpleJwt/SL.JwtTokens.Service/Properties/launchSettings.json (revision 8)
@@ -0,0 +1,12 @@
1+{
2+ "profiles": {
3+ "SL.JwtTokens.Service": {
4+ "commandName": "Project",
5+ "launchBrowser": false,
6+ "applicationUrl": "https://localhost:5001",
7+ "environmentVariables": {
8+ "ASPNETCORE_ENVIRONMENT": "Development"
9+ }
10+ }
11+ }
12+}
--- SimpleJwt/SL.JwtTokens.Service/Services/AuthenticationService.cs (nonexistent)
+++ SimpleJwt/SL.JwtTokens.Service/Services/AuthenticationService.cs (revision 8)
@@ -0,0 +1,56 @@
1+using System;
2+using System.Collections.Generic;
3+using System.IdentityModel.Tokens.Jwt;
4+using System.Linq;
5+using System.Security.Claims;
6+using System.Threading.Tasks;
7+using Grpc.Core;
8+using Microsoft.IdentityModel.Tokens;
9+
10+namespace SL.JwtTokens.Service.Services
11+{
12+ public class AuthenticationService : Authentication.AuthenticationBase
13+ {
14+ public AuthenticationService(FakeUserRepository users)
15+ {
16+ Users = users;
17+ }
18+
19+ public FakeUserRepository Users { get; private set; }
20+
21+ public override async Task<LogonReply> Logon(LogonRequest request, ServerCallContext context)
22+ {
23+ var claims = await Task.Run<List<Claim>>(() =>
24+ {
25+ var user = Users.First(e => e.Name == request.Name);
26+ var claims = new List<Claim>()
27+ {
28+ new Claim(ClaimTypes.NameIdentifier ,user.Id.ToString()),
29+ new Claim(ClaimTypes.Name, user.Name),
30+ };
31+ user.Roles.ToList().ForEach(e => claims.Add(new Claim(ClaimTypes.Role, e.ToString())));
32+ return claims;
33+ });
34+
35+ var tokenString = await Task.Run<string>(() =>
36+ {
37+ var tokenDescriptor = new SecurityTokenDescriptor()
38+ {
39+ Subject = new ClaimsIdentity(claims),
40+ NotBefore = DateTime.UtcNow,
41+ IssuedAt = DateTime.UtcNow,
42+ Issuer = request.Name,
43+ Expires = DateTime.UtcNow.AddMinutes(Constants.SessionTimeoutMinutes),
44+ SigningCredentials = new SigningCredentials(
45+ new SymmetricSecurityKey(Constants.SigningKey),
46+ SecurityAlgorithms.HmacSha256)
47+ };
48+ var tokenHandler = new JwtSecurityTokenHandler();
49+ var token = tokenHandler.CreateToken(tokenDescriptor);
50+ return tokenHandler.WriteToken(token);
51+ });
52+
53+ return await Task.FromResult(new LogonReply { Token = tokenString });
54+ }
55+ }
56+}
--- SimpleJwt/SL.JwtTokens.Service/Services/GreeterService.cs (nonexistent)
+++ SimpleJwt/SL.JwtTokens.Service/Services/GreeterService.cs (revision 8)
@@ -0,0 +1,28 @@
1+using System;
2+using System.Collections.Generic;
3+using System.Linq;
4+using System.Threading.Tasks;
5+using Grpc.Core;
6+using Microsoft.AspNetCore.Authorization;
7+using Microsoft.Extensions.Logging;
8+
9+namespace SL.JwtTokens.Service
10+{
11+ public class GreeterService : Greeter.GreeterBase
12+ {
13+ private readonly ILogger<GreeterService> _logger;
14+ public GreeterService(ILogger<GreeterService> logger)
15+ {
16+ _logger = logger;
17+ }
18+
19+ [Authorize(Roles = "Administrator, Employee")]
20+ public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
21+ {
22+ return Task.FromResult(new HelloReply
23+ {
24+ Message = "Hello " + request.Name
25+ });
26+ }
27+ }
28+}
--- SimpleJwt/SL.JwtTokens.Service/Constants.cs (nonexistent)
+++ SimpleJwt/SL.JwtTokens.Service/Constants.cs (revision 8)
@@ -0,0 +1,15 @@
1+using System;
2+using System.Collections.Generic;
3+using System.Linq;
4+using System.Text;
5+using System.Threading.Tasks;
6+
7+namespace SL.JwtTokens.Service
8+{
9+ public static class Constants
10+ {
11+ public static readonly int SessionTimeoutMinutes = 1;
12+
13+ public static readonly byte[] SigningKey = Encoding.UTF8.GetBytes("the password is swordfish"); // dont store here in real scenario
14+ }
15+}
--- SimpleJwt/SL.JwtTokens.Service/FakeUserRepository.cs (nonexistent)
+++ SimpleJwt/SL.JwtTokens.Service/FakeUserRepository.cs (revision 8)
@@ -0,0 +1,33 @@
1+using System;
2+using System.Collections;
3+using System.Collections.Generic;
4+using System.Linq;
5+using System.Threading.Tasks;
6+using SL.JwtTokens.Service.Model;
7+
8+namespace SL.JwtTokens.Service
9+{
10+ public class FakeUserRepository : IEnumerable<User>
11+ {
12+ private List<User> _users = new List<User>();
13+
14+ public FakeUserRepository()
15+ {
16+ _users.Add(new User(Guid.NewGuid(), "User1", Role.Customer));
17+ _users.Add(new User(Guid.NewGuid(), "User2", Role.Employee));
18+ _users.Add(new User(Guid.NewGuid(), "User3", Role.Administrator));
19+ _users.Add(new User(Guid.NewGuid(), "User4", Role.Customer, Role.Employee));
20+ _users.Add(new User(Guid.NewGuid(), "User5", Role.Customer, Role.Employee, Role.Administrator));
21+ }
22+
23+ public IEnumerator<User> GetEnumerator()
24+ {
25+ return _users.GetEnumerator();
26+ }
27+
28+ IEnumerator IEnumerable.GetEnumerator()
29+ {
30+ return _users.GetEnumerator();
31+ }
32+ }
33+}
--- SimpleJwt/SL.JwtTokens.Service/Program.cs (nonexistent)
+++ SimpleJwt/SL.JwtTokens.Service/Program.cs (revision 8)
@@ -0,0 +1,27 @@
1+using System;
2+using System.Collections.Generic;
3+using System.IO;
4+using System.Linq;
5+using System.Threading.Tasks;
6+using Microsoft.AspNetCore.Hosting;
7+using Microsoft.Extensions.Hosting;
8+
9+namespace SL.JwtTokens.Service
10+{
11+ public class Program
12+ {
13+ public static void Main(string[] args)
14+ {
15+ CreateHostBuilder(args).Build().Run();
16+ }
17+
18+ // Additional configuration is required to successfully run gRPC on macOS.
19+ // For instructions on how to configure Kestrel and gRPC clients on macOS, visit https://go.microsoft.com/fwlink/?linkid=2099682
20+ public static IHostBuilder CreateHostBuilder(string[] args) =>
21+ Host.CreateDefaultBuilder(args)
22+ .ConfigureWebHostDefaults(webBuilder =>
23+ {
24+ webBuilder.UseStartup<Startup>();
25+ });
26+ }
27+}
--- SimpleJwt/SL.JwtTokens.Service/Startup.cs (nonexistent)
+++ SimpleJwt/SL.JwtTokens.Service/Startup.cs (revision 8)
@@ -0,0 +1,76 @@
1+using System;
2+using System.Security.Claims;
3+using System.Threading.Tasks;
4+using Microsoft.AspNetCore.Authentication.JwtBearer;
5+using Microsoft.AspNetCore.Builder;
6+using Microsoft.AspNetCore.Hosting;
7+using Microsoft.AspNetCore.Http;
8+using Microsoft.Extensions.DependencyInjection;
9+using Microsoft.Extensions.Hosting;
10+using Microsoft.IdentityModel.Tokens;
11+using SL.JwtTokens.Service.Services;
12+
13+namespace SL.JwtTokens.Service
14+{
15+ public class Startup
16+ {
17+ public void ConfigureServices(IServiceCollection services)
18+ {
19+ services.AddSingleton<FakeUserRepository>();
20+ services.AddGrpc();
21+
22+ services.AddAuthentication(x =>
23+ {
24+ x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
25+ x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
26+ })
27+ .AddJwtBearer(x =>
28+ {
29+ x.RequireHttpsMetadata = false;
30+ x.SaveToken = false;
31+ x.TokenValidationParameters = new TokenValidationParameters
32+ {
33+ ValidateIssuerSigningKey = true,
34+ IssuerSigningKey = new SymmetricSecurityKey(Constants.SigningKey),
35+ ValidateIssuer = false,
36+ ValidateAudience = false,
37+ ValidateLifetime = true,
38+ ValidateActor = true,
39+ ClockSkew = TimeSpan.FromSeconds(3)
40+ };
41+ });
42+
43+ services.AddAuthorization(options =>
44+ {
45+ options.AddPolicy(JwtBearerDefaults.AuthenticationScheme, policy =>
46+ {
47+ policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme);
48+ policy.RequireClaim(ClaimTypes.NameIdentifier);
49+ policy.RequireClaim(ClaimTypes.Name);
50+ });
51+ });
52+ }
53+
54+ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
55+ {
56+ if (env.IsDevelopment())
57+ {
58+ app.UseDeveloperExceptionPage();
59+ }
60+
61+ app.UseRouting();
62+ app.UseAuthentication();
63+ app.UseAuthorization();
64+
65+ app.UseEndpoints(endpoints =>
66+ {
67+ endpoints.MapGrpcService<AuthenticationService>();
68+ endpoints.MapGrpcService<GreeterService>();
69+ endpoints.MapGet("/", async context =>
70+ {
71+ await context.Response.WriteAsync("Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
72+ });
73+ });
74+ }
75+ }
76+}
--- SimpleJwt/SL.JwtTokens.Service/appsettings.Development.json (nonexistent)
+++ SimpleJwt/SL.JwtTokens.Service/appsettings.Development.json (revision 8)
@@ -0,0 +1,10 @@
1+{
2+ "Logging": {
3+ "LogLevel": {
4+ "Default": "Debug",
5+ "System": "Information",
6+ "Grpc": "Information",
7+ "Microsoft": "Information"
8+ }
9+ }
10+}
--- SimpleJwt/SL.JwtTokens.Service/appsettings.json (nonexistent)
+++ SimpleJwt/SL.JwtTokens.Service/appsettings.json (revision 8)
@@ -0,0 +1,15 @@
1+{
2+ "Logging": {
3+ "LogLevel": {
4+ "Default": "Information",
5+ "Microsoft": "Warning",
6+ "Microsoft.Hosting.Lifetime": "Information"
7+ }
8+ },
9+ "AllowedHosts": "*",
10+ "Kestrel": {
11+ "EndpointDefaults": {
12+ "Protocols": "Http2"
13+ }
14+ }
15+}
--- SimpleJwt/ReadMe.md (nonexistent)
+++ SimpleJwt/ReadMe.md (revision 8)
@@ -0,0 +1,36 @@
1+What & Why)
2+
3+This poc is a simple usage of jwt for token based security.
4+
5+
6+Client)
7+
8+TokenTimeout.cs put token directly to each call.
9+We decode the given token for testing purpose,
10+moreover we wait a minute to see the 1 minute session timeout works properly.
11+
12+UseChannelCredentials.cs add token to a grpc channel permanently
13+which is easier to use i guess.
14+
15+
16+Service)
17+
18+The Authentication service adds id, name and then user roles as role claims to the token.
19+Greeter service use the Authorize attribute so the jwtbearer can verify one of the roles in our token match
20+and its allowed for the token to call SayHello. Remove 'Customer' Role from authorize attribute
21+in SayHello to see it fails by permission denied. We also say id and name are required token claims.
22+
23+
24+Optional)
25+
26+Change solution start settings to multiple and select server and client in this order.
27+
28+
29+Notes)
30+
31+Signing key for tokens should protected by DPAPI and stored somewhere else of course
32+or use Azure secret key store if u realy think this is a good idea.
33+
34+
35+Thats all folks
36+If u see issues or found and error, please tell me.
\ No newline at end of file
Show on old repository browser