grpc-dotnet authentication jwt bearer token

2.4k views Asked by At

I am looking for some feedback or guidance. I have built a grpc server and client in dotnet core 3.1. Only this client will be talking to the server, but still need to protect the server endpoints. I am trying to implement authentication security on it and the jwt bearer token seems like a good path. I am trying to follow the example here(which came from this MS Doc) which seems like a pretty simple implementation, however seems insecure for production use since anyone can hit the generate token endpoint. I figured it could modify and implement a shared secret that is passed on the get token request. Does this seem like a good approach or other thoughts?

Besides this part, I have a question about the client side code that I don't quite understand. At the start of the client, it establishes a grpc channel by calling the CreateAuthenticatedChannel() which appears to only add the bearer token if the _token is set. You don't get the token until the client starts up and hit number three, but I don't see where the channel or client is rebuilt to include the metadata token value. The sample appears to work to but don't understand it.

class Program
{
    // The port number(5001) must match the port of the gRPC server.
    private const string Address = "localhost:5001";

    private static string _token;

    static async Task Main(string[] args)
    {
        var channel = CreateAuthenticatedChannel($"https://{Address}");
        var client = new Ticketer.TicketerClient(channel);

        Console.WriteLine("gRPC Ticketer");
        Console.WriteLine();
        Console.WriteLine("Press a key:");
        Console.WriteLine("1: Get available tickets");
        Console.WriteLine("2: Purchase ticket");
        Console.WriteLine("3: Authenticate");
        Console.WriteLine("4: Exit");
        Console.WriteLine();

        var exiting = false;
        while (!exiting)
        {
            var consoleKeyInfo = Console.ReadKey(intercept: true);
            switch (consoleKeyInfo.KeyChar)
            {
                case '1':
                    await GetAvailableTickets(client);
                    break;
                case '2':
                    await PurchaseTicket(client);
                    break;
                case '3':
                    _token = await Authenticate();
                    break;
                case '4':
                    exiting = true;
                    break;
            }
        }

        Console.WriteLine("Exiting");
    }

    private static GrpcChannel CreateAuthenticatedChannel(string address)
    {
        var credentials = CallCredentials.FromInterceptor((context, metadata) =>
        {
            if (!string.IsNullOrEmpty(_token))
            {
                metadata.Add("Authorization", $"Bearer {_token}");
            }
            return Task.CompletedTask;
        });

        // SslCredentials is used here because this channel is using TLS.
        // Channels that aren't using TLS should use ChannelCredentials.Insecure instead.
        var channel = GrpcChannel.ForAddress(address, new GrpcChannelOptions
        {
            Credentials = ChannelCredentials.Create(new SslCredentials(), credentials)
        });
        return channel;
    }

    private static async Task<string> Authenticate()
    {
        Console.WriteLine($"Authenticating as {Environment.UserName}...");
        var httpClient = new HttpClient();
        var request = new HttpRequestMessage
        {
            RequestUri = new Uri($"https://{Address}/generateJwtToken?name={HttpUtility.UrlEncode(Environment.UserName)}"),
            Method = HttpMethod.Get,
            Version = new Version(2, 0)
        };
        var tokenResponse = await httpClient.SendAsync(request);
        tokenResponse.EnsureSuccessStatusCode();

        var token = await tokenResponse.Content.ReadAsStringAsync();
        Console.WriteLine(token);
        Console.WriteLine("Successfully authenticated.");

        return token;
    }

    private static async Task PurchaseTicket(Ticketer.TicketerClient client)
    {
        Console.WriteLine("Purchasing ticket...");
        try
        {
            var response = await client.BuyTicketsAsync(new BuyTicketsRequest { Count = 1 });
            if (response.Success)
            {
                Console.WriteLine("Purchase successful.");
            }
            else
            {
                Console.WriteLine("Purchase failed. No tickets available.");
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine("Error purchasing ticket." + Environment.NewLine + ex.ToString());
        }
    }

    private static async Task GetAvailableTickets(Ticketer.TicketerClient client)
    {
        Console.WriteLine("Getting available ticket count...");
        var response = await client.GetAvailableTicketsAsync(new Empty());
        Console.WriteLine("Available ticket count: " + response.Count);
    }
}

}

0

There are 0 answers