DEV Community

Adrián Bailador
Adrián Bailador

Posted on

Creating a .NET API with WebSockets and JWT authentication for real-time chat

Introduction

In this article, we will walk through how to build a real-time chat system with the integration of GPT-4 from OpenAI. We'll create an application that not only communicates in real-time but also validates users with JWT tokens to ensure that only authenticated users can interact with the service.

1. Create the Project

First, we'll create a Web API project in .NET and add the necessary dependencies to work with JWT and WebSockets.

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
dotnet add package Microsoft.AspNetCore.WebSockets
Enter fullscreen mode Exit fullscreen mode

2. Configuration in Program.cs

In this section, we configure the API, set up JWT authentication using a secret to validate the tokens, enable WebSockets for real-time communication, and specify that the /ws route can only be accessed with a valid token.

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Net.WebSockets;
using System.Text;

var builder = WebApplication.CreateBuilder(args);

// JWT Authentication Configuration
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = false,
            ValidateAudience = false,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("SecretKey"))
        };
    });

builder.Services.AddHttpClient();
var app = builder.Build();


app.UseAuthentication();
app.UseAuthorization();
app.UseWebSockets();

// WebSocket route protected with JWT
app.MapGet("/ws", WebSocketHandler.HandleWebSocket);

app.Run();
Enter fullscreen mode Exit fullscreen mode

3. JWT Token Generation

Now, let's create a class called AuthService.cs to handle generating the JWT tokens used to authenticate users.

using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.IdentityModel.Tokens;

public class AuthService
{
    private const string SecretKey = "SecretKey";

    public static string GenerateJwtToken()
    {
        var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SecretKey));
        var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
        var token = new JwtSecurityToken(
            claims: new List<Claim> { new Claim(ClaimTypes.Name, "user") },
            expires: DateTime.UtcNow.AddHours(1),
            signingCredentials: credentials);

        return new JwtSecurityTokenHandler().WriteToken(token);
    }
}
Enter fullscreen mode Exit fullscreen mode
  • The GenerateJwtToken function creates a token with a username (Claim) and signs it with a secret key.
  • The token expires in 1 hour, but you can adjust this to your needs.

4. WebSocket Handling and Token Validation

In the WebSocketHandler.cs file, we handle the WebSocket connections and validate that the JWT token is valid before allowing the connection.

using System.IdentityModel.Tokens.Jwt;
using System.Net.WebSockets;
using System.Security.Claims;
using System.Text;
using Microsoft.IdentityModel.Tokens;

public class WebSocketHandler
{
    public static async Task HandleWebSocket(HttpContext context, HttpClient httpClient)
    {
        if (!context.WebSockets.IsWebSocketRequest)
        {
            context.Response.StatusCode = 400;
            return;
        }

        var authHeader = context.Request.Headers["Authorization"].ToString();
        if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Bearer "))
        {
            context.Response.StatusCode = 401;
            return;
        }

        var token = authHeader.Substring("Bearer ".Length).Trim();
        if (!ValidateToken(token))
        {
            context.Response.StatusCode = 401;
            return;
        }

        using var webSocket = await context.WebSockets.AcceptWebSocketAsync();
        var buffer = new byte[1024 * 4];

        while (true)
        {
            var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
            if (result.MessageType == WebSocketMessageType.Close)
            {
                await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closure requested", CancellationToken.None);
                break;
            }

            var message = Encoding.UTF8.GetString(buffer, 0, result.Count);
            var response = await ChatbotService.GetChatbotResponse(httpClient, message);

            var responseBytes = Encoding.UTF8.GetBytes(response);
            await webSocket.SendAsync(new ArraySegment<byte>(responseBytes), WebSocketMessageType.Text, true, CancellationToken.None);
        }
    }

    private static bool ValidateToken(string token)
    {
        var tokenHandler = new JwtSecurityTokenHandler();
        try
        {
            tokenHandler.ValidateToken(token, new TokenValidationParameters
            {
                ValidateIssuer = false,
                ValidateAudience = false,
                ValidateLifetime = true,
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("SecretKey"))
            }, out _);
            return true;
        }
        catch
        {
            return false;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode
  • It receives the WebSocket request.
  • Verifies that the Authorization header is present and valid.
  • If the token is valid, it accepts the WebSocket connection and begins listening for messages.

5. Communication with OpenAI (GPT-4)

The ChatbotService.cs file is where the magic happens. Here, we send a request to OpenAI to get responses using the GPT-4 model.

using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;

public class ChatbotService
{
    private static readonly string apiKey = "API-KEY-ChatGPT";
    private static readonly string apiUrl = "https://api.openai.com/v1/chat/completions";

    public static async Task<string> GetChatbotResponse(HttpClient httpClient, string userMessage)
    {
        var requestBody = new
        {
            model = "gpt-4",
            messages = new[]
            {
                new { role = "system", content = "You are a helpful assistant." },
                new { role = "user", content = userMessage }
            },
            stream = true
        };

        httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
        var response = await httpClient.PostAsJsonAsync(apiUrl, requestBody);

        if (!response.IsSuccessStatusCode)
            return "Error in the chatbot response.";

        using var responseStream = await response.Content.ReadAsStreamAsync();
        using var reader = new StreamReader(responseStream);
        var responseText = new StringBuilder();

        while (!reader.EndOfStream)
        {
            var line = await reader.ReadLineAsync();
            if (!string.IsNullOrWhiteSpace(line) && line.StartsWith("data:"))
            {
                var json = line.Substring(5).Trim();
                var jsonDoc = JsonDocument.Parse(json);
                responseText.Append(jsonDoc.RootElement.GetProperty("choices")[0].GetProperty("message").GetProperty("content").GetString());
            }
        }

        return responseText.ToString();
    }
}
Enter fullscreen mode Exit fullscreen mode
  • This service makes an HTTP call to OpenAI using your API Key.
  • It sends the user's message to the GPT-4 model and returns the response generated in real time.

6. Testing the API

Now that we have everything set up, let's test the API by generating a JWT token and using tools like Postman or wscat for testing.

To generate the token, simply run:

var token = AuthService.GenerateJwtToken();
Console.WriteLine($"Generated Token: {token}");
Enter fullscreen mode Exit fullscreen mode

Then, in Postman or wscat, connect to the WebSocket using the generated token.

You can find the complete source code for this project on GitHub: ChatbotAPI Repository.

Top comments (0)