0
votes

I've been trying to develop a simple Web API with the JWT Bearer Authentication. So far I already have an application fully developed, and now I need a web API to provide communication with other technologies.

To begin my API I found this tutorial here that provides a simple example: https://medium.com/@renato.groffe/asp-net-core-2-0-autentica%C3%A7%C3%A3o-em-apis-utilizando-jwt-json-web-tokens-4b1871efd

The code is available here: https://github.com/renatogroffe/ASPNETCore2_JWT/tree/master/APIAlturas

I was able to test this project, it works fine with the JWT Bearer Authentication.

The problem began when I had to do some dependency injection in my controllers to retrieve data from my repository. My API was not resolving my dependencies, so I had to make some changes in my Startup.cs file.

Therefore, the only thing that is different now in my project from the example above is the Startup.cs file.

What happens here is that my API generates a token, and when I try to send it to my other controller (in the header) it returns: "Bearer error="invalid_token", error_description="The signature is invalid"

My guess is that something in my Startup.cs file is messing with my authentication.

Here is another thing that I noticed, I set my token configurations in the appsettings.json file. When I call my method that generates my token, those parameters are not set in my tokenConfigurations object. However, debugging the code, my startup.cs file receives the parameters. When I call the controller, these parameters now are null in this object.

    public object Post([FromBody]User usuario,[FromServices]SigningConfigurations signingConfigurations, [FromServices]TokenConfigurations tokenConfigurations){     ... my code        

}

here is my startup.cs file

using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Stratec.WebAPI;
using Stratec.Configuration;
using Stratec.Domain;
using Autofac;
using Hangfire;
using Microsoft.AspNetCore.Http;
using Stratec.Web;
using Hangfire.SqlServer;

namespace Stratec.WebAPI
{
    public class Startup
    {
        public IConfiguration Configuration { get; }

        public Startup(IHostingEnvironment env)
        {
            Configuration = new ConfigurationBuilder()
                .SetBasePath(env.ContentRootPath)
                .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
                // .AddXmlFile("appsettings.xml", optional: true, reloadOnChange: true)
                //.AddXmlFile($"appsettings.{env.EnvironmentName}.xml", optional: true)
                .AddEnvironmentVariables()
                .Build();

            Configuracao.Configuration = Configuration;
        }

        public IServiceProvider ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<UsersDAO>();

            services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

            var signingConfigurations = new SigningConfigurations();
            services.AddSingleton(signingConfigurations);

            var tokenConfigurations = new TokenConfigurations();
            new ConfigureFromConfigurationOptions<TokenConfigurations>(
                Configuration.GetSection("TokenConfigurations"))
                    .Configure(tokenConfigurations);
            services.AddSingleton(tokenConfigurations);

            services.AddAuthentication(authOptions =>
            {
                authOptions.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                authOptions.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            }).AddJwtBearer(bearerOptions =>
            {
                var paramsValidation = bearerOptions.TokenValidationParameters;
                paramsValidation.IssuerSigningKey = signingConfigurations.Key;
                paramsValidation.ValidAudience = tokenConfigurations.Audience;
                paramsValidation.ValidIssuer = tokenConfigurations.Issuer;

                // Valida a assinatura de um token recebido
                paramsValidation.ValidateIssuerSigningKey = true;

                // Verifica se um token recebido ainda é válido
                paramsValidation.ValidateLifetime = true;

                // Tempo de tolerância para a expiração de um token (utilizado
                // caso haja problemas de sincronismo de horário entre diferentes
                // computadores envolvidos no processo de comunicação)
                paramsValidation.ClockSkew = TimeSpan.Zero;
            });

            // Ativa o uso do token como forma de autorizar o acesso
            // a recursos deste projeto
            services.AddAuthorization(auth =>
            {
                auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
                    .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
                    .RequireAuthenticatedUser().Build());
            });

            services.AddHangfire(x => x.UseSqlServerStorage(Configuration.GetConnectionString("ConexaoPadrao")));
            JobStorage.Current = new SqlServerStorage(Configuration.GetConnectionString("ConexaoPadrao"));
            // services.AddMvcCore();
            services.AddMvc();

            var assemblies = new[]
            {
                typeof(Startup).Assembly,
                typeof(Colaborador).Assembly
            };

            //IContainer container = null;

            var serviceProvider = ConfigurationApplication.Inicialize(services, Configuration, assemblies);

            //GlobalConfiguration.Configuration.UseAutofacActivator(container);

            return serviceProvider;
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseMiddleware<UnitOfWorkMiddleware>();
            app.UseMiddleware<AutenticacaoMiddleware<Autenticacao>>();

            app.UseMvc();            

            app.UseStaticFiles();
        }
    }
}

Can somebody help me? I've been trying to find something in the questions here, but I can't find a similar issue.

1
Work backwards. Remove the code you added one line at a time to see what causes your issue.Brad
@Brad turns out when I set my ConfigureServices method to void the authentication works. However I need it to be set as "IServiceProvider" in order to be able to resolve dependencies from other projects.Arthur Medeiros
Now follow the path that works. Instead of trying to figure out why it doesn't work when returning IServiceProvider, figure out how to load your other dependencies within the ConfigureServices method declared void. I assume your ConfigurationApplication.Inicialize() is where this is done? A static call to load dependencies has a certain smell about it. Could be nicely cleaned up using extension methods that returns the IServiceCollection, not a built IServiceProvider.Brad
@Brad I'm having a hard time trying to figure out how to load the dependencies I need. Everytime I try to use my ConfigureServices as void I get the message Unable to resolve UnitOfWorkManager . I tried something that seemed obvious services.addScope<UnitOfWorkManager>(); but still I get the same error when calling any method in my controller. Now... There's one more thing, when I return IServiceProvider, the dependencies get resolved, but I get nothing from [fromservices] in my controller. Is there a way to look what is available from services?Arthur Medeiros
You were right about loading the dependencies in my ConfigurationApplication.Inicialize method. Inside of it the code is declaring a ContainerBuilder that does stuff like builder.Populate(services); Then it returns a IContainer.Arthur Medeiros

