Include custom WWW-Authenticate header in 401 Unauthorised response when using Microsoft.Identity.Web - asp.net-core

Following instructions on making MS Office connect to my Asp.NET Core Web API, I am attempting to present a login redirect to MS Office for failed authentications. Following questions and answers I am attempting to include the login redirect information in the WWW-Authenticate header property. My Web API is protected with Azure AD and the Microsoft.Identity.Web library. When the authentication fails and the middleware returns the 401 Unauthorized response, the header does include the WWW-Authenticate property but it's value is only Bearer.
Q: How can update the header information to include the necessary additional redirect information?
I have tried to implement an attribute on the API, derived from IAsyncAuthorizationFilter and access the response header in that. However the middleware already returns a 401 before this attribute is called.

I have made progress by customizing the JwtBearerOptions configuration. However this approach creates an additional header item, instead of overwriting the standard value. As a result I have now 2 KeyValuePairs for the same key in the response header, which will likely have unexpected outcomes.
In my Startup.cs:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(Configuration)
services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options =>
{
var existingOnChallengeHandler = options.Events.OnChallenge;
options.Events.OnChallenge = async context =>
{
await existingOnChallengeHandler(context);
string headerInfo = context.Options.Challenge;
headerInfo += " resource=\"https://management.azure.com/\"";
context.Response.Headers.Append(HeaderNames.WWWAuthenticate, headerInfo);
};
});

The original answer put me on the right track. It turned out to be actually quite simple to do this once I knew to configure the JwtBearerOptions.Challenge property:
services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.Challenge = $"Bearer authorization_uri=\"{authorizationUri}\"";
}

Related

OpenIddict support returning authorization code via GET request for postman

