Skip to content

Commit

Permalink
Merge pull request #15 from caiolombello/master
Browse files Browse the repository at this point in the history
Add token authentication feature and adjust tests
  • Loading branch information
droserasprout authored Jun 2, 2024
2 parents aa7d61f + e172fc5 commit b207c6e
Show file tree
Hide file tree
Showing 25 changed files with 1,460 additions and 318 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@
!**/pdm.lock
!**/README.md
!**/.keep
!**/docker-compose.yml
!**/AspNetAuthExample/**

# Add Python code
!**/*.py
**/.*_cache
**/__pycache__
!pytest.ini

# Add configs and scripts (but not env!)
!**/*.graphql
Expand All @@ -30,3 +33,4 @@
# Docs
!docs/**
docs/_build
!*.md
14 changes: 14 additions & 0 deletions AspNetAuthExample/AspNetAuthExample.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.18" />
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.1.0" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Core" Version="1.1.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
</ItemGroup>

</Project>
67 changes: 67 additions & 0 deletions AspNetAuthExample/Controller/AuthController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;

namespace AspNetAuthExample.Controllers
{
// Define the route for the API controller
[Route("api/[controller]")]
[ApiController]
public class AuthController : ControllerBase
{
// Define the POST endpoint for login
[HttpPost("login")]
public IActionResult Login([FromBody] LoginModel login)
{
// Check if the provided username and password match the predefined values
if (login.Username == "test" && login.Password == "password")
{
// Generate a JWT token if credentials are correct
var token = GenerateToken();
// Return the token in the response
return Ok(new { token });
}
// Return Unauthorized status if credentials are incorrect
return Unauthorized();
}

// Method to generate a JWT token
private string GenerateToken()
{
// Define the security key using a secret key
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("yoursecretkeyheretoSignalRserver"));
// Define the signing credentials using HMAC-SHA256 algorithm
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

// Define the claims to be included in the token
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, "testuser"),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};

// Create the JWT token with specified claims and expiration time
var token = new JwtSecurityToken(
issuer: null,
audience: null,
claims: claims,
expires: DateTime.Now.AddMinutes(30),
signingCredentials: credentials);

// Return the serialized token as a string
return new JwtSecurityTokenHandler().WriteToken(token);
}
}

// Model to represent the login request payload
public class LoginModel
{
// Username property
public string Username { get; set; }
// Password property
public string Password { get; set; }
}
}
22 changes: 22 additions & 0 deletions AspNetAuthExample/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Use the official ASP.NET Core runtime as a parent image
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 80

# Use the SDK image to build and publish the app
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["AspNetAuthExample.csproj", "./"]
RUN dotnet restore "AspNetAuthExample.csproj"
COPY . .
WORKDIR "/src/."
RUN dotnet build "AspNetAuthExample.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "AspNetAuthExample.csproj" -c Release -o /app/publish

# Final stage/image
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "AspNetAuthExample.dll"]
44 changes: 44 additions & 0 deletions AspNetAuthExample/Hub/WeatherHub.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks;

using AspNetAuthExample.Controllers;

namespace AspNetAuthExample
{
// Define a SignalR Hub for weather updates
public class WeatherHub : Hub
{
// Method to send a message to all connected clients
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}

// Method to send a message to a specific group
public async Task SendMessageToGroup(string groupName, string user, string message)
{
await Clients.Group(groupName).SendAsync("ReceiveMessage", user, message);
}

// Method to add a client to a group
public async Task AddToGroup(string groupName)
{
await Groups.AddToGroupAsync(Context.ConnectionId, groupName);
await Clients.Group(groupName).SendAsync("ReceiveMessage", "System", $"{Context.ConnectionId} has joined the group {groupName}.");
}

// Method to remove a client from a group
public async Task RemoveFromGroup(string groupName)
{
await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName);
await Clients.Group(groupName).SendAsync("ReceiveMessage", "System", $"{Context.ConnectionId} has left the group {groupName}.");
}

// Method to send the weather forecast to all clients
public async Task SendWeatherForecast(string forecast)
{
await Clients.All.SendAsync("ReceiveWeatherForecast", forecast);
}
}
}
24 changes: 24 additions & 0 deletions AspNetAuthExample/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;

namespace AspNetAuthExample
{
// Main entry point of the application
public class Program
{
public static void Main(string[] args)
{
// Build and run the host
CreateHostBuilder(args).Build().Run();
}

// Method to create and configure the host builder
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
// Specify the startup class to be used by the web host
webBuilder.UseStartup<Startup>();
});
}
}
80 changes: 80 additions & 0 deletions AspNetAuthExample/Startup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using System.Threading.Tasks;

namespace AspNetAuthExample
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}

public IConfiguration Configuration { get; }

// Method to configure the services for the application
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSignalR();
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("yoursecretkeyheretoSignalRserver"))
};
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];

var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) &&
path.StartsWithSegments("/weatherHub"))
{
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});

services.AddAuthorization();
}

// Method to configure the HTTP request pipeline
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}

app.UseHttpsRedirection();

app.UseRouting();

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

app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHub<WeatherHub>("/weatherHub");
});
}
}
}
17 changes: 10 additions & 7 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,33 @@
help: ## Show this help (default)
@grep -F -h "##" $(MAKEFILE_LIST) | grep -F -v fgrep | sed -e 's/\\$$//' | sed -e 's/##//'

all: ## Run a whole CI pipeline: formatters, linters and tests
all: ## Run a whole CI pipeline: formatters, linters, tests and docs
make lint test docs

lint: ## Lint with all tools
make black ruff mypy

test: ## Run test suite
pytest --cov-report=term-missing --cov=pysignalr --cov-report=xml -s -v tests
docker-compose up --build --exit-code-from test_runner test_runner || docker compose down

docs: ## Generate documentation
docker-compose run --rm docs sphinx-build -b html docs/source docs/build || docker compose down

##

black: ## Format with black
black src tests example.py
docker-compose run --rm formatter 'pip install black && black src tests example.py' || docker compose down

ruff: ## Lint with ruff
ruff check --fix --unsafe-fixes src tests example.py
docker-compose run --rm linter 'pip install ruff && ruff check --fix --unsafe-fixes src tests example.py' || docker compose down

mypy: ## Lint with mypy
mypy --strict src tests example.py
docker-compose run --rm linter 'pip install mypy && mypy --strict src tests example.py' || docker compose down

cover: ## Print coverage for the current branch
diff-cover --compare-branch `git symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@'` coverage.xml
docker-compose run --rm coverage 'pip install diff-cover && diff-cover --compare-branch `git symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@'` coverage.xml' || docker compose down

##

clean: ## Remove all files from .gitignore except for `.venv`
git clean -xdf --exclude=".venv"
sudo git clean -xdf --exclude=".venv"
Loading

0 comments on commit b207c6e

Please sign in to comment.