Secret leaks often occur when a sensitive piece of authentication data is stored with the source code of an application. Considering the source code is intended to be deployed across multiple assets, including source code repositories or application hosting servers, the secrets might get exposed to an unintended audience.

## Why is this an issue?

In most cases, trust boundaries are violated when a secret is exposed in a source code repository or an uncontrolled deployment environment. Unintended people who don’t need to know the secret might get access to it. They might then be able to use it to gain unwanted access to associated services or resources.

The trust issue can be more or less severe depending on the people’s role and entitlement.

### What is the potential impact?

If a JWT secret key leaks to an unintended audience, it can have serious security implications for the corresponding application. The secret key is used to encode and decode JWTs when using a symmetric signing algorithm, and an attacker could potentially use it to perform malicious actions.

For example, an attacker could use the secret key to create their own authentication tokens that appear to be legitimate, allowing them to bypass authentication and gain access to sensitive data or functionality.

In the worst-case scenario, an attacker could be able to execute arbitrary code on the application by abusing administrative features, and take over its hosting server.

## How to fix it in ASP.NET Core

### Code examples

#### Noncompliant code example

Secrets stored in `appsettings.json` can be read by anyone with access to the file.

    [ApiController]
    [Route("login-example")]
    public class LoginExampleController : ControllerBase
    {
        private readonly IConfiguration _config;
        public LoginExampleController(IConfiguration config)
        {
            _config = config;
        }

        [HttpPost]
        public IActionResult Post([FromBody] LoginModel login)
        {
            // Code to validate the login information is omitted

            var key = _config["Jwt:Key"] ??
                throw new ApplicationException("JWT key is not configured.");
            var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key)); // Noncompliant
            var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

            var Sectoken = new JwtSecurityToken(
                "example.com",
                "example.com",
                null,
                expires: DateTime.Now.AddMinutes(120),
                signingCredentials: credentials);

            var token = new JwtSecurityTokenHandler().WriteToken(Sectoken);
            return Ok(token);
        }
    }

Secrets that are hard-coded into the application can be read by anyone with access to the source code or can be decompiled from the application binaries.

    [ApiController]
    [Route("login-example")]
    public class LoginExampleController : ControllerBase
    {
        private const string key = "SecretSecretSecretSecretSecretSecretSecretSecret";

        [HttpPost]
        public IActionResult Post([FromBody] LoginModel login)
        {
            // Code to validate the login information is omitted

            var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key)); // Noncompliant
            var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

            var Sectoken = new JwtSecurityToken(
                "example.com",
                "example.com",
                null,
                expires: DateTime.Now.AddMinutes(120),
                signingCredentials: credentials);

            var token = new JwtSecurityTokenHandler().WriteToken(Sectoken);
            return Ok(token);
        }
    }

#### Compliant solution

    [ApiController]
    [Route("login-example")]
    public class LoginExampleController : ControllerBase
    {
        [HttpPost]
        public IActionResult Post([FromBody] LoginModel login)
        {
            // Code to validate the login information is omitted

            var key = Environment.GetEnvironmentVariable("JWT_KEY") ??
                throw new ApplicationException("JWT key is not configured.");
            var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key));
            var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

            var Sectoken = new JwtSecurityToken(
                "example.com",
                "example.com",
                null,
                expires: DateTime.Now.AddMinutes(120),
                signingCredentials: credentials);

            var token = new JwtSecurityTokenHandler().WriteToken(Sectoken);
            return Ok(token);
        }
    }

### How does this work?

Here, the compliant solution uses an environment variable to hold the secret. Environment variables are easy to change and are not easily accessible outside of the application.

### Going the extra mile

#### Use a secret vault

Secret vaults provide secure methods for storing and accessing secrets. They protect against the unexpected disclosure of the secrets they store.

Microsoft recommends using Azure Key Vault with .NET Core applications.

    var builder = WebApplication.CreateBuilder(args);

    // Get the name of the key vault
    var keyVaultName = Environment.GetEnvironmentVariable("AZURE_KEYVAULT") ??
        throw new ApplicationException("Azure Key Vault location is not configured.");
    // Add Azure Key Vault in the configuration
    builder.Configuration.AddAzureKeyVault(new Uri($"https://{keyVaultName}.vault.azure.net/"), new EnvironmentCredential());
    // Get the JWT secret from Azure Key Vault
    var jwtKey = builder.Configuration.GetSection("JWT-KEY").Get<string>() ??
        throw new ApplicationException("JWT key is not configured.");

    builder.Services
      .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
      .AddJwtBearer(options => {
          options.TokenValidationParameters = new TokenValidationParameters{
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtKey!)),
            ValidateIssuerSigningKey = true,
            ValidIssuer = "example.com",
            ValidateIssuer = true,
            ValidAudience = "example.com",
            ValidateAudience = true,
            ValidateLifetime = true,
          };
      });

## How to fix it in ASP.NET

### Code examples

#### Noncompliant code example

Secrets stored in `web.config` can be read by anyone with access to the file.

    public class LoginExampleController : ApiController
    {
        public IHttpActionResult Post([FromBody] LoginModel login)
        {
            // Code to validate the login information is omitted

            var key = ConfigurationManager.AppSettings["key"] ??
                throw new ApplicationException("JWT key is not configured.");
            var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key));
            var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

            var secToken = new JwtSecurityToken(
                "example.com",
                "example.com",
                null,
                expires: DateTime.Now.AddMinutes(120),
                signingCredentials: credentials
            );

            var token = new JwtSecurityTokenHandler().WriteToken(secToken);
            return Ok(token);
        }
    }

Secrets that are hard-coded into the application can be read by anyone with access to the source code or can be decompiled from the application binaries.

    public class LoginExampleController : ApiController
    {
        private const string key = "SecretSecretSecretSecretSecretSecretSecretSecret";

        public IHttpActionResult Post([FromBody] LoginModel login)
        {
            // Code to validate the login information is omitted

            var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key));
            var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

            var secToken = new JwtSecurityToken(
                "example.com",
                "example.com",
                null,
                expires: DateTime.Now.AddMinutes(120),
                signingCredentials: credentials
            );

            var token = new JwtSecurityTokenHandler().WriteToken(secToken);
            return Ok(token);
        }
    }

#### Compliant solution

    public class LoginExampleController : ApiController
    {
        public IHttpActionResult Post([FromBody] LoginModel login)
        {
            // Code to validate the login information is omitted

            var key = Environment.GetEnvironmentVariable("JWT_KEY") ??
                throw new ApplicationException("JWT key is not configured.");
            var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key));
            var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

            var secToken = new JwtSecurityToken(
                "example.com",
                "example.com",
                null,
                expires: DateTime.Now.AddMinutes(120),
                signingCredentials: credentials
            );

            var token = new JwtSecurityTokenHandler().WriteToken(secToken);
            return Ok(token);
        }
    }

### How does this work?

Here, the compliant solution uses an environment variable to hold the secret. Environment variables are easy to change and are not easily accessible outside of the application.

## Resources

### Documentation

- Microsoft Learn - JwtSecurityToken Class Class
- Microsoft Learn - SymmetricSecurityKey Class

### Standards

- OWASP - Top 10 2021 Category A7 - Identification and Authentication Failures
- OWASP - Top 10 2017 Category A3 - Sensitive Data Exposure
- CWE - CWE-798 - Use of Hard-coded Credentials
- CWE - CWE-259 - Use of Hard-coded Password
- STIG Viewer - Application Security and Development: V-222642 - The application must not contain embedded authentication data.