I have set up an Authorization Server using OpenIddict 3.1.1 (porting over an existing one that was using the older ASOS package directly). I believe I am most of the way there, because when using the client application, I am able to log in, give consent, redirect back to the client, and exchange the authorization code for an access token.
However, when I try to do the same using Postman's OAuth 2.0 authentication support, I am able to log in (and give consent), but when it completes and returns the authorization code, I receive an HTTP 403 from the https://oauth.pstmn.io/v1/callback that I am redirected to:
403 ERROR
The request could not be satisfied.
This distribution is not configured to allow the HTTP request method that was used for this request. The distribution supports only cachable requests. We can't connect to the server for this app or website at this time. There might be too much traffic or a configuration error. Try again later, or contact the app or website owner.
If you provide content to customers through CloudFront, you can find steps to troubleshoot and help prevent this error by reviewing the CloudFront documentation.
Generated by cloudfront (CloudFront)
Request ID: UAXpago6ISiqbgm9U_SVPwh96qz1qoveZWFd0Cra-2FximeWZiY2aQ==
From what I can tell, this is because OpenIddict is issuing a POST request back to the callback url. This works for my client application, but evidently is not supported by Postman.
What configuration tweak do I need to make to OpenIddict to support this in postman?
OpenIddict related config in Startup.ConfigureServices:
services.AddOpenIddict()
.AddCore(options => {
options.AddApplicationStore<ClientStore>();
options.UseEntityFramework()
.UseDbContext<OAuthServerDbContext>()
.ReplaceDefaultEntities<Client, Authorization, OAuthScope, Token, long>()
;
})
.AddServer(options => {
options.RegisterClaims();
options.RegisterScopes(OpenIddictConstants.Scopes.OpenId,
OpenIddictConstants.Scopes.Email,
OpenIddictConstants.Scopes.OfflineAccess,
OpenIddictConstants.Scopes.Profile,
"user");
// flows
options.AllowAuthorizationCodeFlow();
options.AllowRefreshTokenFlow();
options.AllowPasswordFlow();
options.AllowHybridFlow();
// implicit is used by postman
options.AllowImplicitFlow();
var serviceProvider = options.Services.BuildServiceProvider();
var oauthConstants = serviceProvider.GetRequiredService<IOptions<OAuthConstants>>().Value;
var tokenLifetimes = serviceProvider
.GetRequiredService<IOptions<OpenIdConnectServerTokenLifetimeSettings>>().Value;
// security
options.SetAccessTokenLifetime(tokenLifetimes.AccessTokenLifetime)
.SetAuthorizationCodeLifetime(tokenLifetimes.AuthorizationCodeLifetime)
.SetIdentityTokenLifetime(tokenLifetimes.IdentityTokenLifetime)
.SetRefreshTokenLifetime(tokenLifetimes.RefreshTokenLifetime);
options.SetIssuer(new Uri("https://localhost/oauth/"));
// custom handlers added here
options.AddEventHandlers();
// certificate details hidden
options.AddEncryptionCertificate(certificate);
// endpoints
options.SetAuthorizationEndpointUris("/OpenIdConnect/Authorize");
options.SetLogoutEndpointUris("/OpenIdConnect/Logout", "/Account/Logout");
options.SetRevocationEndpointUris("/OpenIdConnect/Revoke");
options.SetTokenEndpointUris("/OpenIdConnect/Token");
options.SetCryptographyEndpointUris("/OpenIdConnect/JWKDoc");
options.SetUserinfoEndpointUris("/OpenIdConnect/UserInfo");
options.UseAspNetCore()
.EnableStatusCodePagesIntegration()
.EnableAuthorizationEndpointPassthrough()
//.EnableTokenEndpointPassthrough()
.EnableLogoutEndpointPassthrough()
.EnableUserinfoEndpointPassthrough()
;
})
.AddValidation(options => {
options.UseLocalServer();
options.UseAspNetCore();
var serviceProvider = options.Services.BuildServiceProvider();
var config = serviceProvider.GetRequiredService<IConfiguration>();
options.SetClientId(config.GetValue<string>(nameof(Settings.OAuthClientId)));
options.SetClientSecret(config.GetValue<string>(nameof(Settings.ClientSecret)));
// certificate details hidden
options.AddEncryptionCertificate(certificate);
});
Postman details:
Authorization
Token Name: Redacted
Grant Type: Authorization Code
Callback URL: disabled, https://oauth.pstmn.io/v1/callback
Authorize using browser: checked
Auth URL: https://localhost/oauth/OpenIdConnect/Authorize
Access Token URL: https://localhost/oauth/OpenIdConnect/Token
Client ID: redacted, but correct
Client Secret: redacted, but correct
Scope: openid offline_access
State:
Client Authentication: Send client credentials in body
edit: The response that it sends to the postman callback URI does include the authorization code in the body, but because of the 403 response, Postman doesn't parse that out and make the follow-up request to exchange the code for the token.
There is an option that you can set to control if the authorization code is received in the URL as a query string or in the body as a post. The option is response_mode and you control that as a client.
I believe if it is not set to response_mode=form_post, then you will get the code in the URL instead.
See the details about this parameter here.

ASP.NET Core : Return Json response on Unauthorized in a filter at the controller/action level

I am not using Identity.
I have this ASP.NET Core configuration enabling two authentication schemes, cookies and basic auth:
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.Cookie.Name = "_auth";
options.Cookie.HttpOnly = true;
options.LoginPath = new PathString("/Account/Login");
options.LogoutPath = new PathString("/Account/LogOff");
options.AccessDeniedPath = new PathString("/Account/Login");
options.ExpireTimeSpan = TimeSpan.FromHours(4);
options.SlidingExpiration = true;
})
.AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>("BasicAuthentication", null);
BasicAuthenticationHandler is a custom class inheriting from AuthenticationHandler and overriding HandleAuthenticateAsync to check the request headers for basic authentication challenge, and returns either AuthenticateResult.Fail() or AuthenticateResult.Success() with a ticket and the user claims.
It works fine as is:
Controllers/Actions with the [Authorize] tag will check the cookies and redirect to the login page is not present.
Controllers/Actions with the [Authorize(AuthenticationSchemes = "BasicAuthentication")] tag will check the header and reply a 401 Unauthorized HTTP code if not present.
Controllers/Actions with the [Authorize(AuthenticationSchemes = "BasicAuthentication,Cookies")] tag will allow both methods to access the page, but somehow use the Cookies redirection mechanism when failing both checks.
My goal is to have most of my project to use Cookies (hence why it is set as default), but have some API type of controllers to accept both methods. It should also be possible to tag the Controllers/Actions to return a specific Json body when desired (as opposed to the login redirect or base 401 response), but only for certain controllers.
I've spent the last 2 days reading different similar questions and answers here on StackOverflow, nothing seems to accommodate my need.
Here's a few methods I found:
The options under AddCookie allow you to set certain events, like OnRedirectToAccessDenied and change the response from there. This does not work because it applies to the whole project.
Under my BasicAuthenticationHandler class, the AuthenticationHandler class allow to override HandleChallengeAsync to change the response from there instead of replying 401. Unfortunately, again it applies globally to everywhere you use the scheme, not on a controller/action level. Not sure if it's applied when mixing multiple schemes either.
Many answers point to adding a Middleware to the solution, again, it impacts the whole project.
Many answers point to Policies, but it seems to be to control whether or not an user have access to the resource based on claims, not controlling the response when he do not.
Many answers suggest creating a class inheriting from AuthorizeAttribute, IAuthorizationFilter. Again, this allow to override the OnAuthorization method to decide if the user have the right or not to access the resource, but not to control the response AFTER the normal authentication scheme failed.
I'm thinking either there's a filter type I'm missing, or maybe I need to create a third authentication type that will mix the previous two and control the response from there. Finding a way to add a custom error message in the options would also be nice.
I managed to do it via a IAuthorizationMiddlewareResultHandler. Not my favorite approach because there can be only one per project and it intercepts all calls, but by checking if a specific (empty) attribute is set, I can control the flow:
public class JsonAuthorizationAttribute : Attribute
{
public string Message { get; set; }
}
public class MyAuthorizationMiddlewareResultHandler : IAuthorizationMiddlewareResultHandler
{
private readonly AuthorizationMiddlewareResultHandler DefaultHandler = new AuthorizationMiddlewareResultHandler();
public async Task HandleAsync(RequestDelegate requestDelegate, HttpContext httpContext, AuthorizationPolicy authorizationPolicy, PolicyAuthorizationResult policyAuthorizationResult)
{
// if the authorization was forbidden and the resource had specific attribute, respond as json
if (policyAuthorizationResult.Forbidden)
{
var endpoint = httpContext.GetEndpoint();
var jsonHeader = endpoint?.Metadata?.GetMetadata<JsonAuthorizationAttribute>();
if (jsonHeader != null)
{
var message = "Invalid User Credentials";
if (!string.IsNullOrEmpty(jsonHeader.Message))
message = jsonHeader.Message;
httpContext.Response.StatusCode = 401;
httpContext.Response.ContentType = "application/json";
var jsonResponse = JsonSerializer.Serialize(new
{
error = message
});
await httpContext.Response.WriteAsync(jsonResponse);
return;
}
}
// Fallback to the default implementation.
await DefaultHandler.HandleAsync(requestDelegate, httpContext, authorizationPolicy, policyAuthorizationResult);
}
}
I was typing this on comment... but it's doesn't fit... so here is something we probably need to make clear before choosing a solution:
Authorization process happen at the upper middleware above controller
Yes, AuthorizationMiddleware was registered when we use app.UseAuthorization();, that quite far above controller layer, so it was returned long before the request can reach controller, so, any type of filter cannot be applied here.
Not specify an authentication scheme or policy would easily lead to un-stable behavior.
Imagine, Authentication process return an instance of User that stick with the request, but what would happen if the permission on cookie and basicAuth was difference, like cookie have myclaim, while basicAuth doens't ? Related process on both type of scheme was difference (like challenge on cookie would lead to /Account/Login and basicAuth to /Login ?). And various logic case that we could implement on each page.
I Know, this is not possible, but it would become a mess, not for the author of these code, but for those maintainers to come.
Json response for some specific process on client ?
This might sound detailed at first glance, but it would rather become burden soon, if some more authentication requirement raise after that (like Jwt). Covering each of these case on client would make user experience quite awkward (like, half-authentication and authorization).
And if It's un-avoidable in the project. Might I suggest create a default authentication scheme with ForwardDefaultSelector that would elected which authentication scheme to use for each request. And maintain a stable routing HashSet that would use to detect on which endpoint to set Json Response as wished on some upper level than AuthorizationMiddleware, by using middleware, ofcourse. Then, we narrow down to 2 centralize places to checkout the authorization.
Chaos came when we tried to make one thing to do somethings. At least in this case, I think we would breath easier when coming to debug phase.

