RE:NODE

აპლიკაციები11 წუთის საკითხავი

ASP.NET Core-ის ავთენტიფიკაცია: cookie-ები, JWT და Identity

აირჩიე cookie-სა და JWT ავთენტიფიკაციას შორის ASP.NET Core-ში, დააყენე Identity და შეინახე data protection-ის გასაღებები, რომ ხელახალმა deploy-მა ყველა აღარ გამოაგდოს.

0 მკითხველი

საიტისთვის, სადაც ბრაუზერი და სერვერი ერთი და იგივე აპლიკაციაა, cookie-ით ავთენტიფიკაცია გამოიყენე. API-სთვის, რომელსაც მობილური აპლიკაციები, სხვა სერვერები ან სხვა დომენზე მყოფი single-page აპლიკაცია იძახებს, გამოიყენე bearer ტოკენები - ჩვეულებრივ JWT-ები, რომლებსაც identity provider გასცემს. ASP.NET Core Identity მომხმარებლების საცავი და პაროლების ლოგიკაა და არა მესამე ავთენტიფიკაციის სქემა; ის რომელიმე მათგანის ქვეშ ზის. რომელიც არ უნდა აირჩიო, ის, რაც ჰოსტინგზე მყოფი აპლიკაციების უმეტესობას ტეხავს, საერთოდ არჩევანი არ არის: ეს data protection-ის გასაღებებია, რომლებიც კონტეინერის შიგნით ცხოვრობს და ხელახალი deploy-ისას ქრება, ყველა მომხმარებელს აგდებს და ყველა ფორმას ტეხავს. ეს პოსტი განიხილავს არჩევანს, თითოეულის დაყენებას, Identity-ის ნაგულისხმევ მნიშვნელობებს და იმას, როგორ შეინახო ეს გასაღებები სწორად.

ავთენტიფიკაცია, ავტორიზაცია და middleware-ის რიგი#

ორი სიტყვა, ორი სამუშაო. ავთენტიფიკაცია არკვევს, ვინ არის გამომძახებელი, cookie-დან, ტოკენიდან ან სერტიფიკატიდან, და აწყობს ClaimsPrincipal-ს. ავტორიზაცია წყვეტს, შეუძლია თუ არა ამ principal-ს იმის გაკეთება, რასაც ითხოვს. ASP.NET Core მათ ცალ-ცალკე ინახავს, პირველისთვის თითო სქემაზე handler-ით, მეორესთვის - პოლიტიკებით.

csharp
builder.Services.AddAuthentication(/* default scheme */);builder.Services.AddAuthorization();var app = builder.Build();app.UseForwardedHeaders();   // behind a proxy: before anything that reads scheme or IPapp.UseRouting();app.UseAuthentication();app.UseAuthorization();app.MapControllers();

WebApplication ავთენტიფიკაციისა და ავტორიზაციის middleware-ს თავად ამატებს, როცა მათი სერვისები დარეგისტრირებულია, მაგრამ მათი აშკარად ჩაწერა რიგს ხილულს ხდის, და რიგს მნიშვნელობა აქვს: ავთენტიფიკაცია ავტორიზაციამდე უნდა გაეშვას, ორივე კი routing-ის შემდეგ, რომ endpoint-ის მეტამონაცემები, როგორიცაა [Authorize], ცნობილი იყოს. forwarded header-ები პირველი მოდის, როცა აპლიკაცია reverse proxy-ს უკან დგას, თორემ აპლიკაციას ჰგონია, რომ ყოველი მოთხოვნა უბრალო HTTP-ით მოვიდა, და secure-ად მონიშნული cookie-ები, გადამისამართების URL-ები და OAuth callback-ები ყველა არასწორად მუშაობს. ASP.NET Core reverse proxy-ს უკან ზუსტ კონფიგურაციას შეიცავს.

