2

I am trying to understand all this stuff about JWT tokens in Asp.Net Core. And after bleeding work with huge amount of missunderstandings I am stuck. My task is to make WebApi server with two controllers (protected and not protected). I should grant tokens from the server and be able to get protected resources. everything seems to be fine when I run the server and try to get protected resources from postman. But when I do the same thing in me Angular client , from another domain I have strange thing. I can get unprotected resource, and I cannot get protected resource with such error:

XMLHttpRequest cannot load http://localhost:10450/api/prvalues. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:10377' is therefore not allowed access.

To make it clear I show all my attemts. My project.json looks like

  "dependencies": {
     "AspNet.Security.OpenIdConnect.Server": "1.0.0-beta4",
     "EntityFramework.Core": "7.0.0-rc1-final",
     "EntityFramework.InMemory": "7.0.0-rc1-final",
     "Microsoft.AspNet.Authentication.JwtBearer": "1.0.0-rc1-final",
     "Microsoft.AspNet.Diagnostics": "1.0.0-rc1-final",
     "Microsoft.AspNet.Identity.EntityFramework": "3.0.0-rc1-final",
     "Microsoft.AspNet.IISPlatformHandler": "1.0.0-rc1-final",
     "Microsoft.AspNet.Mvc": "6.0.0-rc1-final",
     "Microsoft.AspNet.Server.Kestrel": "1.0.0-rc1-final",
     "Microsoft.AspNet.StaticFiles": "1.0.0-rc1-final",
     "Microsoft.Extensions.Configuration.FileProviderExtensions": "1.0.0-rc1-final",
     "Microsoft.Extensions.Logging": "1.0.0-rc1-final",
     "Microsoft.Extensions.Logging.Console": "1.0.0-rc1-final",
     "Microsoft.Extensions.Logging.Debug": "1.0.0-rc1-final",
     "NWebsec.Middleware": "1.0.0-gamma-39",
     "Microsoft.AspNet.Mvc.Cors": "6.0.0-rc1-final",
     "Microsoft.AspNet.Cors": "6.0.0-rc1-final"
},

My Startup.ConfigureServices() looks like

public void ConfigureServices(IServiceCollection services) {

        services.AddCors(options =>
        {
            options.AddPolicy("AllowAllOrigins",
                    builder =>
                    {
                        builder.AllowAnyOrigin();
                        builder.AllowAnyHeader();
                    });
        });

        services.AddEntityFramework()
           .AddInMemoryDatabase()
           .AddDbContext<ApplicationDbContext<ApplicationUser, Application, IdentityRole, string>>(options => {
               options.UseInMemoryDatabase();
           });
        services.AddScoped<IAuthStore<ApplicationUser,Application>, AuthStore<ApplicationUser, Application, IdentityRole, 
            ApplicationDbContext<ApplicationUser, Application, IdentityRole, string>, string>>();
        services.AddScoped<AuthManager<ApplicationUser, Application>>();
        services.AddIdentity<ApplicationUser, IdentityRole>(options =>
        {
            options.Password = new PasswordOptions()
            {
                RequiredLength = 1,
                RequireDigit = false,
                RequireLowercase = false,
                RequireUppercase = false,
                RequireNonLetterOrDigit = false

            };
        }).AddEntityFrameworkStores<ApplicationDbContext<ApplicationUser, Application, IdentityRole, string>>().AddDefaultTokenProviders();
        services.AddAuthentication();

        // Add framework services.

        services.AddCaching();
        services.AddMvc();
    }

AuthManager and AuthStore are stolen from OpenIddict. I will show them later. My Startup.Configure() looks like:

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddConsole(Configuration.GetSection("Logging"));
        loggerFactory.AddDebug();
        app.UseCors("AllowAllOrigins");
        app.UseIISPlatformHandler();
        app.UseDeveloperExceptionPage();
        app.UseStaticFiles();

        // Create a new branch where the registered middleware will be executed only for API calls.            

        app.UseWhen(context => context.Request.Path.StartsWithSegments(new PathString("/api")), branch =>
        {
            branch.UseJwtBearerAuthentication(options =>
            {

                options.AutomaticAuthenticate = true;
                options.AutomaticChallenge = true;
                options.RequireHttpsMetadata = false;
                // Thisi is test, if I uncomment this and SetResource in AuthorizationProvider everything works in postman
                //options.Audience = "http://localhost:10450/";
                // My Angular client
                options.Audience = "http://localhost:10377/";
                // My Api
                options.Authority = "http://localhost:10450/";

            });
        });



        // Note: visit https://docs.nwebsec.com/en/4.2/nwebsec/Configuring-csp.html for more information.
        app.UseCsp(options => options.DefaultSources(configuration => configuration.Self())
                                     .ImageSources(configuration => configuration.Self().CustomSources("data:"))
                                     .ScriptSources(configuration => configuration.UnsafeInline())
                                     .StyleSources(configuration => configuration.Self().UnsafeInline()));

        app.UseXContentTypeOptions();

        app.UseXfo(options => options.Deny());

        app.UseXXssProtection(options => options.EnabledWithBlockMode());

        app.UseOpenIdConnectServer(options =>
        {

            options.Provider = new AuthorizationProvider();
            // Note: see AuthorizationController.cs for more
            // information concerning ApplicationCanDisplayErrors.
            options.ApplicationCanDisplayErrors = true;
            options.AllowInsecureHttp = true;
            options.AuthorizationEndpointPath = PathString.Empty;
            options.TokenEndpointPath = "/token";
            // Note: by default, tokens are signed using dynamically-generated
            // RSA keys but you can also use your own certificate:
            // options.SigningCredentials.AddCertificate(certificate);
        });

        app.UseMvc();
        var hasher = new PasswordHasher<Application>();
        using (var database = app.ApplicationServices.GetService<ApplicationDbContext<ApplicationUser, Application, IdentityRole, string>>())
        {              
            database.Applications.Add(new Application
            {
                Id = "myPublicClient",
                DisplayName = "My client application",
                Type = ApplicationTypes.Public
            });
            database.Applications.Add(new Application
            {
                Id = "myConfidentialClient",
                DisplayName = "My client application",
                Secret = hasher.HashPassword(null, "secret_secret_secret"),
                Type = ApplicationTypes.Confidential
            });

            database.SaveChanges();
            CreateUser(app).Wait();
        }        
    }

