| name | security |
| description | Guia para segurança — autenticação, autorização, secrets management, rate limiting, CORS, security headers e proteção de APIs. Use quando a tarefa envolver segurança, autenticação, JWT, secrets, proteção ou vulnerabilidades. |
Security — Autenticação, Autorização e Proteção
Quando Usar Esta Skill
Use quando a tarefa envolver:
- Autenticação, autorização, JWT
- Secrets management, variáveis de ambiente
- Rate limiting, CORS, security headers
- Proteção de APIs, vulnerabilidades, sanitização
- Palavras-chave: "segurança", "autenticação", "JWT", "secrets", "proteção", "vulnerabilidade"
1. Autenticação JWT
Instalação
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
Configuração
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
var jwtSettings = builder.Configuration.GetSection("JwtSettings");
var secretKey = jwtSettings["SecretKey"] ?? throw new InvalidOperationException("JWT SecretKey not configured");
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = jwtSettings["Issuer"],
ValidAudience = jwtSettings["Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey)),
ClockSkew = TimeSpan.Zero
};
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<Program>>();
logger.LogWarning("Authentication failed: {Error}", context.Exception.Message);
return Task.CompletedTask;
},
OnTokenValidated = context =>
{
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<Program>>();
var userId = context.Principal?.FindFirst("sub")?.Value;
logger.LogInformation("Token validated for user {UserId}", userId);
return Task.CompletedTask;
}
};
});
builder.Services.AddAuthorization();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
appsettings.json (Placeholders)
{
"JwtSettings": {
"SecretKey": "",
"Issuer": "",
"Audience": "",
"ExpirationMinutes": 60
}
}
User Secrets (Dev)
dotnet user-secrets set "JwtSettings:SecretKey" "your-256-bit-secret-key-min-32-chars"
dotnet user-secrets set "JwtSettings:Issuer" "https://your-domain.com"
dotnet user-secrets set "JwtSettings:Audience" "https://your-api.com"
Geração de Token
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.IdentityModel.Tokens;
public class TokenService(IOptions<JwtSettings> jwtSettings) : ITokenService
{
private readonly JwtSettings _settings = jwtSettings.Value;
public string GenerateToken(User user)
{
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, user.Id.ToString()),
new Claim(JwtRegisteredClaimNames.Email, user.Email),
new Claim(JwtRegisteredClaimNames.Name, user.Name),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(ClaimTypes.Role, user.Role.ToString()),
new Claim("tenant_id", user.TenantId.ToString())
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_settings.SecretKey));
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: _settings.Issuer,
audience: _settings.Audience,
claims: claims,
expires: DateTime.UtcNow.AddMinutes(_settings.ExpirationMinutes),
signingCredentials: credentials
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
public ClaimsPrincipal? ValidateToken(string token)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.UTF8.GetBytes(_settings.SecretKey);
try
{
var principal = tokenHandler.ValidateToken(token, new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = true,
ValidIssuer = _settings.Issuer,
ValidateAudience = true,
ValidAudience = _settings.Audience,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
}, out _);
return principal;
}
catch
{
return null;
}
}
}
public class JwtSettings
{
public required string SecretKey { get; init; }
public required string Issuer { get; init; }
public required string Audience { get; init; }
public int ExpirationMinutes { get; init; } = 60;
}
Uso nos Controllers
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
[HttpGet("me")]
[Authorize]
public IActionResult GetCurrentUser()
{
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
var email = User.FindFirst(ClaimTypes.Email)?.Value;
var roles = User.FindAll(ClaimTypes.Role).Select(c => c.Value);
return Ok(new { UserId = userId, Email = email, Roles = roles });
}
[HttpGet("admin")]
[Authorize(Roles = "Admin")]
public IActionResult AdminOnly()
{
return Ok("Admin access granted");
}
}
2. Autorização Baseada em Políticas
Políticas Customizadas
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin"));
options.AddPolicy("PremiumUser", policy => policy.RequireClaim("subscription", "premium"));
options.AddPolicy("MinimumAge", policy => policy.Requirements.Add(new MinimumAgeRequirement(18)));
options.AddPolicy("AdminOrManager", policy =>
policy.RequireAssertion(context =>
context.User.IsInRole("Admin") || context.User.IsInRole("Manager")));
});
public class MinimumAgeRequirement(int minimumAge) : IAuthorizationRequirement
{
public int MinimumAge { get; } = minimumAge;
}
public class MinimumAgeHandler(ILogger<MinimumAgeHandler> logger) : AuthorizationHandler<MinimumAgeRequirement>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
MinimumAgeRequirement requirement)
{
var birthDateClaim = context.User.FindFirst("birthdate");
if (birthDateClaim == null)
{
logger.LogWarning("Birth date claim not found");
return Task.CompletedTask;
}
if (DateTime.TryParse(birthDateClaim.Value, out var birthDate))
{
var age = DateTime.Today.Year - birthDate.Year;
if (age >= requirement.MinimumAge)
{
context.Succeed(requirement);
}
}
return Task.CompletedTask;
}
}
builder.Services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();
Uso
[HttpGet("adult-content")]
[Authorize(Policy = "MinimumAge")]
public IActionResult GetAdultContent()
{
return Ok("Access granted");
}
3. Secrets Management
User Secrets (Desenvolvimento)
dotnet user-secrets init
dotnet user-secrets set "ConnectionStrings:DefaultConnection" "Server=localhost;..."
dotnet user-secrets set "JwtSettings:SecretKey" "your-secret-key"
dotnet user-secrets set "ExternalApi:ApiKey" "your-api-key"
dotnet user-secrets list
dotnet user-secrets remove "JwtSettings:SecretKey"
dotnet user-secrets clear
Variáveis de Ambiente (Produção)
export ConnectionStrings__DefaultConnection="Server=prod;..."
export JwtSettings__SecretKey="prod-secret-key"
$env:ConnectionStrings__DefaultConnection="Server=prod;..."
$env:JwtSettings__SecretKey="prod-secret-key"
docker run -e ConnectionStrings__DefaultConnection="..." -e JwtSettings__SecretKey="..." myapp
Azure Key Vault
dotnet add package Azure.Extensions.AspNetCore.Configuration.Secrets
dotnet add package Azure.Identity
using Azure.Identity;
var builder = WebApplication.CreateBuilder(args);
if (builder.Environment.IsProduction())
{
var keyVaultEndpoint = builder.Configuration["KeyVaultEndpoint"];
if (!string.IsNullOrEmpty(keyVaultEndpoint))
{
builder.Configuration.AddAzureKeyVault(
new Uri(keyVaultEndpoint),
new DefaultAzureCredential());
}
}
AWS Secrets Manager
dotnet add package AWSSDK.SecretsManager
using Amazon.SecretsManager;
using Amazon.SecretsManager.Model;
public class AwsSecretsService
{
private readonly IAmazonSecretsManager _client;
public AwsSecretsService()
{
_client = new AmazonSecretsManagerClient();
}
public async Task<string> GetSecretAsync(string secretName)
{
var request = new GetSecretValueRequest { SecretId = secretName };
var response = await _client.GetSecretValueAsync(request);
return response.SecretString;
}
}
4. Rate Limiting (.NET 7+)
Configuração
dotnet add package Microsoft.AspNetCore.RateLimiting
using System.Threading.RateLimiting;
builder.Services.AddRateLimiter(options =>
{
options.AddFixedWindowLimiter("fixed", limiterOptions =>
{
limiterOptions.PermitLimit = 100;
limiterOptions.Window = TimeSpan.FromMinutes(1);
limiterOptions.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
limiterOptions.QueueLimit = 10;
});
options.AddSlidingWindowLimiter("sliding", limiterOptions =>
{
limiterOptions.PermitLimit = 100;
limiterOptions.Window = TimeSpan.FromMinutes(1);
limiterOptions.SegmentsPerWindow = 6;
limiterOptions.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
limiterOptions.QueueLimit = 10;
});
options.AddTokenBucketLimiter("token", limiterOptions =>
{
limiterOptions.TokenLimit = 100;
limiterOptions.ReplenishmentPeriod = TimeSpan.FromMinutes(1);
limiterOptions.TokensPerPeriod = 100;
limiterOptions.QueueLimit = 10;
});
options.AddConcurrencyLimiter("concurrency", limiterOptions =>
{
limiterOptions.PermitLimit = 10;
limiterOptions.QueueLimit = 20;
});
options.AddPolicy("perIp", context =>
{
var clientIp = context.Connection.RemoteIpAddress?.ToString() ?? "unknown";
return RateLimitPartition.GetFixedWindowLimiter(clientIp, _ => new FixedWindowRateLimiterOptions
{
PermitLimit = 50,
Window = TimeSpan.FromMinutes(1)
});
});
options.OnRejected = async (context, token) =>
{
context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;
if (context.Lease.TryGetMetadata(MetadataName.RetryAfter, out var retryAfter))
{
context.HttpContext.Response.Headers.RetryAfter = retryAfter.TotalSeconds.ToString();
}
await context.HttpContext.Response.WriteAsJsonAsync(new
{
Error = "Too many requests",
Message = "Rate limit exceeded. Please try again later."
}, cancellationToken: token);
};
});
var app = builder.Build();
app.UseRateLimiter();
Uso
[HttpGet]
[EnableRateLimiting("fixed")]
public IActionResult Get()
{
return Ok("Success");
}
[HttpPost]
[EnableRateLimiting("perIp")]
public IActionResult Create([FromBody] CreateInput input)
{
return Ok("Created");
}
[HttpGet("health")]
[DisableRateLimiting]
public IActionResult Health()
{
return Ok("Healthy");
}
5. CORS (Cross-Origin Resource Sharing)
builder.Services.AddCors(options =>
{
if (builder.Environment.IsDevelopment())
{
options.AddPolicy("Development", builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
});
}
options.AddPolicy("Production", builder =>
{
builder.WithOrigins(
"https://myapp.com",
"https://www.myapp.com")
.WithMethods("GET", "POST", "PUT", "DELETE")
.WithHeaders("Content-Type", "Authorization")
.AllowCredentials()
.SetPreflightMaxAge(TimeSpan.FromMinutes(10));
});
options.AddPolicy("PublicApi", builder =>
{
builder.AllowAnyOrigin()
.WithMethods("GET")
.WithHeaders("Content-Type");
});
});
var app = builder.Build();
app.UseCors(app.Environment.IsDevelopment() ? "Development" : "Production");
app.MapGet("/public", () => "Public data")
.RequireCors("PublicApi");
6. Security Headers
public class SecurityHeadersMiddleware(RequestDelegate next)
{
public async Task InvokeAsync(HttpContext context)
{
context.Response.Headers.Append("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
context.Response.Headers.Append("X-Frame-Options", "DENY");
context.Response.Headers.Append("X-Content-Type-Options", "nosniff");
context.Response.Headers.Append("Referrer-Policy", "strict-origin-when-cross-origin");
context.Response.Headers.Append("Content-Security-Policy",
"default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'");
context.Response.Headers.Append("Permissions-Policy",
"geolocation=(), microphone=(), camera=()");
context.Response.Headers.Remove("Server");
context.Response.Headers.Remove("X-Powered-By");
await next(context);
}
}
app.UseMiddleware<SecurityHeadersMiddleware>();
app.UseSecurityHeaders(policies => policies.AddDefaultSecurityHeaders());
7. Validação e Sanitização de Input
Evitar SQL Injection
public async Task<User?> GetUserByEmailUnsafe(string email)
{
var sql = $"SELECT * FROM Users WHERE Email = '{email}'";
return await _context.Users.FromSqlRaw(sql).FirstOrDefaultAsync();
}
public async Task<User?> GetUserByEmail(string email)
{
return await _context.Users
.FromSqlInterpolated($"SELECT * FROM Users WHERE Email = {email}")
.FirstOrDefaultAsync();
}
public async Task<User?> GetUserByEmail(string email)
{
return await _context.Users
.Where(u => u.Email == email)
.FirstOrDefaultAsync();
}
Evitar XSS (Cross-Site Scripting)
using System.Text.RegularExpressions;
public class SanitizationService
{
public string SanitizeHtml(string input)
{
if (string.IsNullOrEmpty(input))
return input;
var noHtml = Regex.Replace(input, "<.*?>", string.Empty);
var sanitized = noHtml
.Replace("<", "<")
.Replace(">", ">")
.Replace("\"", """)
.Replace("'", "'")
.Replace("/", "/");
return sanitized;
}
public string SanitizeFileName(string fileName)
{
var invalidChars = Path.GetInvalidFileNameChars();
return string.Join("_", fileName.Split(invalidChars));
}
}
Evitar Path Traversal
public class FileService
{
private readonly string _basePath = "/app/uploads";
public async Task<byte[]> GetFileAsync(string fileName)
{
var sanitizedFileName = Path.GetFileName(fileName);
var fullPath = Path.Combine(_basePath, sanitizedFileName);
var fullPathResolved = Path.GetFullPath(fullPath);
var basePathResolved = Path.GetFullPath(_basePath);
if (!fullPathResolved.StartsWith(basePathResolved))
{
throw new UnauthorizedAccessException("Invalid file path");
}
return await File.ReadAllBytesAsync(fullPath);
}
}
8. Password Hashing
using System.Security.Cryptography;
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
public class PasswordService
{
public string HashPassword(string password)
{
byte[] salt = RandomNumberGenerator.GetBytes(128 / 8);
string hashed = Convert.ToBase64String(KeyDerivation.Pbkdf2(
password: password,
salt: salt,
prf: KeyDerivationPrf.HMACSHA256,
iterationCount: 100000,
numBytesRequested: 256 / 8));
return $"{Convert.ToBase64String(salt)}.{hashed}";
}
public bool VerifyPassword(string password, string hashedPassword)
{
var parts = hashedPassword.Split('.');
if (parts.Length != 2)
return false;
var salt = Convert.FromBase64String(parts[0]);
var hash = parts[1];
var testHash = Convert.ToBase64String(KeyDerivation.Pbkdf2(
password: password,
salt: salt,
prf: KeyDerivationPrf.HMACSHA256,
iterationCount: 100000,
numBytesRequested: 256 / 8));
return hash == testHash;
}
}
using Microsoft.AspNetCore.Identity;
public class PasswordService2
{
private readonly IPasswordHasher<User> _passwordHasher = new PasswordHasher<User>();
public string HashPassword(User user, string password)
{
return _passwordHasher.HashPassword(user, password);
}
public bool VerifyPassword(User user, string hashedPassword, string providedPassword)
{
var result = _passwordHasher.VerifyHashedPassword(user, hashedPassword, providedPassword);
return result == PasswordVerificationResult.Success;
}
}
9. Checklist de Segurança
Autenticação/Autorização
Secrets
Rate Limiting
CORS
Headers
Validação
Passwords
HTTPS
Resumo
Esta skill cobre:
- ✅ Autenticação JWT (geração, validação)
- ✅ Autorização baseada em políticas
- ✅ Secrets management (User Secrets, Key Vault, variáveis de ambiente)
- ✅ Rate limiting (.NET 7+)
- ✅ CORS (configuração restritiva)
- ✅ Security headers (HSTS, CSP, X-Frame-Options)
- ✅ Validação e sanitização (SQL injection, XSS, path traversal)
- ✅ Password hashing (PBKDF2, Identity PasswordHasher)
- ✅ Checklist de segurança
Sempre que trabalhar com segurança, autenticação, secrets ou proteção de APIs, use esta skill como referência.