კლიენტიგამოიყენერატომ
სერვერზე რენდერებული საიტი (Razor Pages, MVC, Blazor Server)Cookie-ებიბრაუზერი მათ ინახავს და აგზავნის; JavaScript-ში სამართავი არაფერია
SPA, რომელიც API-ის იმავე დომენიდან მოეწოდებაCookie-ებიიგივე მიზეზები, პლუს HttpOnly მონაცემებს სკრიპტებისგან შორს ინახავს
SPA სხვა დომენზეტოკენები, ან backend-for-frontend cookie-ებითcross-site cookie-ებს ბრაუზერები სულ უფრო ხშირად ბლოკავს
მობილური ან desktop აპლიკაციატოკენებიდასაყრდენი cookie jar არ არის
სერვერიდან სერვერზეტოკენები (client credentials)არც მომხმარებელი, არც ბრაუზერი

არგუმენტი, რომ JWT-ები "stateless-ია და ამიტომ უკეთესი", ძირითადად ისეთ მასშტაბს ეხება, რომელიც არ გაქვს. ხელმოწერილი cookie ზუსტად ისევე stateless-ია - ASP.NET Core-ის auth cookie დაშიფრულ claim-ებს შეიცავს და არა სესიის ID-ს - და მას ორი თვისება აქვს, რომელიც JavaScript-ში შენახულ ტოკენებს არ აქვს: შენახვასა და ვადას ბრაუზერი მართავს, ხოლო HttpOnly cookie-ს ჩანერგილი სკრიპტი ვერ წაიკითხავს. ტოკენები თავის ადგილს მაშინ იმსახურებს, როცა კლიენტი ბრაუზერი არ არის, ან როცა ცალკე identity provider მათ რამდენიმე API-სთვის გასცემს.

csharp
builder.Services    .AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)    .AddCookie(options =>    {        options.LoginPath = "/account/login";        options.AccessDeniedPath = "/account/denied";        options.ExpireTimeSpan = TimeSpan.FromDays(7);        options.SlidingExpiration = true;        options.Cookie.Name = "app_auth";        options.Cookie.HttpOnly = true;        options.Cookie.SecurePolicy = CookieSecurePolicy.Always;        options.Cookie.SameSite = SameSiteMode.Lax;    });

ნაგულისხმევი მნიშვნელობები გონივრულია: ExpireTimeSpan 14 დღეა, SlidingExpiration ჩართულია (cookie ხელახლა გაიცემა, როცა მისი სიცოცხლის ნახევარზე მეტი გავიდა), cookie არის HttpOnly, ხოლო SameSite არის Lax. შესვლა principal-ის აწყობა და handler-ისთვის გადაცემაა:

csharp
var claims = new List<Claim>{    new(ClaimTypes.NameIdentifier, user.Id.ToString()),    new(ClaimTypes.Name, user.Email),    new(ClaimTypes.Role, user.Role)};var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,    new ClaimsPrincipal(identity),    new AuthenticationProperties { IsPersistent = rememberMe });

IsPersistent = false ქმნის სესიის cookie-ს, რომელიც ბრაუზერის დახურვისას ქრება; true მას ზემოთ მოცემულ ვადას აძლევს.

ორი რამ აბრკოლებს ხალხს. API endpoint-ისთვის cookie handler-ის ნაგულისხმევი პასუხი არაავთენტიფიცირებულ მოთხოვნაზე 302 გადამისამართებაა შესვლის გვერდზე, რომელსაც JavaScript კლიენტი იღებს როგორც HTML გვერდს 200 სტატუსით. გადაფარე options.Events.OnRedirectToLogin, რომ API-ის გზებისთვის 401 დააბრუნოს. და რადგან ბრაუზერი cookie-ებს ავტომატურად აგზავნის, cookie-ით ავთენტიფიკაციას მდგომარეობის შემცვლელი მოთხოვნებისთვის antiforgery დაცვა სჭირდება: MVC და Razor Pages ფორმებზე ტოკენებს ამოწმებს, ხოლო .NET 8-დან minimal API endpoint-ები, რომლებიც ფორმის მონაცემებს იღებს, დაცულია, როგორც კი app.UseAntiforgery()-ს დაამატებ.

JWT bearer ავთენტიფიკაცია#

bash
$ dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

გარე identity provider-ით - Auth0, Microsoft Entra ID, Keycloak, Duende IdentityServer და მსგავსები - handler-ს issuer-ზე მიმართავ და ის ხელმოწერის გასაღებებს provider-ის მეტამონაცემებიდან ჩამოტვირთავს:

