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.
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.
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.
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);
}
}
[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);
}
}
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.
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,
};
});
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);
}
}
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);
}
}
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.