What is Code Сhallenge in query param in authorization server like IdentityServer (from JS SPA client pov)?

When I do manual redirect, I'm getting an error from IdentityServer
invalid_request, code challenge required
However when I use oidc-client-js library for the same authorization request, I do not get that error. Library somehow sets code challenge under the hood.
Here is me JS code.
Set up:
const config = {
authority: "https://demo.identityserver.io",
client_id: "interactive.confidential",
redirect_uri: "http://localhost:3000/callback",
response_type: "code",
scope:"openid profile email api offline_access",
post_logout_redirect_uri : "http://localhost:3000/post_logout",
};
const url = `https://demo.identityserver.io/connect/authorize?
client_id=${config.client_id}&
redirect_uri=${config.redirect_uri}&
response_type=${config.response_type}&
scope=${config.scope}`;
My manual authorization redirect request that throws:
const onFormSubmit = async (ev: React.FormEvent) => {
ev.preventDefault();
window.location.replace(url); // I simply do replace
}
Code with the library that doesn't throw:
import Oidc from 'oidc-client';
const onFormSubmit = async (ev: React.FormEvent) => {
ev.preventDefault();
const mgr = new Oidc.UserManager(config);
mgr.signinRedirect(); // login redirect here, no errors
}
I want to understand what code challengem is. And how it gets generated. Give me a hint what to read about it.
I ca go on with the library, but I'd prefer not to import third-party libs into my app where possible.
Authorize Endpoint handle multiple grant types, the way you are sending your request, matched to Authorization Code Grant which needs code_challenge parameter during the request.
Try something simpler to make a request like:
GET /connect/authorize?
client_id=client1&
scope=openid email api1&
response_type=id_token token&
redirect_uri=https://myapp/callback&
state=abc&
nonce=xyz
Read Authorize Endpoint for more information.
Heres an example of generating a challenge code:
private string CreateCodeChallenge()
{
_codeVerifier = RandomNumberGenerator.CreateUniqueId();
var sha256 = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithm.Sha256);
var challengeBuffer = sha256.HashData(
CryptographicBuffer.CreateFromByteArray(Encoding.UTF8.GetBytes(_codeVerifier)));
byte[] challengeBytes;
CryptographicBuffer.CopyToByteArray(challengeBuffer, out challengeBytes);
return Base64Url.Encode(challengeBytes);
}
Include the code and the method in the request querystring.
You can generate codes for testing here: https://tonyxu-io.github.io/pkce-generator/
That's as far I've gotten with it but I am shown the login screen.
It's a parameter required by the Proof Key for Code Exchange standard.
OAuth 2.0 public clients utilizing the Authorization Code Grant are susceptible to the authorization code interception attack. This specification describes the attack as well as a technique to mitigate against the threat through the use of Proof Key for Code Exchange (PKCE, pronounced "pixy").

Getting 401 when calling ASP.NET Core 2.1 API. Is this a CORS issue?

