Безопасность OAuth для веб-сайта с API и только внешними провайдерами
У меня есть сайт.Net core 3.2 с RESTful API на одном сервере и клиентский веб-сайт на другом сервере. Пользователи проходят аутентификацию в клиентском приложении только через внешних поставщиков, таких как Facebook, Google или Microsoft. У нас также есть Identity Server 4.0, который мы будем использовать, но он будет действовать как другой внешний провайдер.
Проблема заключается в том, что после аутентификации пользователя на клиенте и определения предоставленных им ролей / требований, как мы можем запросить конкретный ресурс из API? Клиентское веб-приложение знает о пользователе и знает, что пользователь тот, кем они себя называют, а клиентское приложение знает, что они могут делать. Как мы можем безопасно передать эту информацию в API?
Я рассматривал client_credentials между API и веб-сайтом, но, похоже, это для ситуаций, когда нет пользователя, например служб или демонов.
Я не хочу, чтобы API знал или заботился о пользователях, просто о том, что они аутентифицированы и каковы их требования.
2 ответа
Чтобы реализовать аутентификацию в одностраничном приложении, вам необходимо использовать код авторизации с потоком PKCE OAuth2. Это позволяет вам не хранить секреты в своем СПА.
Пожалуйста, не используйте
Implicit
flow, поскольку он устарел из соображений безопасности.
Когда вы отправляете свой токен от клиента на правильно настроенный.NET Core API, вы должны иметь возможность читать
User
свойство контроллера для идентификационной информации.
Если вы правильно настроите API, запрос достигнет контроллера только в том случае, если токен доступа действителен.
Ответ, который я искал, был JWT Tokens: на клиенте, прежде чем он отправит токен-носитель:
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
var accessToken = await GetAccessTokenAsync();
if (!string.IsNullOrWhiteSpace(accessToken))
{
request.SetBearerToken(accessToken);
}
return await base.SendAsync(request, cancellationToken);
}
public async Task<string> GetAccessTokenAsync()
{
var longKey = "FA485BA5-76C3-4FF5-8A33-E3693CA97002";
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(longKey));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
var claims = new List<Claim> {
new Claim("sub", _httpContextAccessor.HttpContext.User.GetUserId())
};
claims.AddRange(_httpContextAccessor.HttpContext.User.Claims);
var token =new JwtSecurityToken(
issuer: "https://localhost:44389",
audience: "https://localhost:44366",
claims: claims.ToArray(),
expires: DateTime.Now.AddMinutes(30),
signingCredentials: credentials
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
И на сервере API
var longKey = "FA485BA5-76C3-4FF5-8A33-E3693CA97002";
services.AddAuthentication(x=> {
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.SaveToken = true;
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
//ValidateLifetime = true,
ValidateIssuerSigningKey = true,
//ValidIssuer = "https://localhost:44366",
//ValidAudience = "https://localhost:44366",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(longKey)),
//ClockSkew = TimeSpan.Zero
};
});