Skip to main content

Authentication

AvantiPoint Packages provides flexible authentication to secure your NuGet feed. You control who can access packages and who can publish them.

Understanding NuGet Authentication

NuGet clients support two authentication methods:

  1. Basic Authentication - Used when consuming packages (downloading). The client sends a username and password/token.
  2. API Key Authentication - Used when publishing packages. The client sends an API key via the X-NuGet-ApiKey header.

AvantiPoint Packages uses a dual-role model:

  • Package Consumer - Can browse and download packages
  • Package Publisher - Can upload new packages and symbols

Implementing Authentication

To add authentication, implement the IPackageAuthenticationService interface and register it in your dependency injection container.

IPackageAuthenticationService Interface

public interface IPackageAuthenticationService
{
// Called when publishing packages (API key authentication)
Task<NuGetAuthenticationResult> AuthenticateAsync(string apiKey, CancellationToken cancellationToken);

// Called when consuming packages (basic authentication)
Task<NuGetAuthenticationResult> AuthenticateAsync(string username, string token, CancellationToken cancellationToken);
}

Basic Implementation

Here's a simple example that checks against hardcoded credentials:

using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using AvantiPoint.Packages.Core;

public class SimpleAuthenticationService : IPackageAuthenticationService
{
public Task<NuGetAuthenticationResult> AuthenticateAsync(string apiKey, CancellationToken cancellationToken)
{
// Validate API key for publishing
if (apiKey == "your-secret-api-key")
{
var identity = new ClaimsIdentity("NuGetAuth");
identity.AddClaim(new Claim(ClaimTypes.Name, "Publisher"));

return Task.FromResult(
NuGetAuthenticationResult.Success(new ClaimsPrincipal(identity))
);
}

return Task.FromResult(
NuGetAuthenticationResult.Fail("Invalid API key", "My Feed")
);
}

public Task<NuGetAuthenticationResult> AuthenticateAsync(string username, string token, CancellationToken cancellationToken)
{
// Validate username/token for consuming
if (username == "consumer@example.com" && token == "consumer-token")
{
var identity = new ClaimsIdentity("NuGetAuth");
identity.AddClaim(new Claim(ClaimTypes.Name, username));
identity.AddClaim(new Claim(ClaimTypes.Email, username));

return Task.FromResult(
NuGetAuthenticationResult.Success(new ClaimsPrincipal(identity))
);
}

return Task.FromResult(
NuGetAuthenticationResult.Fail("Invalid credentials", "My Feed")
);
}
}

Database-Backed Authentication

For production use, you'll want to validate credentials against a database:

using System.Linq;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using AvantiPoint.Packages.Core;
using Microsoft.EntityFrameworkCore;

public class DatabaseAuthenticationService : IPackageAuthenticationService
{
private readonly MyDbContext _db;

public DatabaseAuthenticationService(MyDbContext db)
{
_db = db;
}

public async Task<NuGetAuthenticationResult> AuthenticateAsync(string apiKey, CancellationToken cancellationToken)
{
var token = await _db.ApiTokens
.Include(x => x.User)
.ThenInclude(x => x.Permissions)
.FirstOrDefaultAsync(x => x.Token == apiKey && !x.IsRevoked, cancellationToken);

if (token is null || token.ExpiresAt < DateTime.UtcNow)
{
return NuGetAuthenticationResult.Fail("Invalid or expired API key", "My Feed");
}

// Check if user has publisher permission
if (!token.User.Permissions.Any(x => x.Name == "PackagePublisher"))
{
return NuGetAuthenticationResult.Fail("User is not authorized to publish packages", "My Feed");
}

var identity = new ClaimsIdentity("NuGetAuth");
identity.AddClaim(new Claim(ClaimTypes.Name, token.User.Name));
identity.AddClaim(new Claim(ClaimTypes.Email, token.User.Email));

return NuGetAuthenticationResult.Success(new ClaimsPrincipal(identity));
}

public async Task<NuGetAuthenticationResult> AuthenticateAsync(string username, string token, CancellationToken cancellationToken)
{
var apiToken = await _db.ApiTokens
.Include(x => x.User)
.ThenInclude(x => x.Permissions)
.FirstOrDefaultAsync(x => x.Token == token && x.User.Email == username && !x.IsRevoked, cancellationToken);

if (apiToken is null || apiToken.ExpiresAt < DateTime.UtcNow)
{
return NuGetAuthenticationResult.Fail("Invalid or expired credentials", "My Feed");
}

// Check if user has consumer permission
if (!apiToken.User.Permissions.Any(x => x.Name == "PackageConsumer"))
{
return NuGetAuthenticationResult.Fail("User is not authorized to access packages", "My Feed");
}

var identity = new ClaimsIdentity("NuGetAuth");
identity.AddClaim(new Claim(ClaimTypes.Name, apiToken.User.Name));
identity.AddClaim(new Claim(ClaimTypes.Email, apiToken.User.Email));

return NuGetAuthenticationResult.Success(new ClaimsPrincipal(identity));
}
}

Registering Your Authentication Service

In your Program.cs:

var builder = WebApplication.CreateBuilder(args);

// Register your authentication service
builder.Services.AddScoped<IPackageAuthenticationService, DatabaseAuthenticationService>();

// Configure the package API
builder.Services.AddNuGetPackageApi(options =>
{
options.AddFileStorage();
options.AddSqliteDatabase("Sqlite");
});

var app = builder.Build();
// ... rest of configuration

Using the Feed

For Consumers

Add the feed with credentials:

# Configure the source
dotnet nuget add source https://my-feed.example.com/v3/index.json --name MyFeed

# Add credentials
dotnet nuget update source MyFeed --username consumer@example.com --password consumer-token

Or use nuget.config:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="MyFeed" value="https://my-feed.example.com/v3/index.json" />
</packageSources>
<packageSourceCredentials>
<MyFeed>
<add key="Username" value="consumer@example.com" />
<add key="ClearTextPassword" value="consumer-token" />
</MyFeed>
</packageSourceCredentials>
</configuration>

For Publishers

Push packages with your API key:

dotnet nuget push MyPackage.1.0.0.nupkg --source MyFeed --api-key your-secret-api-key

Important Notes

  • AvantiPoint Packages does not use ASP.NET Core's built-in authentication. The ClaimsPrincipal you provide is only for your own use (e.g., in callbacks).
  • Always use HTTPS in production to protect credentials in transit.
  • Consider implementing token expiration and rotation for better security.
  • Store API keys hashed, never in plain text.

See Also