I've been trying to resolve a 401 error for the past couple days without any success.
ASP.NET Core 2.1 API hosted behind IIS. I'm trying to access the API with windows authorisation but I'm being challenged with a login prompt. If I don't enter a username and password I get a 401 error (screenshot attached). I've followed all the articles I could find and believe I have CORS configured correctly.
Based on the screenshot does this look like a CORS issue? I'm testing via swagger and am calling from what I believe is the same domain. Does anyone have any suggestions regarding what the issue may be?
From what I see in this screenshot, everything works fine. 401 is a desirable error in this scenario, it is also proof that you don't have any problems with CORS because the API responds to your requests in an adequate way.
To break through to Api you should focus on the "Response Headers" section in which the type of authentication is defined as BEARER.
From this we can conclude that authentication is token based and in practice works as follows:
By correctly logging in through Windows Authentication, WebAPI provides a response token in header that identifies you as a user.
In order to have access to API, you should store this token locally, and then make use of it by adding it to header section of each request.
To learn more about token based authentication in swagger, check
https://swagger.io/docs/specification/authentication/bearer-authentication/
To understand how tokens works, check https://jwt.io/
Below is an example of how to achieve the intended goal by configuring swagger in the startup class of asp net core application.
public void ConfigureServices(IServiceCollection services)
{
//other code removed for brevity
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "My App API", Version = "v1" });
c.CustomSchemaIds((type) => type.FullName);
c.DescribeAllEnumsAsStrings();
c.DescribeAllParametersInCamelCase();
c.EnableAnnotations();
c.OperationFilter<FormFileOperationFilter>();
var apiXmlDocFileName = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var apiXmlDocFilePath = Path.Combine(AppContext.BaseDirectory, apiXmlDocFileName);
c.IncludeXmlComments(apiXmlDocFilePath);
c.AddFluentValidationRules();
c.AddSecurityDefinition("Bearer", new ApiKeyScheme() //this is desireable line
{
Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"",
Name = "Authorization",
In = "header"
});
c.AddSecurityRequirement(new Dictionary<string, IEnumerable<string>> { { "Bearer", Enumerable.Empty<string>() } });
});
}
After implementing this you will be able to add the token to all requests directly from the swagger UI.
You can also achieve the intended goal using Postman
https://learning.getpostman.com/docs/postman/sending-api-requests/authorization/
Hope it Helps.

ValidateAntiForgeryToken in an ASP.NET Core React SPA Application