csharp
builder.Services    .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)    .AddJwtBearer(options =>    {        options.Authority = "https://login.example.com/";        options.Audience = "orders-api";        options.MapInboundClaims = false;    });

MapInboundClaims = false claim-ების სახელებს ისე ტოვებს, როგორც ტოკენშია (sub, email, role), ნაცვლად იმისა, რომ ისინი გრძელ ClaimTypes URI-ებად თარგმნოს, და სწორედ ამას ელის ადამიანების უმეტესობა, როცა User.FindFirst("sub")-ს კითხულობს. თუ mapping-ს გამორთავ, უთხარი handler-ს, რომელი claim-ები ატარებს სახელსა და როლებს, TokenValidationParameters.NameClaimType-ისა და RoleClaimType-ის გავლით.

თუ ტოკენებს შენი აპლიკაცია გასცემს, მათ შენს ხელთ არსებული გასაღებით ამოწმებ:

csharp
options.TokenValidationParameters = new TokenValidationParameters{    ValidIssuer = "https://api.example.com",    ValidAudience = "orders-api",    IssuerSigningKey = new SymmetricSecurityKey(        Convert.FromBase64String(builder.Configuration["Jwt:SigningKey"]!)),    ClockSkew = TimeSpan.FromSeconds(30)};

ClockSkew ნაგულისხმევად ხუთი წუთია, რაც ნიშნავს, რომ ვადაგასული ტოკენი მისი exp-ის შემდეგ კიდევ ხუთი წუთი მიიღება. შეამცირე, როცა საათებს ენდობი. ხელმოწერის გასაღები ისეთივე საიდუმლოა, როგორც მონაცემთა ბაზის პაროლი - გარემოს ცვლადი ან საიდუმლოებების საცავი, არასოდეს appsettings.json repository-ში. ASP.NET Core-ის კონფიგურაცია და საიდუმლოებები განიხილავს, სად უნდა ცხოვრობდეს.

საკუთარი JWT-ების გაცემა ნიშნავს, რომ refresh ტოკენები, გაუქმება, გასაღებების როტაცია და კლიენტზე ტოკენის შენახვა შენ გეკისრება. თითოეული გადაჭრადია, და ერთად სწორედ ისინი არის მიზეზი, რის გამოც პატარა პროექტების უმეტესობისთვის რჩევაა "გამოიყენე cookie-ები ან identity provider". თუ ტოკენები გჭირდება და provider-ის გაშვება არ გინდა, შემდეგ სექციაში შუალედური გზაა.

ASP.NET Core Identity#

Identity მომხმარებლების მონაცემთა ბაზა და მის გარშემო არსებული ლოგიკაა: პაროლების hash-ირება, lockout, ელფოსტის დადასტურება, ორფაქტორიანი კოდები, გარე შესვლები და როლები. ის მომხმარებლებს Entity Framework Core-ით ინახავს, ამიტომ მუშაობს SQL Server-თან, PostgreSQL-თან, MySQL-თან ან ნებისმიერ სხვასთან, რასაც EF Core უჭერს მხარს.

csharp
builder.Services.AddDbContext<AppDbContext>(o =>    o.UseSqlServer(builder.Configuration.GetConnectionString("Default")));builder.Services    .AddIdentityApiEndpoints<IdentityUser>()    .AddEntityFrameworkStores<AppDbContext>();var app = builder.Build();app.MapGroup("/account").MapIdentityApi<IdentityUser>();

AddIdentityApiEndpoints და MapIdentityApi, დამატებული .NET 8-ში, გაძლევს JSON API-ს /register-ით, /login-ით, /refresh-ით, ელფოსტის დადასტურებით, პაროლის აღდგენით და ორფაქტორიანი ავთენტიფიკაციის მართვით. შესვლის endpoint აბრუნებს ან cookie-ს (?useCookies=true-ით), ან bearer ტოკენს. ეს ტოკენები JWT-ები არ არის: ისინი გაუმჭვირვალეა, დაცულია ქვემოთ აღწერილი data protection სისტემით, და მათი წაკითხვა მხოლოდ იმ აპლიკაციას შეუძლია, რომელმაც გასცა. ეს უპირატესობაა ერთი აპლიკაციისა და მისი საკუთარი front end-ისთვის; არასწორი ინსტრუმენტია, როცა ერთი და იგივე ტოკენი რამდენიმე სერვისმა უნდა შეამოწმოს.

