Авторизация JWT работает с Swagger, а не с AngularC#

Место общения программистов C#
Ответить
Anonymous
 Авторизация JWT работает с Swagger, а не с Angular

Сообщение Anonymous »

У меня есть клиент Angular, у которого есть служба для вызова API по маршруту «https://localhost:7183/api/url/create».

Код: Выделить всё

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Router } from '@angular/router';
import { Observable } from 'rxjs';

@Injectable({
providedIn: 'root',
})
export class UrlService {
private apiCreateUrlUrl = 'https://localhost:7183/api/url/create';

constructor(private http: HttpClient) {}

create(urlData: { originalUrl: string }): Observable {
console.log(localStorage.getItem('authToken')) // console log prints auth token on screenshot below
const headers = new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': localStorage.getItem('authToken') ?? ''
});

return this.http.post(this.apiCreateUrlUrl, urlData, { headers });
}
}
И API ASP.NET Core MVC:

Код: Выделить всё

[ApiController]
[Route("api/url")]
public class UrlController : ControllerBase
{
private readonly IUrlService _urlService;
private readonly UserManager _userManager;

public UrlController(IUrlService shortUrlService, UserManager userManager)
{
_urlService = shortUrlService;
_userManager = userManager;
}

[Authorize]
[HttpPost("create")]
public async Task CreateShortUrl([FromBody] UrlDTO url)
{
var dddfd = Request;
var shortCode = Guid.NewGuid().ToString()[..6];

var a = await _userManager.GetUserAsync(User);
var newUrl = new UrlEntity
{
OriginalUrl = url.OriginalUrl,
ShortCode = $"https://short.ly/{shortCode}",
UserId = User.FindFirstValue(ClaimTypes.NameIdentifier),
};

await _urlService.CreateUrlAsync(newUrl);
return CreatedAtAction(nameof(GetShortUrl), url);
}
}
Когда я выполняю вызов API «https://localhost:7183/api/url/create» через Swagger, все работает нормально. Однако, когда я делаю то же самое на стороне клиента, я получаю следующий вывод:

Изображение

Я не могу понять, почему я получаю ответ 302 от сообщения, если JWT был представлен так же, как и в Swagger.
Дополнительная информация о Ядро ASP.NET MVC:

Мой секретный ключ: "f0cb5f06980e5be436a66caf5b74c1fe8792f51c7251885bcab19d8af06cf73b"
Program.cs:

Код: Выделить всё

using System.Security.Cryptography;
using System.Text;
using back.Data;
using back.Data.Models;
using back.Services.Authentication;
using back.Services.ShortUrl;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;

var CORS_POLICY = "MY_CORS";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddScoped();
builder.Services.AddScoped();

builder.Services.AddCors(options =>
{
options.AddPolicy(name: CORS_POLICY, builder =>
{
builder
.WithOrigins("http://localhost:4200")
.AllowAnyHeader()
.AllowAnyMethod();
});
});

builder.Services.AddDbContext(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection")
?? throw new ArgumentNullException("DefaultConnection")));

builder.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 = "https://localhost:7183",
ValidAudience = "https://localhost:7183",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration.GetSection("Auth").GetValue("SecretKey")!))
};
});

builder.Services.AddAuthorization();

builder.Services
.AddIdentity(options =>
{
options.Password.RequireDigit = false;
options.Password.RequiredLength = 8;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
options.Password.RequireLowercase = false;
options.Password.RequiredUniqueChars = 0;

options.Lockout.MaxFailedAccessAttempts = 5;
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
options.Lockout.AllowedForNewUsers = true;
})
.AddEntityFrameworkStores()
.AddDefaultTokenProviders();

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
c.AddSecurityDefinition("Bearer", new Microsoft.OpenApi.Models.OpenApiSecurityScheme
{
Description = "JWT Authorization header using the Bearer scheme (Example: 'Bearer 12345abcdef')",
Name = "Authorization",
In = Microsoft.OpenApi.Models.ParameterLocation.Header,
Type = Microsoft.OpenApi.Models.SecuritySchemeType.ApiKey,
Scheme = "Bearer"
});

c.AddSecurityRequirement(new Microsoft.OpenApi.Models.OpenApiSecurityRequirement
{
{
new Microsoft.OpenApi.Models.OpenApiSecurityScheme
{
Reference = new Microsoft.OpenApi.Models.OpenApiReference
{
Type = Microsoft.OpenApi.Models.ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
new string[] {}
}
});
});

// Add services to the container.
builder.Services.AddControllersWithViews();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days.  You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
else
{
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "inforce-tech v1");
c.RoutePrefix = "swagger"; // Use "swagger" as the base path for UI access
});
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseCors(CORS_POLICY);

app.UseAuthentication();
app.UseAuthorization();

app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}")
.RequireCors(CORS_POLICY);

app.Run();
Как я генерирую токен после входа в систему/регистрации:

Код: Выделить всё

public class AuthenticationService : IAuthenticationService
{
private readonly UserManager _userManager;
private readonly SignInManager _signInManager;
private readonly IConfiguration _configuration;

public AuthenticationService(UserManager userManager, SignInManager signInManager,
IConfiguration configuration)
{
_userManager = userManager;
_signInManager = signInManager;
_configuration = configuration;
}

// Log in user
public async Task LogInAsync(string username, string password)
{
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
{
throw new ArgumentException("Username and password are required.");
}

var user = await _userManager.FindByNameAsync(username) ?? throw new InvalidOperationException("User does not exist.");

var result = await _signInManager.PasswordSignInAsync(user, password, isPersistent: false, lockoutOnFailure: false);
if (!result.Succeeded)
{
throw new InvalidOperationException("Invalid login attempt.");
}

return result;
}

// Sign up user
public async Task SignUpAsync(UserEntity user, string password)
{
if (user == null || string.IsNullOrWhiteSpace(password))
{
throw new ArgumentException("User and password are required.");
}

var result = await _userManager.CreateAsync(user, password);
if (!result.Succeeded)
{
throw new InvalidOperationException(string.Join(", ", result.Errors.Select(e => e.Description)));
}

await _signInManager.SignInAsync(user, isPersistent: false);

return result;
}

public string GenerateJwtToken(string username)
{
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, username),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};

var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration.GetSection("Auth").GetValue("SecretKey")!));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

var token = new JwtSecurityToken(
issuer: "https://localhost:7183",
audience: "https://localhost:7183",
claims: claims,
expires: DateTime.Now.AddMinutes(30),
signingCredentials: creds
);

return new JwtSecurityTokenHandler().WriteToken(token);
}
}
В Swagger я использую кнопку «Авторизовать» и записываю в поле свой JWT, сгенерированный после входа в систему. И он проходит через атрибут [Authorize]. Потому что даже если я получу доступ к «https://localhost:7183//api/url/create» и удалю атрибут [Authorize], клиент вернет 200.

Изображение

Я также пытался добавить в заголовок "Авторизация" "Носитель" ключевое слово:

Код: Выделить всё

@Injectable({
providedIn: 'root',
})
export class UrlService {
private apiCreateUrlUrl = 'https://localhost:7183/api/url/create';

constructor(private http: HttpClient) {}

create(urlData: { originalUrl: string }): Observable {
console.log(localStorage.getItem('authToken'))
const headers = new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('authToken') ?? ''}`
});

return this.http.post(this.apiCreateUrlUrl, urlData, { headers });
}
}
Но ничего не работает.

Подробнее здесь: https://stackoverflow.com/questions/792 ... th-angular
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «C#»