I'm trying to use the framework's tools to add some simple CSRF validation to an ASP.NET Core React SPA. The application itself is essentially a create-react-app setup (a single index.html with a root element and everything else is loaded in from bundled JavaScript).
Tinkering with some information found on links such as this one, I've set the following in my Startup.ConfigureServices:
services.AddAntiforgery(options => options.Cookie.Name = "X-CSRF-TOKEN");
And confirmed in my Chrome tools that the cookie is being set. If I omit the above line, a cookie is still set with a partially randomized name, such as: .AspNetCore.Antiforgery.RAtR0X9F8_w Either way the cookie is being set. I've also confirmed that any time I re-start the whole application the cookie value is updated, so the framework is actively setting this cookie.
Observing network requests in my Chrome tools, I confirm that the cookie is being sent to the server on AJAX request. Placing a breakpoint on the server and observing the Request.Cookies value in a controller action also confirms this.
However, if I decorate any such AJAX requested action with [ValidateAntiForgeryToken] then the response is always an empty 400.
Is there a configuration step I've missed somewhere? Perhaps the action attribute is looking in the wrong place and I need to use a different validation?
I just inspect the log and find out there's an exception:
Microsoft.AspNetCore.Antiforgery.AntiforgeryValidationException: The required antiforgery cookie ".AspNetCore.Antiforgery.HPE6W9qucDc" is not present.
at Microsoft.AspNetCore.Antiforgery.Internal.DefaultAntiforgery.ValidateRequestAsync(HttpContext httpContext)
at Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ValidateAntiforgeryTokenAuthorizationFilter.OnAuthorizationAsync(AuthorizationFilterContext context)
It indicates that you forgot to configure the cookie name :
public void ConfigureServices(IServiceCollection services)
{
//services.AddAntiforgery();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
// In production, the React files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/build";
});
}
So I just add a configuration as below :
public void ConfigureServices(IServiceCollection services)
{
services.AddAntiforgery(o => {
o.Cookie.Name = "X-CSRF-TOKEN";
});
// ...
}
and it works now.
Also, if you would like to omit the line of services.AddAntiforgery(options => options.Cookie.Name = "X-CSRF-TOKEN"); , you can use the built-in antiforgery.GetAndStoreTokens(context) method to send cookie:
app.Use(next => context =>
{
if (context.Request.Path == "/")
{
//var tokens = antiforgery.GetTokens(context);
var tokens = antiforgery.GetAndStoreTokens(context);
context.Response.Cookies.Append("X-CSRF-TOKEN", tokens.CookieToken, new CookieOptions { HttpOnly = false });
context.Response.Cookies.Append("X-CSRF-FORM-TOKEN", tokens.RequestToken, new CookieOptions { HttpOnly = false });
}
return next(context);
})
Both should work as expected.
The accepted answer here is extremely incorrect when it suggests to send both cookies via JS-readable cookies:
// do not do this
context.Response.Cookies.Append("X-CSRF-TOKEN", tokens.CookieToken, new CookieOptions { HttpOnly = false });
context.Response.Cookies.Append("X-CSRF-FORM-TOKEN", tokens.RequestToken, new CookieOptions { HttpOnly = false });
If you send both the Cookie token and the Request token in a Cookie that is readable by JS, you are defeating the purpose of having a Cookie token and a Request token.
The purpose of using both tokens is to make sure that
you have a valid session (the HTTP-only Cookie proves this),
you have requested a form from the site using this valid session (the HTTP-readable Cookie or another method can prove this), and
you are submitting the form from the same valid session
Why It's Wrong.
The Request Token
The Request Token ensures that you have actually loaded a page (example.com/example-page). Think about this: if you are logged in to example.com as an administrator, a request from anywhere from your browser (where CORS allows the necessary properties) can successfully validate against Cookie-based CSRF Validation and your authentication.
However, by adding the Request Token, you are confirming that your browser also actually loaded a request to the form (or at least, the site) before submitting it. This is usually done with a hidden input. This is automatically done by using the Form Tag Helper in Asp.Net.
<form action="/myEndpoint" method="POST">
<input name="__RequestVerificationToken" type="hidden" value="#antiforgery.GetAndStoreTokens(context).RequestToken" />
<button type="submit">Submit</button>
</form>
It can also be set .. anywhere. like window.CSRFRequestToken, and manually added to a POST request, like in this fetch example:
fetch('/myEndpoint', { method: 'POST', headers: { 'X-XSRF-Token': window.myCSRFRequestToken, 'Bearer': window.mySuperSecretBearerToken } };
The Cookie Token
In the above contrived example, the user is logged in via a bearer token via OAuth or something (not recommended, use HTTP-only Cookies in a browser environment).
The Cookie Token ensures that a malicious script cannot exfiltrate your Request Token and send requests on your behalf. Without it, in a supply chain attack, a malicious user can send your secrets to a malicious actor:
window.addEventListener('load', => sendMySuperSecretInfoToTheShadowRealm(window.CSRFRequestToken, window.mySuperSecretBearerToken));
Now the malicious user could send a request from wherever they want using your CSRF and bearer token to authenticate. BUT! Not if you have your good friend HTTP-only Cookie-based CSRF Validation -- because JavaScript cannot read HTTP-only cookies.
The Solution
Asp.Net combines these solutions by setting both a Cookie Token and a Request Token. Therefore, when you are sending a request to AspNet you send both:
The cookie:
Cookies.Append('X-CSRF-Token', #antiforgery.GetAndStoreTokens(context).CookieToken);
and either the aspnet form helper tag:
<form action="myEndpoint" />
or manually print the token:
<form action="myEndpoint" asp-antiforgery="false">
#Html.AntiForgeryToken()
</form>
or provide the token manually to your scripts:
window.myCSRFRequestToken = "#antiforgery.GetAndStoreTokens(context).RequestToken)";
fetch('/myEndpoint', { method: 'POST', headers: { 'X-CSRF-Token': window.myCSRFRequestToken };
Don't take my word for it
Please please read this page fully in case I didn't explain anything clearly:
https://learn.microsoft.com/en-us/aspnet/core/security/anti-request-forgery?view=aspnetcore-6.0
A final note:
In the documentation above, the very last example uses a cookie to send the request cookie. This is very different in a subtle way than the answer here. The accepted answer sends both cookies as Javascript-readable { HttpOnly = false }. This means JavaScript can read both and a malicious user can read both and craft a special request themselves that will validate against both Cookie and Request CSRF validations (where CORS allows).
In the documentation, one is sent via an HTTP only cookie (this cannot be read by JS, only used for Cookie-based CSRF validation) and the other is sent via an HTTP-readable cookie. This HTTP-readable cookie MUST be read by JavaScript and used with one of the above methods (form input, header) in order to validate CSRF Request Token Validation.