And finally, my AthorizationProvider.GrantResourceOwnerCredentials() (I use AspNet.Security.OpenIdConnect.Server) is :

    public override async Task GrantResourceOwnerCredentials(GrantResourceOwnerCredentialsContext context)
    {

        #region UserChecking
        var manager = context.HttpContext.RequestServices.GetRequiredService<AuthManager<ApplicationUser, Application>>();

        var user = await manager.FindByNameAsync(context.UserName);
        if (user == null)
        {
            context.Rejected(
                error: OpenIdConnectConstants.Errors.InvalidGrant,
                description: "Invalid credentials.");

            return;
        }

        // Ensure the user is not already locked out.
        if (manager.SupportsUserLockout && await manager.IsLockedOutAsync(user))
        {
            context.Rejected(
                error: OpenIdConnectConstants.Errors.InvalidGrant,
                description: "Account locked out.");

            return;
        }

        // Ensure the password is valid.
        if (!await manager.CheckPasswordAsync(user, context.Password))
        {
            context.Rejected(
                error: OpenIdConnectConstants.Errors.InvalidGrant,
                description: "Invalid credentials.");

            if (manager.SupportsUserLockout)
            {
                await manager.AccessFailedAsync(user);

                // Ensure the user is not locked out.
                if (await manager.IsLockedOutAsync(user))
                {
                    context.Rejected(
                        error: OpenIdConnectConstants.Errors.InvalidGrant,
                        description: "Account locked out.");
                }
            }

            return;
        }

        if (manager.SupportsUserLockout)
        {
            await manager.ResetAccessFailedCountAsync(user);
        }


        if (context.Request.ContainsScope(OpenIdConnectConstants.Scopes.Profile) &&
           !context.Request.ContainsScope(OpenIdConnectConstants.Scopes.Email) &&
            string.Equals(await manager.GetUserNameAsync(user),
                          await manager.GetEmailAsync(user),
                          StringComparison.OrdinalIgnoreCase))
        {
            context.Rejected(
                error: OpenIdConnectConstants.Errors.InvalidRequest,
                description: "The 'email' scope is required.");

            return;
        }

        #endregion

        var identity = await manager.CreateIdentityAsync(user, context.Request.GetScopes());

        var ticket = new AuthenticationTicket(
           new ClaimsPrincipal(identity),
           new AuthenticationProperties(),
           context.Options.AuthenticationScheme);

        //ticket.SetResources(context.Request.GetResources());
        // When I tested with postman
        //ticket.SetResources(new[] { "http://localhost:10450/" });
        ticket.SetResources(new[] { "http://localhost:10377" });
        ticket.SetScopes(context.Request.GetScopes());

        context.Validated(ticket);


    }

I have shown all code , that I think may cause the problem. Sorry, if it long, but I do not know witch part causes problem.

To access to my protecetd resource (simple decorated with [Authorize]) I use such dummy code from my Angular client

$http.defaults.headers.common['Authorization'] = 'Bearer ' + 'token here';
return $http.get('http://localhost:10450/' + 'api/prvalues');

As I said, I have cors error for such request. But if I try to get unprotected resource (remove [Authorize] attribute from controller) everything works fine.

2
  • Did you try using AllowCredentials() together with AllowAnyMethod() as in the Cors example on github? github.com/aspnet/Mvc/blob/… Commented Mar 4, 2016 at 8:33
  • Yes. It has no effect Commented Mar 4, 2016 at 9:09

1 Answer 1

1

I have found the solution. When I set resources in my AthorizationProvider.GrantResourceOwnerCredentials() I set "http://localhost:10377" , but in UseJwtBearerAuthentication middleware I set "http://localhost:10377/" in authority option (slash included at the end). It is very silly mistake.

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.