Feat/auth#133
Conversation
There was a problem hiding this comment.
Pull request overview
Adds an authentication flow centered on Supabase + Google OAuth, wiring up new backend auth endpoints and updating the frontend login link to initiate the flow.
Changes:
- Updated the Svelte login page to route sign-in through
/api/auth/google. - Introduced backend
AuthController,IAuthService, andAuthServicefor OAuth callback handling and token→user lookup. - Added JWT bearer authentication/authorization middleware registration and updated CORS policy naming.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| frontend/src/routes/login/+page.svelte | Points the login CTA to the new Google auth endpoint. |
| backend/TRFSAE.MemberPortal.API/Controllers/AuthController.cs | Implements Google OAuth redirect, callback exchange, /me, logout, and a temporary test endpoint. |
| backend/TRFSAE.MemberPortal.API/Interfaces/IAuthService.cs | Defines auth service contract used by the controller. |
| backend/TRFSAE.MemberPortal.API/Services/AuthService.cs | Implements token validation and user lookup from a Supabase JWT. |
| backend/TRFSAE.MemberPortal.API/Program.cs | Registers auth service, configures JWT bearer auth, enables UseAuthentication(), and renames CORS policy. |
| backend/TRFSAE.MemberPortal.API/Enums.cs | Adds Role.Unverified enum value. |
Comments suppressed due to low confidence (1)
backend/TRFSAE.MemberPortal.API/Program.cs:82
- CORS policy allows only
http://localhost:3000, but the dev frontend is configured to run onhttp://127.0.0.1:3000(seefrontend/vite.config.ts). If the frontend ever calls the API directly (without the dev proxy) this will fail CORS with credentials. Consider allowing both hosts in dev and/or moving allowed origins to configuration.
options.AddPolicy("AllowSvelteApp", policy =>
{
policy.WithOrigins("http://localhost:3000")
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| var handler = new JwtSecurityTokenHandler(); | ||
| var jwtToken = handler.ReadJwtToken(token); | ||
| var userIdClaim = jwtToken.Claims.FirstOrDefault(c => c.Type == "sub")?.Value; | ||
| if (userIdClaim == null) return null; | ||
|
|
||
| var userDto = await _userService.GetUserAsync(Guid.Parse(userIdClaim)); | ||
| if (userDto == null) return null; |
There was a problem hiding this comment.
hes right but probablyj ust implement it differently
| // Sync functionality removed to avoid changes outside auth | ||
| await Task.CompletedTask; |
There was a problem hiding this comment.
this part is now out of my paygrade; what do you think
| var supabaseUrl = builder.Configuration["SupabaseUrl"]; | ||
| var jwtSecret = builder.Configuration["SupabaseJwtSecret"]; | ||
|
|
||
| var url = builder.Configuration["SupabaseUrl"] ?? throw new InvalidOperationException("Supabase URL is not configured."); | ||
| var key = builder.Configuration["SupabaseKey"] ?? throw new InvalidOperationException("Supabase Key is not configured."); | ||
| return new Client(url, key, options); | ||
| }); | ||
| options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters | ||
| { | ||
| ValidateIssuer = true, | ||
| ValidIssuer = $"{supabaseUrl}/auth/v1", | ||
|
|
||
| ValidateAudience = false, | ||
|
|
||
| ValidateLifetime = true, | ||
|
|
||
| ValidateIssuerSigningKey = true, | ||
| IssuerSigningKey = new Microsoft.IdentityModel.Tokens.SymmetricSecurityKey( | ||
| System.Text.Encoding.UTF8.GetBytes(jwtSecret!) | ||
| ), |
| catch (Exception ex) | ||
| { | ||
| return StatusCode(500, "Authentication failed: " + ex.Message); | ||
| } |
There was a problem hiding this comment.
The copilot suggestion is right; but we don't really have to worry about it for now, we should probably just revisit this later before we release/not even bother bc this is member portal
| public bool ValidateSupabaseToken(string token) | ||
| { | ||
| if (string.IsNullOrEmpty(token)) return false; | ||
|
|
||
| try | ||
| { | ||
| var handler = new JwtSecurityTokenHandler(); | ||
| var jwtToken = handler.ReadJwtToken(token); | ||
| return jwtToken.ValidTo > DateTime.UtcNow; | ||
| } |
There was a problem hiding this comment.
what copilot said (man ts is gonna take my job)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
liangricky7
left a comment
There was a problem hiding this comment.
copilot stole my job
| catch (Exception ex) | ||
| { | ||
| return StatusCode(500, "Authentication failed: " + ex.Message); | ||
| } |
There was a problem hiding this comment.
The copilot suggestion is right; but we don't really have to worry about it for now, we should probably just revisit this later before we release/not even bother bc this is member portal
| public bool ValidateSupabaseToken(string token) | ||
| { | ||
| if (string.IsNullOrEmpty(token)) return false; | ||
|
|
||
| try | ||
| { | ||
| var handler = new JwtSecurityTokenHandler(); | ||
| var jwtToken = handler.ReadJwtToken(token); | ||
| return jwtToken.ValidTo > DateTime.UtcNow; | ||
| } |
There was a problem hiding this comment.
what copilot said (man ts is gonna take my job)
| var handler = new JwtSecurityTokenHandler(); | ||
| var jwtToken = handler.ReadJwtToken(token); | ||
| var userIdClaim = jwtToken.Claims.FirstOrDefault(c => c.Type == "sub")?.Value; | ||
| if (userIdClaim == null) return null; | ||
|
|
||
| var userDto = await _userService.GetUserAsync(Guid.Parse(userIdClaim)); | ||
| if (userDto == null) return null; |
There was a problem hiding this comment.
hes right but probablyj ust implement it differently
| // Sync functionality removed to avoid changes outside auth | ||
| await Task.CompletedTask; |
There was a problem hiding this comment.
this part is now out of my paygrade; what do you think
| var supabaseUrl = builder.Configuration["SupabaseUrl"]; | ||
| var jwtSecret = builder.Configuration["SupabaseJwtSecret"]; | ||
|
|
||
| var url = builder.Configuration["SupabaseUrl"] ?? throw new InvalidOperationException("Supabase URL is not configured."); | ||
| var key = builder.Configuration["SupabaseKey"] ?? throw new InvalidOperationException("Supabase Key is not configured."); | ||
| return new Client(url, key, options); | ||
| }); | ||
| options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters | ||
| { | ||
| ValidateIssuer = true, | ||
| ValidIssuer = $"{supabaseUrl}/auth/v1", | ||
|
|
||
| ValidateAudience = false, | ||
|
|
||
| ValidateLifetime = true, | ||
|
|
||
| ValidateIssuerSigningKey = true, | ||
| IssuerSigningKey = new Microsoft.IdentityModel.Tokens.SymmetricSecurityKey( | ||
| System.Text.Encoding.UTF8.GetBytes(jwtSecret!) | ||
| ), |
implemented auth