NetCore JWT - Dlaczego nie mogę się zalogować za pomocą Tokenu?

0

Witam serdecznie. W tym samym projekcie co NetCore WebApp stworzyłem interfejs API. Potrafię uzyskać Token, ale przy próbie zalogowania do akcji z atrybutem cały czas otrzymuję "treść strony logowania". Co robię nie tak?
startup:

 services.AddDefaultIdentity<ApplicationUser>(options =>
            {

                options.SignIn.RequireConfirmedAccount = false;
                options.SignIn.RequireConfirmedEmail = false;
                options.SignIn.RequireConfirmedPhoneNumber = false;

                options.Password.RequireDigit = false;
                options.Password.RequiredLength = 1;
                options.Password.RequiredUniqueChars = 1;
                options.Password.RequireLowercase = false;
                options.Password.RequireNonAlphanumeric = false;
                options.Password.RequireUppercase = false;

                options.User.RequireUniqueEmail = true;
            })
                .AddRoles<IdentityRole>()
                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddDefaultTokenProviders();

            services.AddAuthentication().AddFacebook(facebookOptions =>
            {
                facebookOptions.AppId = Configuration["Authentication:Facebook:AppId"];
                facebookOptions.AppSecret = Configuration["Authentication:Facebook:AppSecret"];
            }).AddJwtBearer(options =>
            {
                options.SaveToken = true;
                options.RequireHttpsMetadata = false;
                options.TokenValidationParameters = new TokenValidationParameters()
                {
                    ValidateIssuer = true,
                    ValidateAudience = true,
                    ValidAudience = Configuration["JWT:ValidAudience"],
                    ValidIssuer = Configuration["JWT:ValidIssuer"],
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JWT:Secret"]))
                };
            });

API

[HttpPost]
        [Route("Login2")]
        public async Task<IActionResult> Login2([FromBody] LoginModel model)
        {
            var user = await _userManager.FindByEmailAsync(model.Login);
            if (user != null && await _userManager.CheckPasswordAsync(user, model.Password))
            {
                var userRoles = await _userManager.GetRolesAsync(user);

                var authClaims = new List<Claim>
                {
                    new Claim(ClaimTypes.Name, user.UserName),
                    new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
                };

                foreach (var userRole in userRoles)
                {
                    authClaims.Add(new Claim(ClaimTypes.Role, userRole));
                }

                var authSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JWT:Secret"]));

                var token = new JwtSecurityToken(
                    issuer: _configuration["JWT:ValidIssuer"],
                    audience: _configuration["JWT:ValidAudience"],
                    expires: DateTime.Now.AddHours(3),
                    claims: authClaims,
                    signingCredentials: new SigningCredentials(authSigningKey, SecurityAlgorithms.HmacSha256)
                    );

                return Ok(new
                {
                    token = new JwtSecurityTokenHandler().WriteToken(token),
                    expiration = token.ValidTo
                });
            }
            return Unauthorized();
        }

Przy próbie logowania:
screenshot-20210211191049.png

Przy próbie pobrania danych z akcji z atrybutem Authorize

[Authorize]
        [HttpGet]
        public IEnumerable<string> Get()
        {
            return new string[] { "value1", "value2" };
        }

screenshot-20210211191138.png

Jak sprawić, aby przy niepomyślnej autoryzacji do strony w kontrolerach MVC przenosiło do strony logowania, a w API zwracało odpowiednie dane z błędem? I przede wszystkim, abym mógł się zalogować za pomocą owego tokenu

3

Pokaż jak konfigurujesz Middlewary - metoda Configure

a tak w ogóle, to czy AddDefaultIdentity przypadkiem nie dodaje Identity opartego o Cookiesy i stronę logowania?

public static IdentityBuilder AddDefaultIdentity<TUser>(this IServiceCollection services, Action<IdentityOptions> configureOptions) where TUser : class
{
	services.AddAuthentication(o =>
	{
		o.DefaultScheme = IdentityConstants.ApplicationScheme;
		o.DefaultSignInScheme = IdentityConstants.ExternalScheme;
	})
	.AddIdentityCookies(o => { });

	return services.AddIdentityCore<TUser>(o =>
	{
		o.Stores.MaxLengthForKeys = 128;
		configureOptions?.Invoke(o);
	})
		.AddDefaultUI()
		.AddDefaultTokenProviders();
}

public static IdentityCookiesBuilder AddIdentityCookies(this AuthenticationBuilder builder, Action<IdentityCookiesBuilder> configureCookies)
{
	var cookieBuilder = new IdentityCookiesBuilder();
	cookieBuilder.ApplicationCookie = builder.AddApplicationCookie();
	cookieBuilder.ExternalCookie = builder.AddExternalCookie();
	cookieBuilder.TwoFactorRememberMeCookie = builder.AddTwoFactorRememberMeCookie();
	cookieBuilder.TwoFactorUserIdCookie = builder.AddTwoFactorUserIdCookie();
	configureCookies?.Invoke(cookieBuilder);
	return cookieBuilder;
}

public static IdentityBuilder AddDefaultUI(this IdentityBuilder builder)
{
	builder.AddSignInManager();
	AddRelatedParts(builder);

	builder.Services.ConfigureOptions(
		typeof(IdentityDefaultUIConfigureOptions<>)
			.MakeGenericType(builder.UserType));
	builder.Services.TryAddTransient<IEmailSender, EmailSender>();

	return builder;
}
0

