Preventing XSS Attacks in ASP.NET Core Web API

Cross-site scripting (XSS) is one of the most common vulnerabilities found in web applications. It occurs when malicious scripts are injected into web pages or APIs and executed by the victim's browser, potentially compromising sensitive information. This article will explain how to prevent XSS attacks in an ASP.NET Core Web API by sanitizing inputs, encoding outputs, and applying security best practices.

What is XSS?

XSS attacks occur when an attacker injects malicious scripts into content that is sent to users without proper validation or encoding. In the context of Web APIs, the API may process user input and return it to clients (browsers or other consumers), which could lead to script execution and malicious activities.

Example of an XSS Attack

Imagine a Web API that accepts user input, such as a name, and returns it back to the client.

{
    "name": "<script>alert('XSS Attack!');</script>"
}

If the API does not sanitize this input, the malicious JavaScript (<script>alert('XSS Attack!');</script>) will be executed in the client’s browser.

Types of XSS Attacks

  1. Stored XSS: Malicious scripts are stored in the database or file system and executed when the victim visits the page that retrieves and displays the data.
  2. Reflected XSS: Malicious scripts are embedded in the URL and executed when the victim clicks the link.
  3. DOM-Based XSS: The vulnerability is within the client-side JavaScript itself.

Preventing XSS in ASP.NET Core Web API

Here’s a step-by-step guide to protect your API from XSS attacks.

1. Input Validation and Data Annotations

The first line of defense against XSS is to validate user inputs using model validation and constraints.

In ASP.NET Core, you can use Data Annotations to specify validation rules for models. For example, a user registration API might look like this:

public class UserInput
{
    [Required]
    [MaxLength(50)]
    [RegularExpression(@"^[a-zA-Z0-9]*$", ErrorMessage = "Invalid characters in name")]
    public string Name { get; set; }

    [Required]
    [EmailAddress]
    public string Email { get; set; }
}

By applying these annotations, the API ensures that only valid data is accepted and limits the possibility of malicious scripts getting through.

Example API Controller

[ApiController]
[Route("api/[controller]")]
public class UserController : ControllerBase
{
    [HttpPost]
    [Route("create")]
    public IActionResult CreateUser([FromBody] UserInput userInput)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }
        return Ok(new { Message = "User created successfully!" });
    }
}

In the above example, the input Name is restricted to alphanumeric characters, which limits the possibility of script injection.

2. Sanitizing User Input

Even with validation in place, you should sanitize inputs that could potentially be harmful. ASP.NET Core provides various ways to sanitize and encode user inputs.

  • Using the HtmlEncoder Class: You can use the HtmlEncoder class to encode dangerous characters before processing the input.
    using System.Text.Encodings.Web;
    public string SanitizeInput(string input)
    {
        return HtmlEncoder.Default.Encode(input);
    }
    
  • This encodes any special characters like <, >, or & that could be used for XSS attacks.
  • Example Usage in API
    [HttpPost]
    [Route("sanitize")]
    public IActionResult SanitizeUserInput([FromBody] string userInput)
    {
        var sanitizedInput = HtmlEncoder.Default.Encode(userInput);
        return Ok(new { SanitizedInput = sanitizedInput });
    }
    

3. Using a Third-Party Library for Sanitization

For more complex scenarios, you can use a third-party library like Ganss.XSS, which allows for advanced HTML sanitization.

  • Install the NuGet Package
    Install-Package Ganss.XSS
    
  • Example Code
    using Ganss.XSS;
    public string SanitizeHtml(string input)
    {
        var sanitizer = new HtmlSanitizer();
        return sanitizer.Sanitize(input);
    }
    

4. Content Security Policy (CSP)

A Content Security Policy (CSP) is a security header that helps prevent XSS by controlling which resources (scripts, images, styles) can be loaded by the browser.

You can add CSP headers in ASP.NET Core like this.

public void Configure(IApplicationBuilder app)
{
    app.Use(async (context, next) =>
    {
        context.Response.Headers.Add("Content-Security-Policy", "default-src 'self'; script-src 'self'");
        await next();
    });

}

This policy restricts the loading of scripts to only those from the same domain, making it much harder for attackers to load malicious external scripts.

5. HTTP-Only and Secure Cookies

If your Web API works with cookies (e.g., for authentication), always mark them as HttpOnly and Secure to prevent client-side scripts from accessing them.

options.Cookie.HttpOnly = true;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;

6. Sanitize Data from Third-Party APIs

If your API consumes data from third-party services, it's important to sanitize that data before returning it to your clients. Even trusted APIs could be compromised, so always validate and sanitize the data.

Example

public IActionResult FetchDataFromExternalApi()
{
    var externalApiData = GetExternalApiData();
    var sanitizedData = HtmlEncoder.Default.Encode(externalApiData);
    return Ok(new { Data = sanitizedData });
}

7. Ensure Proper Response Headers

Return the proper Content-Type headers in your API responses. If you’re returning JSON, ensure the Content-Type is set to application/json. This prevents browsers from interpreting JSON responses as HTML or scripts.

context.Response.ContentType = "application/json";

8. Real-World Example of XSS Prevention

