JED
JED

Reputation: 1694

Cannot get current user or authorize

Working on an Angular 2/ASP.NET Core 2.1 app. Upon logging in, I can console.log the JWT token from the front-end, but when I try to access a controller (AccountsController.cs) with an [Authorize] attribute, I get a 401 error. If I remove the attribute, and I try to get the current user using var currentUserId = int.Parse(User.FindFirst(ClaimTypes.NameIdentifier).Value); I get sent immediately back to the front-end and receive an error message via the resolver.

It seems that the current user data is not being sent to the back-end with the request. Or it's not being stored? Or maybe I'm not accessing it correctly?

account-list.resolver.ts

import { Resolve, Router, ActivatedRouteSnapshot } from '@angular/router';
import { Account } from '../../_models/account';
import { Injectable } from '@angular/core';
import { AccountService } from '../../_services/account.service';
import { AlertifyService } from '../../_services/alertify.service';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/catch';

@Injectable()
export class AccountListResolver implements Resolve<Account[]> {
  pageSize = 5;
  pageNumber = 1;

  constructor(private accountService: AccountService,
    private router: Router,
    private alertify: AlertifyService) {
  }

  resolve(route: ActivatedRouteSnapshot): Observable<Account[]> {
    return this.accountService.getAccounts(this.pageNumber, this.pageSize).catch(error => {
      this.alertify.error('Problem retrieving data');
      this.router.navigate(['/dashboard']);
      return Observable.of(null);
    });
  }
}


account.service.ts

import { Injectable } from '@angular/core';
import { environment } from '../../environments/environment';
import { Account } from '../_models/account';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';
import { PaginatedResult } from '../_models/pagination';
import { HttpClient, HttpParams } from '@angular/common/http';

@Injectable()
export class AccountService {
  baseUrl = environment.apiUrl;

  constructor(private authHttp: HttpClient) { }

  getAccounts(page?, itemsPerPage?, accountParams?: any) {
    const paginatedResult: PaginatedResult<Account[]> = new PaginatedResult<Account[]>();
    let params = new HttpParams();

    if (page != null && itemsPerPage != null) {
      params = params.append('pageNumber', page);
      params = params.append('pageSize', itemsPerPage);
    }

    // if (accountParams != null) {
    //   params = params.append('paramName', accountParams.paramName);
    // }

    return this.authHttp
      .get<Account[]>(this.baseUrl + 'accounts', { observe: 'response', params })
      .map(response => {
        paginatedResult.result = response.body;
        if (response.headers.get('Pagination') != null) {
          paginatedResult.pagination = JSON.parse(response.headers.get('Pagination'));
        }

        return paginatedResult;
      });
  }
}


AccountsController

[Authorize]
[Route("api/[controller]")]
public class AccountsController : Controller
{
    private readonly IBaseRepository _repo;
    private readonly IMapper _mapper;

    public AccountsController(IBaseRepository repo, IMapper mapper)
    {
        _mapper = mapper;
        _repo = repo;
    }

    [HttpGet]
    public async Task<IActionResult> GetAccounts([FromQuery] AccountParams accountParams)
    {
        var currentUserId = int.Parse(User.FindFirst(ClaimTypes.NameIdentifier).Value);
        //^^^WHERE IT BREAKS WHEN AUTHORIZATION ATTRIBUTE IS REMOVED

        //code to generate list of accounts to return
        accountParams.UserId = currentUserId;

        var accounts = await _repo.GetAccounts(accountParams);

        var accountsToReturn = _mapper.Map<IEnumerable<AccountForListDto>>(accounts);

        Response.AddPagination(accounts.CurrentPage, accounts.PageSize, accounts.TotalCount, accounts.TotalPages);

        return Ok(accountsToReturn);
    }
}