სერვერზე რენდერებული აპლიკაციებისთვის კლასიკური გზაა AddDefaultIdentity<IdentityUser>() scaffold-ით შექმნილი Razor Pages UI-ით.

Identity-ის ნაგულისხმევი მნიშვნელობების ცოდნა გამოშვებამდე ღირს:

პარამეტრინაგულისხმევიკომენტარი
Password.RequiredLength6გაზარდე; სიგრძე სიმბოლოების კლასებს სჯობს
Password.RequireDigit, RequireUppercase, RequireLowercase, RequireNonAlphanumerictrueბევრი გუნდი ამათ არბილებს და სიგრძეს ზრდის
Lockout.MaxFailedAccessAttempts5მოქმედებს მხოლოდ მაშინ, როცა შესვლა lockoutOnFailure: true-ით იძახება
Lockout.DefaultLockoutTimeSpan5 წუთი
SignIn.RequireConfirmedEmailfalseჩართე, როგორც კი ელფოსტის გაგზავნას შეძლებ
User.RequireUniqueEmailfalseჩვეულებრივ true გინდა

lockout-ის რიგი ის არის, რომელიც ორჯერ უნდა წაიკითხო. SignInManager.PasswordSignInAsync(user, password, isPersistent, lockoutOnFailure: false) - რომელსაც ძველი შაბლონები იყენებდა - ჩავარდნებს საერთოდ არ ითვლის, ამიტომ მასზე აგებულ შესვლის ფორმაზე პაროლის გამოცნობა უსასრულოდ შეიძლება. გადაეცი true და შესვლის endpoint-ის წინ rate limiting-იც დააყენე.

პაროლებს Identity-ის PasswordHasher PBKDF2-ით hash-ავს, ფორმატის მარკერით, ასე რომ ახალ ვერსიებში უფრო ძლიერი პარამეტრები ძველ პაროლებს შემდეგი წარმატებული შესვლისას ხელახლა hash-ავს.

Data protection-ის გასაღებები: რატომ აგდებს ხელახალი deploy ყველას#

auth handler-ის მიერ ჩაწერილი ყოველი cookie, ყოველი antiforgery ტოკენი, ყოველი Identity bearer ტოკენი და პაროლის აღდგენის ყოველი ბმული ASP.NET Core-ის data protection სისტემის გასაღებებით იშიფრება. Linux-ზე ნაგულისხმევად ეს გასაღებები პირველი გამოყენებისას გენერირდება და იწერება ~/.aspnet/DataProtection-Keys-ში - კონტეინერის შიგნით, იმ მომხმარებლის საწყის დირექტორიაში, რომელიც აპლიკაციას უშვებს. თუ ეს დირექტორია ხელახალ build-ს ან deploy-ს ვერ გადაურჩება, ვერც გასაღებები გადაურჩება. ახალი deploy ახალ გასაღებებს აგენერირებს, და ყველაფერი, რაც ძველებით დაიშიფრა, ერთბაშად წაუკითხავი ხდება.

სიმპტომები გამორჩეულია: deploy-ის შემდეგ ყველა მომხმარებელი გამოგდებულია, The antiforgery token could not be decrypted deploy-მდე გახსნილ ფორმებზე, და გაშვებისას ასეთი გაფრთხილებები:

code
warn: Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository[60]      Storing keys in a directory '/root/.aspnet/DataProtection-Keys' that may not be      persisted outside of the container. Protected data will be unavailable when      container is destroyed.warn: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[35]      No XML encryptor configured. Key may be persisted to storage in unencrypted form.

გამოსწორება გასაღებების ისეთ ადგილას შენახვაა, რომელიც პროცესს გადაურჩება. მონაცემთა ბაზა, რომელსაც აპლიკაცია უკვე იყენებს, ყველაზე მარტივი ადგილია:

