-
Notifications
You must be signed in to change notification settings - Fork 1
Replace Microsoft Identity with JWT/Refresh Token authentication supporting Google OAuth with domain restrictions #41
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
Copilot
wants to merge
8
commits into
master
Choose a base branch
from
copilot/update-authentication-jwt-refresh-token
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
2da96d6
Initial plan
Copilot ca08152
Implement JWT/Refresh Token authentication with OAuth provider support
Copilot beaee77
Add token refresh support and update claim references
Copilot f729357
Refactor authentication to use minimal APIs instead of controllers
Copilot c1c9a0f
Split authentication models into separate record files and enhance pr…
Copilot 9b4de60
Add backward compatibility for legacy AzureAd configuration
Copilot 9bdc9ea
Consolidate migrations, remove unused fields, and add Google Workspac…
Copilot 97fc5c5
Address code review feedback: split config classes, add session track…
Copilot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,11 +1,90 @@ | ||
| # AvantiPoint Packages Templates | ||
|
|
||
| The following is a dotnet template for a basic NuGet Package feed using AvantiPoint Packages. This feed uses Azure Active Directory to authenticate users in the web. Authenticated users can then create and manage their own Auth Tokens for use with the package feed. By default only the first user has Package Publishing privileges. You can change this or implement more complex user management scenarios. | ||
| The following is a dotnet template for a basic NuGet Package feed using AvantiPoint Packages. This feed uses JWT-based authentication with OAuth providers (Microsoft or Google) to authenticate users in the web interface. Authenticated users can then create and manage their own Auth Tokens for use with the package feed. By default only the first user has Package Publishing privileges. You can change this or implement more complex user management scenarios. | ||
|
|
||
| In addition to this the NuGet Package Authentication, and Callback Handlers are pre-wired up, and come with an Email Service and basic html templates. This will send an email to your users to welcome them when they create their first token, along with any time they create or revoke a token, they have uploaded a package or symbols package, or download a package from a new IP Address. | ||
| The NuGet Package Authentication and Callback Handlers are pre-wired up, and come with an Email Service and basic html templates. This will send an email to your users to welcome them when they create their first token, along with any time they create or revoke a token, they have uploaded a package or symbols package, or download a package from a new IP Address. | ||
|
|
||
| ## Authentication | ||
|
|
||
| This template uses JWT (JSON Web Tokens) with refresh tokens for authentication. Users can sign in using either Microsoft Azure AD or Google OAuth. The authentication flow: | ||
|
|
||
| 1. User clicks "Sign in" and selects their provider (Microsoft or Google) | ||
| 2. User is redirected to the OAuth provider to authenticate | ||
| 3. After successful authentication, the user is redirected back with an authorization code | ||
| 4. The application exchanges the code for user information and generates JWT access and refresh tokens | ||
| 5. Tokens are stored locally and used for subsequent requests | ||
| 6. When the user logs out, refresh tokens are revoked and local state is cleared | ||
|
|
||
| ### Key Features | ||
|
|
||
| - **JWT Access Tokens**: Short-lived tokens (15 minutes by default) for API authentication | ||
| - **Refresh Tokens**: Long-lived tokens (7 days by default) for obtaining new access tokens | ||
| - **Local Logout**: Signing out only clears the local session and revokes tokens, without logging out from Microsoft or Google | ||
| - **Flexible Provider Choice**: Choose between Microsoft Azure AD or Google OAuth during template setup | ||
|
|
||
| ## Setup Instructions | ||
|
|
||
| You will need: | ||
|
|
||
| 1. Create a new Application in Azure Active Directory. Be sure to add Access and ID tokens after creating the application in the Azure Portal. | ||
| 2. Update the App Settings with your Tenant Id, Client Id and Domain. | ||
| 3. Update the Email Settings with the email address you want emails to send from along with the Send Grid API Key. | ||
| ### For Microsoft Azure AD: | ||
|
|
||
| 1. Create a new Application in Azure Active Directory | ||
| 2. Add a Web platform with redirect URI: `https://your-domain.com/api/authentication/callback/microsoft` | ||
| 3. Enable ID tokens in the Authentication settings | ||
| 4. Create a client secret | ||
| 5. Update the app settings with your Tenant ID, Client ID, and Client Secret | ||
|
|
||
| ### For Google OAuth: | ||
|
|
||
| 1. Create a new project in Google Cloud Console | ||
| 2. Enable the Google+ API | ||
| 3. Create OAuth 2.0 credentials (Web application type) | ||
| 4. Add authorized redirect URI: `https://your-domain.com/api/authentication/callback/google` | ||
| 5. Update the app settings with your Client ID and Client Secret | ||
|
|
||
| ### Additional Configuration: | ||
|
|
||
| 3. Generate a secure JWT secret key (minimum 32 characters) | ||
| 4. Update the Email Settings with the email address you want emails to send from along with the SendGrid API Key | ||
|
|
||
| ### Configuration Options | ||
|
|
||
| The application supports two configuration formats: | ||
|
|
||
| **New Format (Recommended)**: Use the `OAuth` section in appsettings.json: | ||
| ```json | ||
| "OAuth": { | ||
| "Provider": "Microsoft", | ||
| "Microsoft": { | ||
| "TenantId": "your-tenant-id", | ||
| "ClientId": "your-client-id", | ||
| "ClientSecret": "your-client-secret" | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| **Legacy Format (Backward Compatible)**: Use the `AzureAd` section (existing deployments): | ||
| ```json | ||
| "AzureAd": { | ||
| "TenantId": "your-tenant-id", | ||
| "ClientId": "your-client-id", | ||
| "ClientSecret": "your-client-secret" | ||
| } | ||
| ``` | ||
|
|
||
| Both formats are supported to maintain backward compatibility with existing deployments. | ||
|
|
||
| ## Template Parameters | ||
|
|
||
| When creating a new project from this template, you can specify: | ||
|
|
||
| - `--OAuthProvider`: Choose "Microsoft" or "Google" for authentication | ||
| - `--MSTenantId`: Microsoft Azure AD Tenant ID | ||
| - `--MSClientId`: Microsoft Azure AD Client ID | ||
| - `--MSClientSecret`: Microsoft Azure AD Client Secret | ||
| - `--GoogleClientId`: Google OAuth Client ID | ||
| - `--GoogleClientSecret`: Google OAuth Client Secret | ||
| - `--JwtSecret`: Secret key for signing JWT tokens (min 32 chars) | ||
| - `--EmailFromDomain`: Domain for sending emails from | ||
| - `--SendGridApiKey`: SendGrid API key for email delivery | ||
| - `--PostmarkApiKey`: Postmark API key (alternative to SendGrid) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
98 changes: 98 additions & 0 deletions
98
templates/NuGetFeedTemplate/Authentication/AuthenticationExtensions.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| using System.Text; | ||
| using Microsoft.AspNetCore.Authentication.JwtBearer; | ||
| using Microsoft.EntityFrameworkCore; | ||
| using Microsoft.IdentityModel.Tokens; | ||
| using NuGetFeedTemplate.Configuration; | ||
| using NuGetFeedTemplate.Data; | ||
| using System.Security.Claims; | ||
|
|
||
| namespace NuGetFeedTemplate.Authentication; | ||
|
|
||
| public static class AuthenticationExtensions | ||
| { | ||
| public static IServiceCollection AddJwtAuthentication( | ||
| this IServiceCollection services, | ||
| IConfiguration configuration) | ||
| { | ||
| // Configure JWT settings | ||
| var jwtSettings = new JwtSettings(); | ||
| configuration.GetSection("JwtSettings").Bind(jwtSettings); | ||
| services.AddSingleton(jwtSettings); | ||
|
|
||
| // Configure OAuth settings with backward compatibility | ||
| var oauthSettings = new OAuthSettings(); | ||
| configuration.GetSection("OAuth").Bind(oauthSettings); | ||
|
|
||
| // Support legacy AzureAd configuration for backward compatibility | ||
| var azureAdSection = configuration.GetSection("AzureAd"); | ||
| if (azureAdSection.Exists() && oauthSettings.Microsoft == null) | ||
| { | ||
| oauthSettings.Microsoft = new MicrosoftOAuthSettings(); | ||
| oauthSettings.Microsoft.TenantId = azureAdSection["TenantId"]; | ||
| oauthSettings.Microsoft.ClientId = azureAdSection["ClientId"]; | ||
| oauthSettings.Microsoft.ClientSecret = azureAdSection["ClientSecret"]; | ||
| oauthSettings.Microsoft.Instance = azureAdSection["Instance"] ?? "https://login.microsoftonline.com/"; | ||
| oauthSettings.Microsoft.CallbackPath = azureAdSection["CallbackPath"] ?? "/api/authentication/callback/microsoft"; | ||
| oauthSettings.Provider = "Microsoft"; | ||
| } | ||
|
|
||
| services.AddSingleton(oauthSettings); | ||
|
|
||
| // Add JWT Authentication | ||
| services.AddAuthentication(options => | ||
| { | ||
| options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; | ||
| options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; | ||
| }) | ||
| .AddJwtBearer(options => | ||
| { | ||
| options.TokenValidationParameters = new TokenValidationParameters | ||
| { | ||
| ValidateIssuer = true, | ||
| ValidateAudience = true, | ||
| ValidateLifetime = true, | ||
| ValidateIssuerSigningKey = true, | ||
| ValidIssuer = jwtSettings.Issuer, | ||
| ValidAudience = jwtSettings.Audience, | ||
| IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.Secret)) | ||
| }; | ||
|
|
||
| // Support token from cookie for browser requests | ||
| options.Events = new JwtBearerEvents | ||
| { | ||
| OnMessageReceived = context => | ||
| { | ||
| // Check for token in cookie first (for browser) | ||
| if (context.Request.Cookies.TryGetValue("access_token", out var token)) | ||
| { | ||
| context.Token = token; | ||
| } | ||
| return Task.CompletedTask; | ||
| }, | ||
| OnTokenValidated = async context => | ||
| { | ||
| var feedContext = context.HttpContext.RequestServices.GetRequiredService<FeedContext>(); | ||
| var email = context.Principal.FindFirstValue(ClaimTypes.Email); | ||
|
|
||
| if (!string.IsNullOrEmpty(email)) | ||
| { | ||
| var user = await feedContext.Users.FirstOrDefaultAsync(x => x.Email == email); | ||
|
|
||
| if (user != null && user.IsRevoked) | ||
| { | ||
| context.Fail("User access has been revoked."); | ||
| } | ||
| } | ||
| } | ||
| }; | ||
| }); | ||
|
|
||
| services.AddAuthorization(options => | ||
| { | ||
| // By default, all incoming requests will be authorized according to the default policy | ||
| options.FallbackPolicy = options.DefaultPolicy; | ||
| }); | ||
|
|
||
| return services; | ||
| } | ||
| } |
15 changes: 15 additions & 0 deletions
15
templates/NuGetFeedTemplate/Configuration/GoogleOAuthSettings.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| namespace NuGetFeedTemplate.Configuration; | ||
|
|
||
| public class GoogleOAuthSettings | ||
| { | ||
| public string ClientId { get; set; } | ||
| public string ClientSecret { get; set; } | ||
| public string CallbackPath { get; set; } = "/signin-google"; | ||
|
|
||
| /// <summary> | ||
| /// The Google Workspace domain (e.g., "example.com") to restrict authentication to. | ||
| /// Only users with email addresses from this domain will be allowed to authenticate. | ||
| /// This prevents personal Gmail accounts from accessing the feed. | ||
| /// </summary> | ||
| public string WorkspaceDomain { get; set; } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| namespace NuGetFeedTemplate.Configuration; | ||
|
|
||
| public class JwtSettings | ||
| { | ||
| public string Secret { get; set; } | ||
| public string Issuer { get; set; } | ||
| public string Audience { get; set; } | ||
| public int AccessTokenExpirationMinutes { get; set; } = 15; | ||
| public int RefreshTokenExpirationDays { get; set; } = 7; | ||
| } |
10 changes: 10 additions & 0 deletions
10
templates/NuGetFeedTemplate/Configuration/MicrosoftOAuthSettings.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| namespace NuGetFeedTemplate.Configuration; | ||
|
|
||
| public class MicrosoftOAuthSettings | ||
| { | ||
| public string TenantId { get; set; } | ||
| public string ClientId { get; set; } | ||
| public string ClientSecret { get; set; } | ||
| public string Instance { get; set; } = "https://login.microsoftonline.com/"; | ||
| public string CallbackPath { get; set; } = "/signin-microsoft"; | ||
| } |
12 changes: 12 additions & 0 deletions
12
templates/NuGetFeedTemplate/Configuration/OAuthSettings.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| namespace NuGetFeedTemplate.Configuration; | ||
|
|
||
| public class OAuthSettings | ||
| { | ||
| public string Provider { get; set; } = "Microsoft"; // "Microsoft" or "Google" | ||
|
|
||
| // Microsoft settings | ||
| public MicrosoftOAuthSettings Microsoft { get; set; } | ||
|
|
||
| // Google settings | ||
| public GoogleOAuthSettings Google { get; set; } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot how can we ensure that we generate a value if one is not present?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in commit 97fc5c5. Added two generated GUID parameters (
JwtSecretGeneratedandJwtSecretGenerated2) that are concatenated in appsettings to create a 64-character secret when JwtSecret parameter is not provided. The template replacesGeneratedJwtSecretGuid1GeneratedJwtSecretGuid2with the two generated GUIDs.