Let’s implement an API endpoint that accepts a user's comment and sanitizes the input before storing it in the database.

  • Comment Model
    public class Comment
    {
        public int Id { get; set; }
        [Required]
        [MaxLength(500)]
        public string Content { get; set; }
        public DateTime CreatedAt { get; set; }
    }
    
  • CommentController
    [ApiController]
    [Route("api/[controller]")]
    public class CommentController : ControllerBase
    {
        private readonly HtmlSanitizer _sanitizer;
        public CommentController()
        {
            _sanitizer = new HtmlSanitizer();
        }
        [HttpPost]
        [Route("create")]
        public IActionResult CreateComment([FromBody] Comment comment)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }
            comment.Content = _sanitizer.Sanitize(comment.Content);
            comment.CreatedAt = DateTime.UtcNow;
            _dbContext.Comments.Add(comment);
            _dbContext.SaveChanges();
            return Ok(new { Message = "Comment created successfully!" });
        }
    }
    

Complete End to end Example In Asp.net Core Web API

Step 1. Create a New ASP.NET Core Web API Project.

  1. Open Visual Studio Code.
  2. Create a new folder for your project.
  3. Open the folder in VS Code.
  4. Open a terminal in VS Code and run the following command to create an ASP.NET Core Web API project.
    dotnet new webapi -n XSSAttacksinASP.NETCoreWebAPI
    

Step 2. Add Ganss.XSS NuGet Package.

Navigate to the project folder and install the Ganss.XSS NuGet package, which will help with input sanitization.

dotnet add package Ganss.XSS

Step 3. Modify the Project Structure.

Our project structure should look something like this.

XSSAttacksinASP.NETCoreWebAPI/
│
├── Controllers/
│   └── CommentController.cs
│
├── Models/
│   └── Comment.cs
│
├── Program.cs
├── Startup.cs
├── XssPreventionApi.csproj
└── appsettings.json

Step 4. Define the Model (Comment.cs)

In the Models folder, create a Comment.cs file to define the model for user input.

using System.ComponentModel.DataAnnotations;
namespace XSSAttacksinASP.NETCoreWebAPI.Models
{
    public class Comment
    {
        public int Id { get; set; }
        [Required]
        [MaxLength(500, ErrorMessage = "Comment cannot be longer than 500 characters.")]
        public string Content { get; set; }
        public DateTime CreatedAt { get; set; }
    }
}

Step 5. Create the Comment Controller (CommentController.cs).

In the Controllers folder, create a CommentController.cs file to handle incoming HTTP requests for the comments. We'll use the Ganss.XSS library to sanitize the comment content before saving it to a data store (in this example, we'll just simulate saving it).

using Ganss.Xss;
using Microsoft.AspNetCore.Mvc;
using XSSAttacksinASP.NETCoreWebAPI.Models;
namespace XSSAttacksinASP.NETCoreWebAPI.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class CommentController : ControllerBase
    {
        private readonly HtmlSanitizer _sanitizer;
        public CommentController()
        {
            _sanitizer = new HtmlSanitizer();
        }
        [HttpPost]
        [Route("create")]
        public IActionResult CreateComment([FromBody] Comment comment)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }
            comment.Content = _sanitizer.Sanitize(comment.Content);
            comment.CreatedAt = DateTime.UtcNow;
            return Ok(new { Message = "Comment created successfully!", SanitizedContent = comment.Content });
        }
    }
}

Step 6. Configure Program. cs and Startup. cs (ASP.NET Core 6+)

Since ASP.NET Core 6+ has consolidated the Startup and Program files, you may just need to ensure the basic structure is in place to handle API routing and services.


namespace XSSAttacksinASP.NETCoreWebAPI
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);
            // Add services to the container.
            builder.Services.AddControllers();
            // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
            builder.Services.AddEndpointsApiExplorer();
            builder.Services.AddSwaggerGen();
            var app = builder.Build();
            // Configure the HTTP request pipeline.
            if (app.Environment.IsDevelopment())
            {
                app.UseSwagger();
                app.UseSwaggerUI();
            }
            app.UseHttpsRedirection();
            app.UseAuthorization();
            app.MapControllers();
            app.Run();
        }
    }
}

Step 7. Add Launch Settings (Optional).

If you want to configure launch settings (for example, to run the project on a specific port), modify the Properties/launchSettings.json file.

{
  "profiles": {
    "XssPreventionApi": {
      "commandName": "Project",
      "dotnetRunMessages": true,
      "launchBrowser": true,
      "applicationUrl": "https://localhost:5001;http://localhost:5000",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}

Step 8. Run the Project.

  1. Open the terminal and navigate to the project folder (XssPreventionApi).
  2. Run the project using the following command.
    dotnet run
    

Step 9. Test the API Using Postman or curl.

You can use Postman or Curl to send POST requests to the API.

  • Example Request with curl
    curl -X POST https://localhost:5001/api/comment/create \
    -H "Content-Type: application/json" \
    -d "{\"Content\": \"<script>alert('XSS Attack!');</script>\"}"
    
  • Example Response
    {
      "message": "Comment created successfully!",
      "sanitizedContent": "\"}"
    }

In the response, you'll see that the <script> tags have been sanitized, preventing the malicious code from executing.

Step 10. Testing XSS Prevention.

You can test by sending different kinds of potentially harmful input, such as.

  • <script>alert('XSS')</script>
  • <img src="x" onerror="alert('XSS')"/>
  • onmouseover="alert('XSS')"

All these will be sanitized to their encoded form to ensure no harmful JavaScript gets executed.

Github Project Link

https://github.com/SardarMudassarAliKhan/XSSAttacksinASP.NETCoreWebAPI

Project Output

Project Output

Conclusion

This project demonstrates how to prevent XSS attacks in an ASP.NET Core Web API using input validation and sanitization techniques. The Ganss.XSS library plays a key role in sanitizing inputs, ensuring that dangerous scripts do not reach the client. This method, combined with best practices like CSP and secure cookie management, helps keep your web applications safe from XSS vulnerabilities.


Similar Articles