bash
$ dotnet add package Microsoft.AspNetCore.DataProtection.EntityFrameworkCore
csharp
public class AppDbContext(DbContextOptions<AppDbContext> options)    : IdentityDbContext<IdentityUser>(options), IDataProtectionKeyContext{    public DbSet<DataProtectionKey> DataProtectionKeys => Set<DataProtectionKey>();}builder.Services.AddDataProtection()    .SetApplicationName("orders-web")    .PersistKeysToDbContext<AppDbContext>();

დაამატე migration ახალი ცხრილისთვის და გააკეთე deploy. ალტერნატივებია PersistKeysToStackExchangeRedis (Microsoft.AspNetCore.DataProtection.StackExchangeRedis პაკეტი, რომელიც Valkey-სთანაც მუშაობს, თუ ეს Valkey სერვერი დისკზე ინახავს მონაცემებს) ან PersistKeysToFileSystem დირექტორიით, რომლის შესახებაც იცი, რომ deploy-ებს გადაურჩება. SetApplicationName მნიშვნელოვანია, როცა რამდენიმე ინსტანსმა ან აპლიკაციამ გასაღებები უნდა გაიზიაროს: ნაგულისხმევად აპლიკაციის სახელი content root-ის გზიდან გამოიყვანება, ამიტომ ერთი და იმავე აპლიკაციის ორი ასლი, სხვადასხვა გზაზე deploy-ებული, ერთმანეთის cookie-ებს ვერ წაიკითხავს.

გასაღებები ნაგულისხმევად ყოველ 90 დღეში როტირდება, ხოლო ძველები ინახება, რომ არსებული cookie-ები მაინც გაიშიფროს. შენახული გასაღებებისთვის ProtectKeysWithCertificate მათ X.509 სერტიფიკატით შიფრავს; მის გარეშე ნებისმიერს, ვისაც გასაღებების ცხრილის წაკითხვა შეუძლია, cookie-ების გაყალბებაც შეუძლია, ამიტომ მონაცემთა ბაზა, რომელიც მათ ინახავს, ისეთივე ზრუნვას იმსახურებს, როგორც თავად მომხმარებლების ცხრილი. მონაცემთა ბაზის უსაფრთხოების ჩამონათვალი ამ მხარეს ფარავს.

გარე შესვლები, OpenID Connect და გასვლა#

მომხმარებლებისთვის იმ ანგარიშით შესვლის საშუალების მიცემა, რომელიც უკვე აქვთ - Google, Microsoft, GitHub ან შენი კომპანიის single sign-on - პაროლების შენახვას, აღდგენის პროცესებსა და ორფაქტორიან მოთხოვნებს გადასცემს იმას, ვისი სრული განაკვეთის სამუშაოც ეს არის. ASP.NET Core-ში ეს არის დისტანციური ავთენტიფიკაციის სქემა cookie handler-ის თავზე: დისტანციური სქემა provider-თან მიმოსვლას ამუშავებს, ხოლო cookie სქემა შედეგს იმახსოვრებს.

csharp
builder.Services    .AddAuthentication(options =>    {        options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;        options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;    })    .AddCookie()    .AddOpenIdConnect(options =>    {        options.Authority = "https://login.example.com/";        options.ClientId = builder.Configuration["Oidc:ClientId"];        options.ClientSecret = builder.Configuration["Oidc:ClientSecret"];        options.ResponseType = "code";        options.SaveTokens = false;        options.MapInboundClaims = false;    });

provider-სპეციფიკური პაკეტები - Microsoft.AspNetCore.Authentication.Google, .MicrosoftAccount და სხვები - თხელი შეფუთვებია შევსებული endpoint-ებით. თითოეულ სქემას აქვს callback გზა, რომელზეც provider უკან გადაამისამართებს (/signin-oidc OpenID Connect-ისთვის, /signin-google Google-ისთვის), და ეს ზუსტი URL, https://-ით და შენი რეალური დომენით, provider-თან უნდა იყოს დარეგისტრირებული. TLS-ის დამასრულებელი proxy-ს უკან აპლიკაცია ამ URL-ს იმ მოთხოვნიდან აწყობს, რომელსაც ხედავს; forwarded header-ების გარეშე ის http:// მისამართს აწყობს, provider მას დაურეგისტრირებლად უარყოფს, და შეცდომის გვერდი, რომელსაც ხედავ, provider-ისაა და არა შენი.

