0

I am working through a book on ASP.NET Core Web API and I am trying to implement authorization.

Here is my Program.cs:

var builder = WebApplication.CreateBuilder(args);

var connString = builder.Configuration.GetConnectionString("DefaultConnection");

builder.Logging.ClearProviders()
               .AddSimpleConsole()
               .AddDebug();

builder.Host.UseSerilog((ctx, lc) => 
{
    lc.ReadFrom.Configuration(ctx.Configuration);

    lc.Enrich.WithMachineName();
    lc.Enrich.WithThreadId();

    // This causes SeriLog to log to a table called LogEvents in the database. If the
    // table does not exist, it is created.
    lc.WriteTo.MSSqlServer(
        connectionString: connString,
        sinkOptions: new MSSqlServerSinkOptions
        {
            TableName = "LogEvents",
            AutoCreateSqlTable = true
        },
        columnOptions: new ColumnOptions()
        {
        AdditionalColumns = new SqlColumn[]
        {
            new SqlColumn()
            {
                ColumnName = "SourceContext",
                PropertyName = "SourceContext",
                DataType = System.Data.SqlDbType.NVarChar
            }
        }
        }
    );
},

// Keep sending log events to previously defined logging providers
writeToProviders: true);

// Add services to the container.
builder.Services.AddControllers(options => 
{
    options.ModelBindingMessageProvider.SetValueIsInvalidAccessor((x) => $"The value '{x}' is invalid.");
    options.ModelBindingMessageProvider.SetValueMustBeANumberAccessor((x) => $"The field {x} must be a number.");
    options.ModelBindingMessageProvider.SetAttemptedValueIsInvalidAccessor((x, y) => $"The value '{x}' is not valid for {y}.");
    options.ModelBindingMessageProvider.SetMissingKeyOrValueAccessor(() => $"A value is required.");
});

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
    options.SwaggerDoc("v1", new OpenApiInfo { Title = "My Test API", Version = "v1" });

    options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
    {
        In = ParameterLocation.Header,
        Description = "Please enter token",
        Name = "Authorization",
        Type = SecuritySchemeType.Http,
        BearerFormat = "JWT",
        Scheme = "bearer"
    });
    options.AddSecurityRequirement(new OpenApiSecurityRequirement
    {
        {
            new OpenApiSecurityScheme
            {
                Reference = new OpenApiReference
                {
                    Type = ReferenceType.SecurityScheme,
                    Id = "Bearer"
                }
            },
            Array.Empty<string>()
        }
    });
});

builder.Services.AddCors(options => {

    options.AddDefaultPolicy(cfg => {
        cfg.WithOrigins(builder.Configuration["AllowedOrigins"]);
        cfg.AllowAnyHeader();
        cfg.AllowAnyMethod();
    });

    options.AddPolicy(name: "AnyOrigin",
        cfg => {
            cfg.AllowAnyOrigin();
            cfg.AllowAnyHeader();
            cfg.AllowAnyMethod();
        });
});

if (string.IsNullOrEmpty(connString))
{
    throw new Exception("Connection string not found");
}

builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connString));

builder.Services.AddIdentityCore<ApiUser>(options =>
{
    options.Password.RequireDigit = true;
    options.Password.RequireLowercase = true;
    options.Password.RequireUppercase = true;
    options.Password.RequireNonAlphanumeric = true;
    options.Password.RequiredLength = 12;
})
.AddEntityFrameworkStores<ApplicationDbContext>();

builder.Services.AddAuthentication(options => 
{
    options.DefaultAuthenticateScheme =
    options.DefaultChallengeScheme =
    options.DefaultForbidScheme =
    options.DefaultScheme =
    options.DefaultSignInScheme =
    options.DefaultSignOutScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options => {options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidIssuer = builder.Configuration["JWT:Issuer"],
        ValidateAudience = true,
        ValidAudience = builder.Configuration["JWT:Audience"],
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(builder.Configuration["JWT:SigningKey"]))
    };
});
builder.Services.AddAuthorization();

builder.Services.AddIdentity<ApiUser, IdentityRole>()
                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddDefaultTokenProviders();

var app = builder.Build();

app.UseSwagger();
app.UseSwaggerUI();

if (app.Configuration.GetValue<bool>("UseDeveloperExceptionPage"))
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler("/error");
}

app.UseHttpsRedirection();

app.UseCors();

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

app.MapGet("/auth/test/1",
    [Authorize]
    [EnableCors("AnyOrigin")]
    [ResponseCache(NoStore = true)]() =>
    {
        return Results.Ok("You are authorized!");
    });

app.MapControllers();

app.Run();

The AccountController's Login method returns a JWT that I am supposed to use to authorize additional API calls.

The book says that if I try the Test method (defined using minimal API at the bottom) in Swagger WITHOUT logging then I should get a http 401 - Unauthorized error. Instead, I get a http 405 "Method Not Allowed" error.

But when I do log in and get the JWT and pass it to the test method, I still get the 405.

What am I doing wrong here?

2
  • you mapped a GET method. Is that the method of your swagger call? On a side note, don't allow any origin. Credentialed requests can't be wild-carded. (same goes for methods, methinks) Only use CORS headers if you must make cross-domain calls, and only allow specific domains. Commented Apr 11 at 18:32
  • @browsermator Yes, it shows up as a Get in Swagger. If I remove the [Authorize] asttribute, it works. With the attribute I get a 405. It's shown at the bottom of my code listing Commented Apr 11 at 22:38

1 Answer 1

0

If you're getting a method not allowed error, you've probably set the change you need to send incorrectly in your method. If you are posting json data in the method you need to use

public IActionResult MyMehod( [FromBody]string parameterA)

to capture the parameter.

If you are sending Form data, you should also add

public IActionResult MyMehod( [FromForm]string parameterA)

I think you should check this first, if you think it is correct, send me a short example of the API backend.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.