JWT Authorization works with Swagger and not with Angular

Discussão em 'Angular' iniciado por Quikler, Dezembro 11, 2024.

  1. Quikler

    Quikler Guest

    I have an angular client which has a service to call api by route "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<any> {
    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 });
    }
    }


    And ASP.NET Core MVC API:

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

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

    [Authorize]
    [HttpPost("create")]
    public async Task<IActionResult> 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);
    }
    }


    When I executing a call to API "https://localhost:7183/api/url/create" through swagger everything works fine. However when I do the same on client side I'm getting this output:
    [​IMG]

    I cannot understand why I'm getting 302 response from post if JWT was presented the same as in swagger.

    Additional info about ASP.NET Core MVC:
    My secret key: "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<IAuthenticationService, AuthenticationService>();
    builder.Services.AddScoped<IUrlService, UrlService>();

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

    builder.Services.AddDbContext<AppDbContext>(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<string>("SecretKey")!))
    };
    });

    builder.Services.AddAuthorization();

    builder.Services
    .AddIdentity<UserEntity, IdentityRole>(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<AppDbContext>()
    .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();


    How I'm generation the token after login/signup:

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

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

    // Log in user
    public async Task<SignInResult> 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<IdentityResult> 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<string>("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);
    }
    }


    In swagger I use Authorize button and writing my JWT generated after login into field. And it passes though [Authorize] attribute. Because even if I access "https://localhost:7183//api/url/create" and remove [Authorize] attribute the client returns 200.
    [​IMG]

    I also tried to add into header "Authorization" the "Bearer" keyword:

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

    constructor(private http: HttpClient) {}

    create(urlData: { originalUrl: string }): Observable<any> {
    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 });
    }
    }


    But nothing works.

    Continue reading...

Compartilhe esta Página