Skip to content

T3: Identity API controller + JWT auth#63

Open
devin-ai-integration[bot] wants to merge 2 commits into
devin/identity-t2-persistencefrom
devin/identity-t3-api
Open

T3: Identity API controller + JWT auth#63
devin-ai-integration[bot] wants to merge 2 commits into
devin/identity-t2-persistencefrom
devin/identity-t3-api

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements the full Identity API controller with JWT-based authentication and BCrypt password hashing.

Key changes:

  • IdentityController — four endpoints:

    • POST /api/identity/register → validates username/email uniqueness, BCrypt-hashes password, persists AppUser, returns 201 + AppUserDto
    • POST /api/identity/login → verifies credentials via BCrypt.Verify, checks IsEnabled, issues JWT with claims (sub, name, email, role), returns token
    • GET /api/identity/users ([Authorize]) → returns all users
    • GET /api/identity/users/{id:guid} ([Authorize]) → returns single user or 404
  • IdentityService (Infrastructure) — implements IIdentityService; JWT generation uses HmacSha256 with config-driven secret/issuer/audience/expiration; login rejects disabled accounts

  • Program.cs — registers JwtBearerDefaults authentication scheme with TokenValidationParameters, adds UseAuthentication()/UseAuthorization() middleware, registers IIdentityServiceIdentityService DI binding

  • NuGet additions: BCrypt.Net-Next 4.*, Microsoft.AspNetCore.Authentication.JwtBearer 10.*, System.IdentityModel.Tokens.Jwt 8.*, Microsoft.Extensions.Configuration.Abstractions 10.*

Build passes. Validated: register→201, login→200+token, unauthenticated→401, authenticated→200.

Link to Devin session: https://partner-workshops.devinenterprise.com/sessions/0f07b7a8f1014aec962930431c54da9f
Requested by: @mbatchelor81


Open in Devin Review

- Add JWT authentication middleware with bearer token validation
- Implement IdentityService with BCrypt password hashing and JWT generation
- Replace scaffold controller with full register/login/users endpoints
- Add [Authorize] protection to user listing endpoints
- Configure JWT settings in appsettings.json
- Register DI services for IIdentityService
@mbatchelor81 mbatchelor81 self-assigned this Jun 26, 2026
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 potential issues.

View 4 additional findings in Devin Review.

Open in Devin Review

Comment on lines +80 to +84
public async Task<IEnumerable<AppUserDto>> GetAllUsersAsync()
{
var users = await _context.Users.ToListAsync();
return users.Select(MapToDto);
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 GetAllUsersAsync loads entire user table into memory without pagination

The GetAllUsersAsync method at IdentityService.cs:82 calls ToListAsync() on the entire Users table with no pagination, filtering, or limit. As the user base grows, this will load all user records into memory in a single query. The GetUsers controller endpoint (IdentityController.cs:46-49) is protected by [Authorize] but not by any role restriction, so any authenticated user can trigger this. This could become a performance concern at scale.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged — this is a known limitation for now. Pagination would require changes to the IIdentityService interface in the Domain layer (which is out of scope for T3 per the task constraints). Can be addressed in a follow-up task.

Check user.IsEnabled after credential verification before issuing JWT token.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 3 additional findings in Devin Review.

Open in Devin Review

Comment on lines +64 to +72
if (user == null || !BCrypt.Net.BCrypt.Verify(request.Password, user.PasswordHash))
{
return new LoginResult { Success = false, ErrorMessage = "Invalid username or password." };
}

if (!user.IsEnabled)
{
return new LoginResult { Success = false, ErrorMessage = "This account has been disabled." };
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 Login verifies password before checking if account is disabled

In LoginAsync, the BCrypt password verification (IdentityService.cs:64) runs before the IsEnabled check (IdentityService.cs:69). This means: (1) expensive BCrypt work is performed for disabled accounts unnecessarily, and (2) a correct password on a disabled account yields a different error message ('This account has been disabled') than an incorrect one ('Invalid username or password'), which reveals account state to the caller. This may be intentional UX, but if not, reversing the order (check IsEnabled first, then verify password) would be both more efficient and more uniform in error responses.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is intentional UX — a legitimate user whose account was disabled should see a clear "account disabled" message rather than a generic credentials error (which would lead them to think they mistyped their password). The information disclosure tradeoff is acceptable here since username enumeration is already possible via the register endpoint's uniqueness check. The BCrypt cost on disabled accounts is negligible in practice.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant