├── 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 | --------------------------------------------------------------------------------