├── CreditRatingClient
├── appsettings.json
├── Protos
│ └── credit-rating-service.proto
├── .gitignore
├── CreditRatingClient.csproj
└── Program.cs
├── CreditRatingService
├── appsettings.Development.json
├── Properties
│ └── launchSettings.json
├── Protos
│ └── credit-rating-service.proto
├── .gitignore
├── appsettings.json
├── CreditRatingService.csproj
├── Program.cs
├── Services
│ └── CreditRatingService.cs
└── Startup.cs
└── README.md
/CreditRatingClient/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Auth0": {
3 | "Domain": "YOUR_AUTH0_DOMAIN",
4 | "Audience": "YOUR_UNIQUE_IDENTIFIER",
5 | "ClientId": "YOUR_CLIENT_ID",
6 | "ClientSecret": "YOUR_CLIENT_SECRET"
7 | }
8 | }
--------------------------------------------------------------------------------
/CreditRatingService/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Debug",
5 | "System": "Information",
6 | "Grpc": "Information",
7 | "Microsoft": "Information"
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/CreditRatingService/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "CreditRatingService": {
4 | "commandName": "Project",
5 | "launchBrowser": false,
6 | "applicationUrl": "https://localhost:5001",
7 | "environmentVariables": {
8 | "ASPNETCORE_ENVIRONMENT": "Development"
9 | }
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/CreditRatingClient/Protos/credit-rating-service.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | option csharp_namespace = "CreditRatingService";
4 |
5 | package CreditRating;
6 |
7 | service CreditRatingCheck {
8 | rpc CheckCreditRequest (CreditRequest) returns (CreditReply);
9 | }
10 |
11 | message CreditRequest {
12 | string customerId = 1;
13 | int32 credit = 2;
14 | }
15 |
16 | message CreditReply {
17 | bool isAccepted = 1;
18 | }
--------------------------------------------------------------------------------
/CreditRatingService/Protos/credit-rating-service.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | option csharp_namespace = "CreditRatingService";
4 |
5 | package CreditRating;
6 |
7 | service CreditRatingCheck {
8 | rpc CheckCreditRequest (CreditRequest) returns (CreditReply);
9 | }
10 |
11 | message CreditRequest {
12 | string customerId = 1;
13 | int32 credit = 2;
14 | }
15 |
16 | message CreditReply {
17 | bool isAccepted = 1;
18 | }
--------------------------------------------------------------------------------
/CreditRatingClient/.gitignore:
--------------------------------------------------------------------------------
1 | *.swp
2 | *.*~
3 | project.lock.json
4 | .DS_Store
5 | *.pyc
6 |
7 | # Visual Studio Code
8 | .vscode
9 |
10 | # User-specific files
11 | *.suo
12 | *.user
13 | *.userosscache
14 | *.sln.docstates
15 |
16 | # Build results
17 | [Dd]ebug/
18 | [Dd]ebugPublic/
19 | [Rr]elease/
20 | [Rr]eleases/
21 | x64/
22 | x86/
23 | build/
24 | bld/
25 | [Bb]in/
26 | [Oo]bj/
27 | msbuild.log
28 | msbuild.err
29 | msbuild.wrn
--------------------------------------------------------------------------------
/CreditRatingService/.gitignore:
--------------------------------------------------------------------------------
1 | *.swp
2 | *.*~
3 | project.lock.json
4 | .DS_Store
5 | *.pyc
6 |
7 | # Visual Studio Code
8 | .vscode
9 |
10 | # User-specific files
11 | *.suo
12 | *.user
13 | *.userosscache
14 | *.sln.docstates
15 |
16 | # Build results
17 | [Dd]ebug/
18 | [Dd]ebugPublic/
19 | [Rr]elease/
20 | [Rr]eleases/
21 | x64/
22 | x86/
23 | build/
24 | bld/
25 | [Bb]in/
26 | [Oo]bj/
27 | msbuild.log
28 | msbuild.err
29 | msbuild.wrn
--------------------------------------------------------------------------------
/CreditRatingService/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Warning",
5 | "Microsoft.Hosting.Lifetime": "Information"
6 | }
7 | },
8 | "AllowedHosts": "*",
9 | "Kestrel": {
10 | "EndpointDefaults": {
11 | "Protocols": "Http2"
12 | }
13 | },
14 | "Auth0": {
15 | "Domain": "YOUR_AUTH0_DOMAIN",
16 | "Audience": "YOUR_UNIQUE_IDENTIFIER"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/CreditRatingService/CreditRatingService.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.1
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/CreditRatingService/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Hosting;
2 | using Microsoft.Extensions.Hosting;
3 | using Microsoft.AspNetCore.Server.Kestrel.Core;
4 | using System.Runtime.InteropServices;
5 |
6 | namespace CreditRatingService
7 | {
8 | public class Program
9 | {
10 | public static void Main(string[] args)
11 | {
12 | CreateHostBuilder(args).Build().Run();
13 | }
14 |
15 | public static IHostBuilder CreateHostBuilder(string[] args) =>
16 | Host.CreateDefaultBuilder(args)
17 | .ConfigureWebHostDefaults(webBuilder =>
18 | {
19 | if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) {
20 | webBuilder.ConfigureKestrel(options =>
21 | {
22 | // Setup a HTTP/2 endpoint without TLS.
23 | options.ListenLocalhost(5000, o => o.Protocols =
24 | HttpProtocols.Http2);
25 | });
26 | }
27 | webBuilder.UseStartup();
28 | });
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This repository contains two .NET Core projects implementing a service application (`CreditRatingService`) and a client (`CreditRatingClient`) communicating via gRPC and secured with [Auth0]("http://localhost:5000").
2 |
3 | The following article describes the implementation details: [Securing gRPC-based Microservices in .NET Core](https://auth0.com/blog/securing-grpc-microservices-dotnet-core/)
4 |
5 | ## To run the applications:
6 |
7 | Clone the repo: `git clone https://github.com/auth0-blog/secure-grpc-dotnet.git`
8 |
9 | To run the `CreditRatingService` application:
10 |
11 | 1. Move to the `CreditRatingService` folder
12 | 2. Fill the `appsettings.json` file with the registered API parameters from Auth0
13 | 3. Type `dotnet run` in a terminal window
14 |
15 |
16 |
17 | To run the `CreditRatingClient` application:
18 |
19 | 1. Move to the `CreditRatingClient` folder
20 | 2. Fill the `appsettings.json` file with the registered M2M Application parameters from Auth0
21 | 3. Type `dotnet run` in a terminal window
22 |
23 |
24 |
25 | ## Requirements:
26 |
27 | - [.NET Core 3.1](https://dotnet.microsoft.com/download/dotnet-core/3.1) installed on your machine
28 |
29 |
30 |
--------------------------------------------------------------------------------
/CreditRatingClient/CreditRatingClient.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp3.1
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | runtime; build; native; contentfiles; analyzers; buildtransitive
14 | all
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | PreserveNewest
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/CreditRatingService/Services/CreditRatingService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 | using Grpc.Core;
5 | using Microsoft.Extensions.Logging;
6 | using Microsoft.AspNetCore.Authorization;
7 |
8 | namespace CreditRatingService
9 | {
10 | public class CreditRatingCheckService : CreditRatingCheck.CreditRatingCheckBase
11 | {
12 | private readonly ILogger _logger;
13 | private static readonly Dictionary customerTrustedCredit = new Dictionary()
14 | {
15 | {"id0201", 10000},
16 | {"id0417", 5000},
17 | {"id0306", 15000}
18 | };
19 | public CreditRatingCheckService(ILogger logger)
20 | {
21 | _logger = logger;
22 | }
23 |
24 | [Authorize]
25 | public override Task CheckCreditRequest(CreditRequest request, ServerCallContext context)
26 | {
27 | return Task.FromResult(new CreditReply
28 | {
29 | IsAccepted = IsEligibleForCredit(request.CustomerId, request.Credit)
30 | });
31 | }
32 |
33 | private bool IsEligibleForCredit(string customerId, Int32 credit)
34 | {
35 | bool isEligible = false;
36 |
37 | if (customerTrustedCredit.TryGetValue(customerId, out Int32 maxCredit))
38 | {
39 | isEligible = credit <= maxCredit;
40 | }
41 |
42 | return isEligible;
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/CreditRatingService/Startup.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Builder;
2 | using Microsoft.AspNetCore.Hosting;
3 | using Microsoft.AspNetCore.Http;
4 | using Microsoft.Extensions.DependencyInjection;
5 | using Microsoft.Extensions.Hosting;
6 | using Microsoft.Extensions.Configuration;
7 | using Microsoft.AspNetCore.Authentication.JwtBearer;
8 |
9 | namespace CreditRatingService
10 | {
11 | public class Startup
12 | {
13 | public IConfiguration Configuration { get; }
14 | public Startup(IConfiguration configuration)
15 | {
16 | Configuration = configuration;
17 | }
18 | // This method gets called by the runtime. Use this method to add services to the container.
19 | // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
20 | public void ConfigureServices(IServiceCollection services)
21 | {
22 | services.AddAuthentication(options =>
23 | {
24 | options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
25 | options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
26 | }).AddJwtBearer(options =>
27 | {
28 | options.Authority = $"https://{Configuration["Auth0:Domain"]}/";
29 | options.Audience = Configuration["Auth0:Audience"];
30 | });
31 | services.AddAuthorization();
32 | services.AddGrpc();
33 | }
34 |
35 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
36 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
37 | {
38 | if (env.IsDevelopment())
39 | {
40 | app.UseDeveloperExceptionPage();
41 | }
42 |
43 | app.UseRouting();
44 |
45 | app.UseAuthentication();
46 | app.UseAuthorization();
47 |
48 | app.UseEndpoints(endpoints =>
49 | {
50 | endpoints.MapGrpcService();
51 |
52 | endpoints.MapGet("/", async context =>
53 | {
54 | 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");
55 | });
56 | });
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/CreditRatingClient/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using CreditRatingService;
4 | using Grpc.Net.Client;
5 | using System.Runtime.InteropServices;
6 | using System.IO;
7 | using Microsoft.Extensions.Configuration;
8 | using Grpc.Core;
9 | using Auth0.AuthenticationApi;
10 | using Auth0.AuthenticationApi.Models;
11 |
12 | namespace CreditRatingClient
13 | {
14 | class Program
15 | {
16 | static async Task Main(string[] args)
17 | {
18 | var serverAddress = "https://localhost:5001";
19 |
20 | if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
21 | {
22 | // The following statement allows you to call insecure services. To be used only in development environments.
23 | AppContext.SetSwitch(
24 | "System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
25 | serverAddress = "http://localhost:5000";
26 | }
27 |
28 | var channel = GrpcChannel.ForAddress(serverAddress);
29 | var client = new CreditRatingCheck.CreditRatingCheckClient(channel);
30 | var creditRequest = new CreditRequest { CustomerId = "id0201", Credit = 7000 };
31 |
32 | var accessToken = await GetAccessToken();
33 | var headers = new Metadata();
34 | headers.Add("Authorization", $"Bearer {accessToken}");
35 |
36 | var reply = await client.CheckCreditRequestAsync(creditRequest, headers);
37 |
38 | Console.WriteLine($"Credit for customer {creditRequest.CustomerId} {(reply.IsAccepted ? "approved" : "rejected")}!");
39 | Console.WriteLine("Press any key to exit...");
40 | Console.ReadKey();
41 | }
42 |
43 | // Using an authorized channel doesn't work on MacOS
44 | //static async Task Main(string[] args)
45 | //{
46 | // //// The port number(5001) must match the port of the gRPC server.
47 | // var channel = await CreateAuthorizedChannel("https://localhost:5001");
48 | // var client = new CreditRatingCheck.CreditRatingCheckClient(channel);
49 | // var creditRequest = new CreditRequest { CustomerId = "id0201", Credit = 7000 };
50 | // var reply = await client.CheckCreditRequestAsync(creditRequest);
51 |
52 | // Console.WriteLine($"Credit for customer {creditRequest.CustomerId} {(reply.IsAccepted ? "approved" : "rejected")}!");
53 | // Console.WriteLine("Press any key to exit...");
54 | // Console.ReadKey();
55 | //}
56 |
57 | static IConfiguration GetAppSettings()
58 | {
59 | var builder = new ConfigurationBuilder()
60 | .SetBasePath(Directory.GetCurrentDirectory())
61 | .AddJsonFile("appsettings.json");
62 |
63 | return builder.Build();
64 | }
65 |
66 | static async Task GetAccessToken()
67 | {
68 | var appAuth0Settings = GetAppSettings().GetSection("Auth0");
69 | var auth0Client = new AuthenticationApiClient(appAuth0Settings["Domain"]);
70 | var tokenRequest = new ClientCredentialsTokenRequest()
71 | {
72 | ClientId = appAuth0Settings["ClientId"],
73 | ClientSecret = appAuth0Settings["ClientSecret"],
74 | Audience = appAuth0Settings["Audience"]
75 | };
76 | var tokenResponse = await auth0Client.GetTokenAsync(tokenRequest);
77 |
78 | return tokenResponse.AccessToken;
79 | }
80 |
81 | private async static Task CreateAuthorizedChannel(string address)
82 | {
83 | var accessToken = await GetAccessToken();
84 |
85 | var credentials = CallCredentials.FromInterceptor((context, metadata) =>
86 | {
87 | if (!string.IsNullOrEmpty(accessToken))
88 | {
89 | metadata.Add("Authorization", $"Bearer {accessToken}");
90 | }
91 | return Task.CompletedTask;
92 | });
93 |
94 | // SslCredentials is used here because this channel is using TLS.
95 | // CallCredentials can't be used with ChannelCredentials.Insecure on non-TLS channels.
96 | var channel = GrpcChannel.ForAddress(address, new GrpcChannelOptions
97 | {
98 | Credentials = ChannelCredentials.Create(new SslCredentials(), credentials)
99 | });
100 | return channel;
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------