SaveTokens = false provider-ის access და refresh ტოკენებს auth cookie-ს გარეთ ტოვებს. შეინახე ისინი მხოლოდ მაშინ, თუ აპლიკაცია მომხმარებლის სახელით provider-ის API-ებს იძახებს, რადგან ისინი cookie-ს ადიდებს - რამდენიმე claim-ით იმდენად, რომ ის chunked cookie-ებად იყოფა.

გასვლა არის HttpContext.SignOutAsync() cookie სქემისთვის, პლუს გასვლა provider-თან, თუ გინდა, რომ მომხმარებელი იქაც გავიდეს. რადგან auth cookie თვითკმარია, მომხმარებლის წაშლა ან მისი როლის შეცვლა უკვე გაცემულ cookie-ზე გავლენას არ ახდენს: ის ვადის გასვლამდე ძალაში რჩება. Identity ამას security stamp-ით ამუშავებს - მნიშვნელობით, რომელიც იცვლება, როცა პაროლი ან უსაფრთხოების პარამეტრები იცვლება, და ნაგულისხმევად ყოველ 30 წუთში ხელახლა მოწმდება (SecurityStampValidatorOptions.ValidationInterval). შეამოკლე ინტერვალი, თუ გაუქმებამ უფრო სწრაფად უნდა იმოქმედოს, ან შეამოწმე "გათიშულის" ალამი შენს cookie-ის ვალიდაციის მოვლენაში მყისიერი ეფექტისთვის. ორფაქტორიანი ავთენტიფიკაცია authenticator აპლიკაციით Identity-ში ჩაშენებულია - UserManager TOTP კოდებსა და აღდგენის კოდებს აგენერირებს და ამოწმებს - და მისი ჩართვა ღირს ყველა ანგარიშზე, რომელსაც ადმინისტრატორის უფლებები აქვს.

ავტორიზაციის პოლიტიკები#

ავთენტიფიკაცია გეუბნება, ვინ; პოლიტიკები წყვეტს, რა. როლები მუშაობს და უხეში წესებისთვის კარგია:

csharp
builder.Services.AddAuthorizationBuilder()    .AddPolicy("Admin", p => p.RequireRole("admin"))    .AddPolicy("CanRefund", p => p.RequireClaim("permission", "refunds"))    .SetFallbackPolicy(new AuthorizationPolicyBuilder()        .RequireAuthenticatedUser()        .Build());app.MapPost("/orders/{id}/refund", Refund).RequireAuthorization("CanRefund");app.MapGet("/health", () => "ok").AllowAnonymous();

fallback პოლიტიკა აქ ყველაზე სასარგებლო ხაზია: ის ყოველ endpoint-ს შესული მომხმარებლის მოთხოვნას უწესებს, თუ ის აშკარად არ არის მონიშნული [AllowAnonymous]-ით ან .AllowAnonymous()-ით. ეს ნაგულისხმევს აბრუნებს "დავიწყებული ატრიბუტი ნიშნავს საჯაროს"-დან "დავიწყებული ატრიბუტი ნიშნავს ჩაკეტილს"-მდე, და სწორედ ამ მიმართულებით გინდა, რომ შეცდომები ჩავარდეს. არ დაგავიწყდეს შესვლის გვერდის, endpoint-ებით მიწოდებული სტატიკური ფაილების და health check-ების ანონიმურად მონიშვნა.

პოლიტიკები, რომლებიც რესურსზეა დამოკიდებული - "მომხმარებლებს მხოლოდ საკუთარი შეკვეთების რედაქტირება შეუძლიათ" - რესურსზე დაფუძნებული ავტორიზაციაა, რომელიც IAuthorizationService.AuthorizeAsync(User, order, "EditOrder")-ით და handler-ით კეთდება, რომელიც შეკვეთას იღებს. ასეთი წესი როლებში ნუ ჩადებ.

პრობლემების მოგვარება#

