|
| 1 | +<!-- 71b416db-4a21-4a16-88c9-4334eb7f72c1 e85163ce-c2f0-49b5-b924-bc011a8e060a --> |
| 2 | +# User Authentication Implementation Plan |
| 3 | + |
| 4 | +## Overview |
| 5 | + |
| 6 | +Implement GitHub OAuth App authentication flow for the Blazor web dashboard, including team-based authorization, secure session management, logout functionality, and first-time user onboarding wizard. |
| 7 | + |
| 8 | +## Phase 1: NuGet Packages & Configuration |
| 9 | + |
| 10 | +### 1.1 Add Required NuGet Packages |
| 11 | + |
| 12 | +Add to `10xGitHubPolicies.App.csproj`: |
| 13 | + |
| 14 | +- `AspNet.Security.OAuth.GitHub` (latest stable version) |
| 15 | +- `Microsoft.AspNetCore.Authentication.Cookies` (included in .NET 8, verify) |
| 16 | + |
| 17 | +### 1.2 Configure User Secrets |
| 18 | + |
| 19 | +Set up OAuth App credentials using .NET Secret Manager: |
| 20 | + |
| 21 | +```bash |
| 22 | +cd 10xGitHubPolicies.App |
| 23 | +dotnet user-secrets set "GitHub:ClientId" "YOUR_OAUTH_APP_CLIENT_ID" |
| 24 | +dotnet user-secrets set "GitHub:ClientSecret" "YOUR_OAUTH_APP_CLIENT_SECRET" |
| 25 | +``` |
| 26 | + |
| 27 | +### 1.3 Update appsettings.json |
| 28 | + |
| 29 | +Add placeholder for OAuth configuration (actual values in secrets): |
| 30 | + |
| 31 | +```json |
| 32 | +"GitHub": { |
| 33 | + "ClientId": "", |
| 34 | + "ClientSecret": "" |
| 35 | +} |
| 36 | +``` |
| 37 | + |
| 38 | +## Phase 2: Authentication Infrastructure |
| 39 | + |
| 40 | +### 2.1 Configure Authentication in Program.cs |
| 41 | + |
| 42 | +Add authentication middleware before `var app = builder.Build();`: |
| 43 | + |
| 44 | +```csharp |
| 45 | +// Add authentication services |
| 46 | +builder.Services.AddAuthentication(options => |
| 47 | +{ |
| 48 | + options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; |
| 49 | + options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; |
| 50 | + options.DefaultChallengeScheme = "GitHub"; |
| 51 | +}) |
| 52 | +.AddCookie(options => |
| 53 | +{ |
| 54 | + options.LoginPath = "/login"; |
| 55 | + options.LogoutPath = "/logout"; |
| 56 | + options.AccessDeniedPath = "/access-denied"; |
| 57 | + options.ExpireTimeSpan = TimeSpan.FromHours(24); |
| 58 | + options.SlidingExpiration = false; |
| 59 | +}) |
| 60 | +.AddGitHub(options => |
| 61 | +{ |
| 62 | + options.ClientId = builder.Configuration["GitHub:ClientId"]; |
| 63 | + options.ClientSecret = builder.Configuration["GitHub:ClientSecret"]; |
| 64 | + options.CallbackPath = "/signin-github"; |
| 65 | + options.Scope.Add("read:org"); |
| 66 | + options.SaveTokens = true; // Save access token for team verification |
| 67 | +}); |
| 68 | + |
| 69 | +builder.Services.AddAuthorization(); |
| 70 | +builder.Services.AddHttpContextAccessor(); |
| 71 | +``` |
| 72 | + |
| 73 | +### 2.2 Add Authentication Middleware |
| 74 | + |
| 75 | +Add after `app.UseRouting();` in Program.cs: |
| 76 | + |
| 77 | +```csharp |
| 78 | +app.UseAuthentication(); |
| 79 | +app.UseAuthorization(); |
| 80 | +``` |
| 81 | + |
| 82 | +## Phase 3: Create Authentication Pages |
| 83 | + |
| 84 | +### 3.1 Create Login Page (`Pages/Login.razor`) |
| 85 | + |
| 86 | +- Display "Login with GitHub" button |
| 87 | +- Trigger OAuth challenge on click |
| 88 | +- Show application branding/description |
| 89 | + |
| 90 | +### 3.2 Create Access Denied Page (`Pages/AccessDenied.razor`) |
| 91 | + |
| 92 | +- Display clear message about insufficient permissions |
| 93 | +- Show which team is required (from config) |
| 94 | +- Provide instructions on requesting access |
| 95 | +- Include logout button |
| 96 | + |
| 97 | +### 3.3 Create Logout Page (`Pages/Logout.razor`) |
| 98 | + |
| 99 | +- Handle sign-out logic |
| 100 | +- Clear authentication cookies |
| 101 | +- Redirect to login page |
| 102 | + |
| 103 | +### 3.4 Create Onboarding Page (`Pages/Onboarding.razor`) |
| 104 | + |
| 105 | +- Step-by-step wizard for first-time setup |
| 106 | +- Display config.yaml template with copy button |
| 107 | +- Instructions for creating `.github` repository |
| 108 | +- Instructions for placing config.yaml file |
| 109 | +- "Check Configuration" button to verify setup |
| 110 | + |
| 111 | +## Phase 4: Authorization Service |
| 112 | + |
| 113 | +### 4.1 Create Authorization Service Interface |
| 114 | + |
| 115 | +Create `Services/Authorization/IAuthorizationService.cs`: |
| 116 | + |
| 117 | +```csharp |
| 118 | +public interface IAuthorizationService |
| 119 | +{ |
| 120 | + Task<bool> IsUserAuthorizedAsync(ClaimsPrincipal user); |
| 121 | + Task<string?> GetAuthorizedTeamAsync(); |
| 122 | +} |
| 123 | +``` |
| 124 | + |
| 125 | +### 4.2 Implement Authorization Service |
| 126 | + |
| 127 | +Create `Services/Authorization/AuthorizationService.cs`: |
| 128 | + |
| 129 | +- Inject `IGitHubService` and `IConfigurationService` |
| 130 | +- Extract user's access token from claims |
| 131 | +- Retrieve authorized team from config.yaml |
| 132 | +- Call `IGitHubService.IsUserMemberOfTeamAsync()` to verify membership |
| 133 | +- Handle configuration errors gracefully |
| 134 | + |
| 135 | +### 4.3 Register Service in Program.cs |
| 136 | + |
| 137 | +```csharp |
| 138 | +builder.Services.AddScoped<IAuthorizationService, AuthorizationService>(); |
| 139 | +``` |
| 140 | + |
| 141 | +## Phase 5: Protect Dashboard & Add Authorization Logic |
| 142 | + |
| 143 | +### 5.1 Update Index.razor |
| 144 | + |
| 145 | +- Add `@attribute [Authorize]` at the top |
| 146 | +- Inject `IAuthorizationService` and `IConfigurationService` |
| 147 | +- In `OnInitializedAsync()`: |
| 148 | + - Check if config.yaml exists (catch `ConfigurationNotFoundException`) |
| 149 | + - If missing, redirect to `/onboarding` |
| 150 | + - If present, verify team membership via `IAuthorizationService` |
| 151 | + - If not authorized, redirect to `/access-denied` |
| 152 | + - If authorized, load dashboard data as normal |
| 153 | + |
| 154 | +### 5.2 Update App.razor |
| 155 | + |
| 156 | +Replace with `CascadingAuthenticationState` wrapper: |
| 157 | + |
| 158 | +```razor |
| 159 | +<CascadingAuthenticationState> |
| 160 | + <Router AppAssembly="@typeof(App).Assembly"> |
| 161 | + <Found Context="routeData"> |
| 162 | + <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"> |
| 163 | + <NotAuthorized> |
| 164 | + <RedirectToLogin /> |
| 165 | + </NotAuthorized> |
| 166 | + </AuthorizeRouteView> |
| 167 | + <FocusOnNavigate RouteData="@routeData" Selector="h1" /> |
| 168 | + </Found> |
| 169 | + <NotFound> |
| 170 | + <PageTitle>Not found</PageTitle> |
| 171 | + <LayoutView Layout="@typeof(MainLayout)"> |
| 172 | + <p role="alert">Sorry, there's nothing at this address.</p> |
| 173 | + </LayoutView> |
| 174 | + </NotFound> |
| 175 | + </Router> |
| 176 | +</CascadingAuthenticationState> |
| 177 | +``` |
| 178 | + |
| 179 | +### 5.3 Create RedirectToLogin Component |
| 180 | + |
| 181 | +Create `Shared/RedirectToLogin.razor`: |
| 182 | + |
| 183 | +- Use `NavigationManager` to redirect to `/login` |
| 184 | + |
| 185 | +## Phase 6: Update UI Components |
| 186 | + |
| 187 | +### 6.1 Update MainLayout.razor |
| 188 | + |
| 189 | +- Add `<AuthorizeView>` to show user info when authenticated |
| 190 | +- Display username from claims |
| 191 | +- Add logout button in header/navigation |
| 192 | +- Show login prompt for unauthenticated users |
| 193 | + |
| 194 | +### 6.2 Update _Imports.razor |
| 195 | + |
| 196 | +Add required namespaces: |
| 197 | + |
| 198 | +```razor |
| 199 | +@using Microsoft.AspNetCore.Components.Authorization |
| 200 | +@using Microsoft.AspNetCore.Authorization |
| 201 | +@using System.Security.Claims |
| 202 | +``` |
| 203 | + |
| 204 | +## Phase 7: Testing & Validation |
| 205 | + |
| 206 | +### 7.1 Manual Testing Checklist |
| 207 | + |
| 208 | +- Unauthenticated user redirected to login |
| 209 | +- OAuth flow completes successfully |
| 210 | +- Authorized team member can access dashboard |
| 211 | +- Non-team member sees access denied page |
| 212 | +- Missing config.yaml shows onboarding wizard |
| 213 | +- Logout clears session and redirects to login |
| 214 | +- Session expires after 24 hours |
| 215 | +- Callback URL `/signin-github` works correctly |
| 216 | + |
| 217 | +### 7.2 Error Scenarios |
| 218 | + |
| 219 | +- Handle GitHub OAuth errors gracefully |
| 220 | +- Handle network failures during team verification |
| 221 | +- Handle invalid/expired access tokens |
| 222 | +- Handle malformed config.yaml |
| 223 | + |
| 224 | +## Phase 8: Documentation Updates |
| 225 | + |
| 226 | +### 8.1 Update README.md |
| 227 | + |
| 228 | +- Add OAuth App setup instructions |
| 229 | +- Document required GitHub OAuth App configuration |
| 230 | +- Add callback URL setup instructions |
| 231 | +- Update local development setup steps |
| 232 | + |
| 233 | +### 8.2 Create Authentication Documentation |
| 234 | + |
| 235 | +Create `docs/authentication.md`: |
| 236 | + |
| 237 | +- Detailed OAuth flow explanation |
| 238 | +- Team-based authorization process |
| 239 | +- Session management details |
| 240 | +- Troubleshooting guide |
| 241 | + |
| 242 | +### 8.3 Update CHANGELOG.md |
| 243 | + |
| 244 | +Add entry for authentication feature implementation |
| 245 | + |
| 246 | +## Key Files to Create/Modify |
| 247 | + |
| 248 | +**New Files:** |
| 249 | + |
| 250 | +- `Pages/Login.razor` |
| 251 | +- `Pages/Logout.razor` |
| 252 | +- `Pages/AccessDenied.razor` |
| 253 | +- `Pages/Onboarding.razor` |
| 254 | +- `Services/Authorization/IAuthorizationService.cs` |
| 255 | +- `Services/Authorization/AuthorizationService.cs` |
| 256 | +- `Shared/RedirectToLogin.razor` |
| 257 | +- `docs/authentication.md` |
| 258 | + |
| 259 | +**Modified Files:** |
| 260 | + |
| 261 | +- `Program.cs` (authentication configuration) |
| 262 | +- `10xGitHubPolicies.App.csproj` (NuGet packages) |
| 263 | +- `Pages/Index.razor` (authorization checks) |
| 264 | +- `App.razor` (authentication state) |
| 265 | +- `Shared/MainLayout.razor` (user info, logout) |
| 266 | +- `_Imports.razor` (namespaces) |
| 267 | +- `appsettings.json` (OAuth config placeholders) |
| 268 | +- `README.md` (setup instructions) |
| 269 | +- `CHANGELOG.md` (feature entry) |
| 270 | + |
| 271 | +## Security Considerations |
| 272 | + |
| 273 | +1. **Token Storage**: OAuth access tokens stored securely in authentication cookie (encrypted by ASP.NET Core) |
| 274 | +2. **HTTPS Only**: Ensure cookies are marked as secure in production |
| 275 | +3. **CSRF Protection**: Blazor Server provides built-in anti-forgery protection |
| 276 | +4. **Session Timeout**: 24-hour fixed expiration prevents indefinite sessions |
| 277 | +5. **Minimal Scopes**: Only request `read:org` scope (least privilege) |
| 278 | +6. **Secret Management**: Use .NET Secret Manager for local dev, Azure Key Vault for production |
| 279 | + |
| 280 | +## Dependencies |
| 281 | + |
| 282 | +- Existing `IGitHubService.IsUserMemberOfTeamAsync()` method |
| 283 | +- Existing `IConfigurationService.GetConfigAsync()` method |
| 284 | +- GitHub OAuth App must be created and configured with correct callback URL |
| 285 | + |
| 286 | +### To-dos |
| 287 | + |
| 288 | +- [ ] Add authentication NuGet packages and configure user secrets |
| 289 | +- [ ] Configure authentication middleware in Program.cs with Cookie and GitHub OAuth |
| 290 | +- [ ] Create IAuthorizationService and implementation for team-based authorization |
| 291 | +- [ ] Create Login.razor page with GitHub OAuth button |
| 292 | +- [ ] Create Logout.razor page to handle sign-out |
| 293 | +- [ ] Create AccessDenied.razor page with clear messaging and instructions |
| 294 | +- [ ] Create Onboarding.razor wizard for first-time setup with config template |
| 295 | +- [ ] Update App.razor with CascadingAuthenticationState and AuthorizeRouteView |
| 296 | +- [ ] Add authorization logic to Index.razor with config check and team verification |
| 297 | +- [ ] Update MainLayout.razor with user info display and logout button |
| 298 | +- [ ] Add authentication namespaces to _Imports.razor |
| 299 | +- [ ] Test complete authentication flow including OAuth, authorization, and error scenarios |
| 300 | +- [ ] Update README.md, create docs/authentication.md, and update CHANGELOG.md |
0 commit comments