1 Answers

1
votes
  1. I've just tested the code in the tutorial you mentioned above. When I post a request to the login action:

    POST http://localhost:56435/api/login HTTP/1.1
    Content-Type : application/json
    
    {userId:"usuario01",accessKey:"94be650011cf412ca906fc335f615cdc"}
    

the response will be :

HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: application/json; charset=utf-8
Server: Kestrel
X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNcc3RyZWFteHVcRG93bmxvYWRzXEFTUE5FVENvcmUyX0pXVC1tYXN0ZXJcQVNQTkVUQ29yZTJfSldULW1hc3RlclxBUElBbHR1cmFzXEFQSUFsdHVyYXNcYXBpXGxvZ2lu?=
X-Powered-By: ASP.NET
Date: Thu, 30 Aug 2018 01:12:10 GMT

{
  "authenticated": true,
  "created": "2018-08-30 09:12:10",
  "expiration": "2018-08-30 09:13:10",
  "accessToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6WyJ1c3VhcmlvMDEiLCJ1c3VhcmlvMDEiXSwianRpIjoiZTk0ZDU3NTMwZTczNDMzMTlkYjVlN2EwMDY2YjQwOTUiLCJuYmYiOjE1MzU1OTE1MzAsImV4cCI6MTUzNTU5MTU5MCwiaWF0IjoxNTM1NTkxNTMwLCJpc3MiOiJFeGVtcGxvSXNzdWVyIiwiYXVkIjoiRXhlbXBsb0F1ZGllbmNlIn0.WR3V9kkI_Pyhpw-TnpbTsByB4JZa61PFymUGdm-3_CGInbOOH6RxbMchCdbojyflSZBf3d8O7RYiz2xiMoonkOcJc6gtO0ODCv-cUDPJYApwJVYOq1HEqSs0STvKdSjRZF6j0DM4WON6fpoVwKAq0rwng1aEf9bQue6Pl-fwbzbaCxhCrQtDyDYKyfO0tg-VMGQfMyV29Ab0s4W2L5bcB0w0jAgfFianAD2DKSDSVsDSiBTd7b-Np9OcEtBvXCkXMFEqGzkOIKGAR5kzTiOWPo_Dh9qOVlsooRtFbhOjxWqeYRR76fZ-OOt9Sg6eG5zu1T7lPNywKFeAznS2rss1ig",
  "message": "OK"
}

Note the expiration here indicates that the access token will be expired in 1 min. If I send requests with these token in later than 61secs :

GET http://localhost:56435/api/ConversorAlturas/PesMetros/1.13 HTTP/1.1
Authorization : Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6WyJ1c3VhcmlvMDEiLCJ1c3VhcmlvMDEiXSwianRpIjoiZTk0ZDU3NTMwZTczNDMzMTlkYjVlN2EwMDY2YjQwOTUiLCJuYmYiOjE1MzU1OTE1MzAsImV4cCI6MTUzNTU5MTU5MCwiaWF0IjoxNTM1NTkxNTMwLCJpc3MiOiJFeGVtcGxvSXNzdWVyIiwiYXVkIjoiRXhlbXBsb0F1ZGllbmNlIn0.WR3V9kkI_Pyhpw-TnpbTsByB4JZa61PFymUGdm-3_CGInbOOH6RxbMchCdbojyflSZBf3d8O7RYiz2xiMoonkOcJc6gtO0ODCv-cUDPJYApwJVYOq1HEqSs0STvKdSjRZF6j0DM4WON6fpoVwKAq0rwng1aEf9bQue6Pl-fwbzbaCxhCrQtDyDYKyfO0tg-VMGQfMyV29Ab0s4W2L5bcB0w0jAgfFianAD2DKSDSVsDSiBTd7b-Np9OcEtBvXCkXMFEqGzkOIKGAR5kzTiOWPo_Dh9qOVlsooRtFbhOjxWqeYRR76fZ-OOt9Sg6eG5zu1T7lPNywKFeAznS2rss1ig

the response will be:

HTTP/1.1 401 Unauthorized
Server: Kestrel
WWW-Authenticate: Bearer error="invalid_token", error_description="The token is expired"
X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNcc3RyZWFteHVcRG93bmxvYWRzXEFTUE5FVENvcmUyX0pXVC1tYXN0ZXJcQVNQTkVUQ29yZTJfSldULW1hc3RlclxBUElBbHR1cmFzXEFQSUFsdHVyYXNcYXBpXENvbnZlcnNvckFsdHVyYXNcUGVzTWV0cm9zXDEuMTM=?=
X-Powered-By: ASP.NET
Date: Thu, 30 Aug 2018 01:17:02 GMT
Content-Length: 0

Note the error message is "invalid_token", error_description="The token is expired"

I'm not sure whether your have your expiration set as 1 min or not . However ,you'd better check it firstly .If it doesn't help , goto step 2.

  1. As there's not enough information in your code above , I suggest you should check the following list :

a). What's the bearer you send to the controller when you suffer a invalid_token ?

b). What's your Configuracao ? The statement ofConfiguracao.Configuration = Configuration; make me confused . Could you please show us the Configuracao ?

b). I notice that both you and the tutorial used a custom authentication/authorization way instead of standard UseAuthentication(). I'm also not sure what do you mean by app.UseMiddleware<AutenticacaoMiddleware<Autenticacao>>() . Could you please show us the AutenticacaoMiddleware and Autenticacao ?