შევედი და მაშინვე ისევ ანონიმური ვარ. middleware-ის რიგი არასწორია, cookie მონიშნულია secure-ად, მაგრამ აპლიკაციას ჰგონია, რომ მოთხოვნა HTTP-ია (forwarded header-ები აკლია), ან SameSite=Strict cookie-ს აგდებს login provider-იდან უკან cross-site გადამისამართებისას.

`IDX10500: Signature validation failed` ან `IDX10214: Audience validation failed`. ტოკენი სხვა audience-ისთვის გაიცა, ან აპლიკაცია არასწორი გასაღებით ან authority-ით ამოწმებს. ლოკალურად გაშიფრე ტოკენი და შეადარე iss და aud შენს პარამეტრებს.

ყოველი deploy-ის შემდეგ ყველა გამოგდებულია. data protection-ის გასაღებები არ ინახება. იხილე ზემოთ.

`User.Identity.Name` null-ია JWT-ებით. claim-ების mapping გამორთულია და NameClaimType არ არის დაყენებული იმ claim-ზე, რომელსაც შენი ტოკენები იყენებს.

OAuth callback correlation შეცდომით ვარდება. correlation cookie მიმოსვლას ვერ გადაურჩა - ხშირად იმიტომ, რომ აპლიკაციამ TLS-ის დამასრულებელი proxy-ს უკან http:// redirect URI დააგენერირა. ჯერ forwarded header-ები გაასწორე.

FAQ#

უსაფრთხოა JWT-ის localStorage-ში შენახვა?

მუშაობს, და შენს გვერდზე გაშვებულ ნებისმიერ სკრიპტს მისი წაკითხვა შეუძლია - ამიტომ ერთი cross-site scripting ხარვეზი ყველა ტოკენს ჟონავს. HttpOnly cookie-ს სკრიპტები ვერ წაიკითხავს. ბრაუზერის აპლიკაციისთვის, რომელიც თავისი API-ის იმავე დომენზეა, cookie-ები უფრო უსაფრთხო ნაგულისხმევია.

მჭირდება ASP.NET Core Identity?

მხოლოდ მაშინ, თუ შენი აპლიკაცია საკუთარ მომხმარებლებსა და პაროლებს ინახავს. თუ მომხმარებლები გარე provider-ით შედიან, ან single sign-on სისტემით, რომელსაც უკვე უშვებ, cookie-ს ან JWT-ის handler-იც საკმარისია და Identity ამატებს ცხრილებს, რომლებსაც არ გამოიყენებ.

საკმარისად დიდხანს, რომ ხალხი არ გაღიზიანდეს, და საკმარისად მოკლედ, რომ მოპარული ლეპტოპი მუდმივი სესია არ იყოს. შვიდიდან თოთხმეტ დღემდე sliding expiration-ით საიტების უმეტესობას ერგება; ადმინისტრაციული ზონები უფრო მოკლე სიცოცხლესა და ორფაქტორიან ავთენტიფიკაციას იმსახურებს.

რატომ ვიღებ antiforgery შეცდომებს მხოლოდ deploy-ის შემდეგ?

იმიტომ, რომ ტოკენი data protection-ის გასაღებით დაიშიფრა, რომელიც აღარ არსებობს. შეინახე გასაღებები მონაცემთა ბაზაში ან სხვა საცავში, რომელიც deploy-ებს გადაურჩება, და შეცდომები შეწყდება.

შეუძლია ჩემი აპლიკაციის ორ ინსტანსს შესვლების გაზიარება?

კი, თუ ისინი data protection-ის გასაღებებსა და ერთსა და იმავე აპლიკაციის სახელს იზიარებენ. შეინახე გასაღებები საერთო მონაცემთა ბაზაში ან Valkey-ში და გამოიძახე SetApplicationName ერთი და იმავე მნიშვნელობით ორივეზე.


კომენტარები

სრულიად ანონიმურად: ანგარიშის, ელფოსტის და cookie-ის გარეშე. ინახება მხოლოდ სახელი, ტექსტი და დრო - სხვა არაფერი. ბმულების რაოდენობა ლიმიტირებულია.

0/2000