**EDIT**

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    var key = Encoding.ASCII.GetBytes(Configuration.GetSection("AppSettings:Token").Value);

    services.AddDbContext<DataContext>(x => x
        .UseSqlServer(Configuration.GetConnectionString("DefaultConnection"), b =>
            b.MigrationsAssembly(("MyApp.App")))
        .ConfigureWarnings(warnings => warnings.Ignore(CoreEventId.IncludeIgnoredWarning)));

    services.AddMvc()
        .AddJsonOptions(opt =>
        {
            opt.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
        })
        .SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

    services.AddSpaStaticFiles(configuration => { configuration.RootPath = "ClientApp/dist"; });

    services.AddTransient<Seed>();

    services.AddCors();

    services.AddAutoMapper();

    services.AddScoped<IAuthRepository, AuthRepository>();

    services.AddScoped<IBaseRepository, BaseRepository>();

    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(options =>
        {
            options.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = new SymmetricSecurityKey(key),
                ValidateIssuer = false,
                ValidateAudience = false
            };
        });

    services.AddScoped<LogUserActivity>();
}

public void ConfigureDevelopmentServices(IServiceCollection services)
{
    var key = Encoding.ASCII.GetBytes(Configuration.GetSection("AppSettings:Token").Value);

    services.AddDbContext<DataContext>(x => x
        .UseSqlServer(Configuration.GetConnectionString("DefaultConnection"), b =>
            b.MigrationsAssembly(("MyApp.App")))
        .ConfigureWarnings(warnings => warnings.Ignore(CoreEventId.IncludeIgnoredWarning)));

    services.AddMvc()
        .AddJsonOptions(opt =>
        {
            opt.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
        })
        .SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

    services.AddSpaStaticFiles(configuration => { configuration.RootPath = "ClientApp/dist"; });

    services.AddTransient<Seed>();

    services.AddCors();

    services.AddAutoMapper();

    services.AddScoped<IAuthRepository, AuthRepository>();

    services.AddScoped<IBaseRepository, BaseRepository>();

    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(options =>
        {
            options.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = new SymmetricSecurityKey(key),
                ValidateIssuer = false,
                ValidateAudience = false
            };
        });

    services.AddScoped<LogUserActivity>();
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler(builder =>
        {
            builder.Run(async context =>
            {
                context.Response.StatusCode = (int) HttpStatusCode.InternalServerError;

                var error = context.Features.Get<IExceptionHandlerFeature>();
                if (error != null)
                {
                    context.Response.AddApplicationError(error.Error.Message);
                    await context.Response.WriteAsync(error.Error.Message);
                }
            });
        });
    }

    app.ConfigureSwagger(Assembly.GetExecutingAssembly());

    app.UseCors(x => x.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin().AllowCredentials());
    app.UseDefaultFiles();
    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseSpaStaticFiles();

    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller}/{action=Index}/{id?}");
    });

    app.UseSpa(spa =>
    {
        // To learn more about options for serving an Angular SPA from ASP.NET Core,
        // see https://go.microsoft.com/fwlink/?linkid=864501

        spa.Options.SourcePath = "ClientApp";

        if (env.IsDevelopment())
        {
            spa.UseProxyToSpaDevelopmentServer("http://localhost:4200");
            //spa.UseAngularCliServer(npmScript: "start");
        }
    });
}

Upvotes: 1

Views: 775

Answers (1)

blowdart
blowdart

Reputation: 56500

Did you add the JWT authorization handler?

In your startup.cs

  1. Is there app.UseAuthentication() in the Configure method?
  2. Is it before app.UseMvc()?
  3. Is there app.AddAuthentication() in your ConfigureServices method?
  4. Is it before app.AddMvc()?
  5. Is there a call to AddJwtBearer() in your ConfigureServices method hanging off the call to AddAuthentication()?
  6. Do you have the right keys in the options for the JwtBearer service?
  7. If JwtBearer isn't your only authentication mechanism (for example you also added Identity) are you specifying the scheme name for bearer in your Authorize attribute?

From your config it looks like you're missing app.UseAuthentication() in the configure method.

So you need to put it before app.UseMvc() like so;

app.UseSpaStaticFiles();
app.UseAuthentication()
app.UseMvc(routes =>
{
    routes.MapRoute(
        name: "default",
        template: "{controller}/{action=Index}/{id?}");
});

Upvotes: 2

Related Questions