Security
CSRF protection
Bulletcode.NET relies on the standard ASP.NET Core mechanism of CSRF protection. This mechanism is automatically enabled when the application calls the AddControllersWithViews()
extension method of IServiceCollection
.
The BaseController
class, which should be inherited by all web controllers of the application (except API controllers), has the AutoValidateAntiforgeryToken
, which means that validation of antiforgery tokens is automatically applied to all actions using unsafe HTTP methods, other than GET. When the antiforgery token is missing or invalid, a 400 error is automatically returned. See the MVC chapter for more information.
When you use a <form>
tag in a Razor view, the tag helper which is built into ASP.NET Core automatically adds a hidden input field containing the antiforgery token.
Bulletcode.NET makes it possible to create simple action buttons, which trigger a POST
request to the specified URL in order to perform an action with a side effect, such as deleting an object, logging out, etc. A custom Tag Helper for link tags with asp-post
attribute converts the link into a button. The PostButton
JavaScript handler, which is automatically attached to such button, automatically appends the antiforgery token to the form data. This protects such action from CSRF attacks.
WARNING
The application should never use plain links to invoke actions which have side effects, because GET
requests cannot be protected from CSRF attacks.
The PostButton
handler retrieves the token from <meta>
tags with csrf-param
and csrf-token
names. In order for this mechanism to work, these tags should be included in the page header. For example:
@{
var csrfTokens = User.Identity.IsAuthenticated ? Antiforgery.GetAndStoreTokens( Context ) : null;
}
@if ( csrfTokens != null )
{
<meta name="csrf-param" content="@csrfTokens.FormFieldName">
<meta name="csrf-token" content="@csrfTokens.RequestToken">
}
NOTE
The GetAndStoreTokens()
method prevents the page from being cached, so make sure that it is only called when the token is actually needed. In the example above, it is only generated when the user is logged in.
In applications using the ClientView architecture, you can use the PostButton
component to achieve a similar effect. This component performs the request using the client-side router service, which automatically adds the antiforgery token to all POST
requests. The token is automatically injected into client view data when the user is authenticated or when the ClientFormView()
method is called.
Open redirect protection
To prevent from open redirect attacks, the application should use LocalRedirect()
instead of Redirect()
when the redirect URL is provided by user input, such as a query string parameter.
For example, the /Account/Login
action accepts a returnUrl
parameter, containing the target page to which the user is redirected after logging in:
[HttpPost]
public async Task<IActionResult> Login( LoginViewModel model, string returnUrl = null )
{
if ( ModelState.IsValid )
{
if ( await _accountService.SignInAsync( HttpContext, model.Email, model.Password, model.RememberMe ) )
return returnUrl != null ? LocalRedirect( returnUrl ) : RedirectToAction( "Index", "Home" );
ModelState.AddModelError( nameof( model.Email ), _translator._( "Invalid email address or password." ) );
}
return View( model );
}
Using LocalRedirect()
ensures that the user is not redirected to an external site after logging in.
When using the ClientRedirect()
method with a URL coming from user input in a ClientView application, call Url.IsLocalUrl()
to make sure that the URL is local.
XSS protection
Razor views in ASP.NET Core provide XSS protection by automatically encoding all output sourced from the view model and other variables.
Custom Tag Helpers and View Components provided by Bulletcode.NET are also XSS-safe. All string parameters passed to tags and view components are automatically encoded. This is ensured by using internally such classes as TagBuilder
and HtmlContentBuilder
.
The Grid
view component can be used with columns which render plain text values, which are automatically encoded, or with column which render HTML. To make this possible, the GridOptionsBuilder
class contains two overloads of AddColumn()
methods — one using a string
, and another one using an IHtmlContent
. This way, an unencoded string is never misinterpreted as HTML content.
WARNING
In order to securely generate IHtmlContent
, the application should also use TagBuilder
and HtmlContentBuilder
. Make sure to never pass unencoded input to the AppendHtml()
method of HtmlContentBuilder
.
When building URLs, it is recommended to use the Action()
method of the IUrlHelper
service, which ensures that all parameters are properly encoded. You can also use the custom AppendQueryString()
extension method to merge the specified set of query string parameters which the existing query string of the current page. When a custom URL must be created, make sure to use the UrlEncoder
to encode values coming from untrusted input.
In order to generate inline JavaScript code, it is recommended to use the JsSerializer
class described in the JSON Serialization chapter. This ensures that all data is encoded in JSON format which can be safely placed in a <script>
tag.
Cookies
Bulletcode.NET creates the following cookies related to security:
- Authentication cookie (by default,
".BC.User"
) — see Authentication - Antiforgery cookie (by default,
".BC.Antiforgery"
) — see above - Session cookie (by default,
".BC.Session"
) — see Session
All these cookies are created on the server side by ASP.NET Core with the following CookieBuilder
options to ensure that they are secure:
HttpOnly
—true
for all cookiesSecurePolicy
—SameAsRequest
for all cookiesSameSite
—Strict
for the authentication cookie,Lax
for other cookies
Bulletcode.NET overrides some of these settings for the session cookie to make it more secure. Other cookies use default options configured by ASP.NET Core.
In addition, Bulletcode.NET uses the following cookies which store user preferences:
- Language cookie (by default,
".BC.Language"
) — see Internationalization - Font size cookie (by default,
".BC.FontSize"
) — see View Components
The font size cookie is created by the client-side script for the ApplicationHeader
view component and its client-view counterpart when the user changes the font size. The language cookie is not created by the framework, but can be created by the application to override the default language.
Password hashing
Local users are authenticated using a password which is stored securely as a hash in the database. The IAccountService
uses the IUserPasswordService
in order to check and update password hashes stored in the database. However, the hash itself is calculated and verified using the IPasswordHasher
service which is part of the ASP.NET Core Identity API. Although Bulletcode.NET doesn’t use the Identity API to manage users, this particular service is registered when the AddUserService()
method is called, as described in the Database chapter.
Using the standard password hashing algorithm ensures that the hashes are compatible with other ASP.NET Core applications, and provides maximum security.
The view models related to user management, which are part of Bulletcode.NET, contain a validation rule which ensures that passwords have a length of at least 10 characters. However, no additional password requirements are implemented. Applications which require a strict password policy should implement additional validation.
NOTE
For maximum security, it is recommended to use an external authentication provider, such as Entra, which provides additional benefits, such as support for multi-factor authentication.