@WeiXiao:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseDatabaseErrorPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
                app.UseHsts();
            }
            app.UseRequestLocalization();
            app.UseHttpsRedirection();
            app.UseStaticFiles();
            
            app.UseRouting();

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

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");
                endpoints.MapRazorPages();
            });
        }

Tutaj nic nie zmieniałem w szablonie

1

Routing twojej apki mapuje urle takie jak

[localhost]/controller/action/{opcjonalne parametry}

A Ty próbujesz uderzyć na
[localhost]/api/controller/action

Chyba, że jeszcze nadpisujesz routing atrybutami w kontrolerze?

0

API:

[HttpPost]
        [Route("Login2")]
        public async Task<IActionResult> Login2([FromBody] LoginModel model)
        {
            var user = await _userManager.FindByEmailAsync(model.Login);
            if (user != null && await _userManager.CheckPasswordAsync(user, model.Password))
            {
                var userRoles = await _userManager.GetRolesAsync(user);

                var authClaims = new List<Claim>
                {
                    new Claim(ClaimTypes.Name, user.Email),
                    new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
                };

                foreach (var userRole in userRoles)
                {
                    authClaims.Add(new Claim(ClaimTypes.Role, userRole));
                }

                var authSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JWT:Secret"]));

                var token = new JwtSecurityToken(
                    expires: DateTime.Now.AddHours(3),
                    claims: authClaims,
                    signingCredentials: new SigningCredentials(authSigningKey, SecurityAlgorithms.HmacSha256)
                    );

                return Ok(new
                {
                    token = new JwtSecurityTokenHandler().WriteToken(token),
                    expiration = token.ValidTo
                });
            }
            return Unauthorized();
        }
// GET: api/MuzostacjaApi, 
        [Authorize]
        [HttpGet]
        public IEnumerable<string> Get()
        {
            return new string[] { "value1", "value2" };
        }

Otrzymuję token prawidłowo
screenshot-20210213184851.png

Ale z logowaniem już gorzej, ponieważ otrzymuję kod html strony "zarejestruj się"
screenshot-20210213184957.png

1

Dobrze rozumiem - chcesz mieć oba sposoby przechowywania poświadczeń po stronie klienta?

JWT oraz Cookiesy?

0

@WeiXiao: dokładnie tak.
jak zrobię tak:

 services.AddDefaultIdentity<ApplicationUser>(options =>
            {

                options.SignIn.RequireConfirmedAccount = false;
                options.SignIn.RequireConfirmedEmail = false;
                options.SignIn.RequireConfirmedPhoneNumber = false;

                options.Password.RequireDigit = false;
                options.Password.RequiredLength = 1;
                options.Password.RequiredUniqueChars = 1;
                options.Password.RequireLowercase = false;
                options.Password.RequireNonAlphanumeric = false;
                options.Password.RequireUppercase = false;

                options.User.RequireUniqueEmail = true;
            })
                .AddRoles<IdentityRole>()
                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddDefaultTokenProviders();

            services.AddAuthentication().AddFacebook(facebookOptions =>
            {
                facebookOptions.AppId = Configuration["Authentication:Facebook:AppId"];
                facebookOptions.AppSecret = Configuration["Authentication:Facebook:AppSecret"];
            });
            //    .AddJwtBearer(options =>
            //{
            //    options.SaveToken = true;
            //    options.RequireHttpsMetadata = false;
            //    options.TokenValidationParameters = new TokenValidationParameters()
            //    {
            //        ValidateIssuer = false,
            //        ValidateAudience = false,
            //        ValidAudience = Configuration["JWT:ValidAudience"],
            //        ValidIssuer = Configuration["JWT:ValidIssuer"],
            //        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JWT:Secret"]))
            //    };
            //});
            services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = "JwtBearer";
                options.DefaultChallengeScheme = "JwtBearer";
            }).AddJwtBearer("JwtBearer", options => {
                options.TokenValidationParameters = new TokenValidationParameters()
                {
                    ValidateIssuerSigningKey = true,
                    ValidateIssuer = false,
                    ValidateAudience = false,
                    ValidAudience = Configuration["JWT:ValidAudience"],
                    ValidIssuer = Configuration["JWT:ValidIssuer"],
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JWT:Secret"])),
                    ValidateLifetime = true,
                };
            });

działają poświadczenia na podstawie tokenu, ale nie działa logowanie na stronie w przeglądarce.
screenshot-20210213195020.png

Wybacz za tak długą odpowiedź, ale google mnie pochłonęło i ten godzinny filmik :D

1

a czy przypadkiem nie trzeba jakoś rozbudować authentication middleware aby określał (np. na podstawie URLa) kiedy ma użyć którego sposobu?

0

Czyli, jeżeli łączy się kontrolerem API ma używać drugiego sposobu authentication, a dla reszty pierwszego? Da się coś takiego zrobić?

1

Zerknij na to, może to cię jakoś nakieruje

ASP.NET Core 2.0 combining Cookies and Bearer Authorization for the same endpoint

Oni tam robią takie coś

[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
oraz
[Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)]

0

Jedyne co dodałem to [Authorize(AuthenticationSchemes = "Bearer")] i zaczęło wszystko działać idealnie. Dziękuję Ci <3

1 użytkowników online, w tym zalogowanych: 0, gości: 1