As you may hopefully already know, you should never include secrets (such as API keys, symmetrical keys, certificates, etc.) in your repositories. Not even in private ones.
Recently, I had to use secrets for a personal project and for a customer project. I had to come up with an elegant solution for my .NET projects and I thought I’d share my insights and my best practices with you.
Generally speaking, there are two approaches when it comes to implementing secrets in your .NET projects:
- User Secrets: You include secrets in a secret file. The .NET app will load them for you during runtime. In production, you specify the secrets through other means (such as Key Vault, etc.). This typically works for ASP.NET Core projects and worker services.
- Constants: You specify typed secrets in a static class and reference the class in your projects. This is typically used for apps and test projects.
In this article, I will focus on the latter scenario for .NET based projects:
- In my personal project I had to add secrets to my UWP app using App Center and GitHub
- In a commercial project I went for TDD so naturally, I had to provide some secrets for my integration tests without going over the top because I’ve wanted to keep things KISS. For that, I used Azure DevOps.
Without further ado, let’s go.
Initial Project Considerations
Now, how exactly you include secrets purely depends on your project. In my case, I created a partial static class called Constants.cs
where I define the required properties, and another partial class Constants.Secret.cs
that contains the propagated values:
To give you an idea, here’s how both files look like:
// Constants.cs
public static partial class Constants
{
public static string ApiKey { get; set; }
}
// Constants.Secret.cs
public static partial class Constants
{
static Constants()
{
ApiKey = "verysecretkey!123"
}
}
The Rosyln compiler resolves both files automatically for you. To use the class, there’s no special configuration required and you can simply go ahead and reference the Constants.ApiKey
property from anywhere.
Next up, before you go ahead and commit anything to Git, make sure you add the secret file to your .gitignore file:
Constants.Secret.cs
This will ignore all files that match the name in your repository so it will never be checked into your remote repository. Quite handy if you have many different projects in your solution which have their own set of secrets.
Go ahead, provide a good commit message and push it. Once you have done that, we can look into the CI/CD side of things.
CI/CD
We require some CI/CD setup because as of now, all builds will simply fail since the compiler cannot find the Constants.Secret.cs file in the repository — primarily because it is referenced in our solution, but it cannot be found in the remote repository’s working directory.
To resolve this, I will showcase three options that should work for most scenarios: GitHub, Azure DevOps and App Center.
Based on these scenarios you should get inspired and be able to apply the knowledge to other services.
GitHub
GitHub allows you to store secrets in your repository. These secrets can then be referenced from your GitHub Actions.
To manage your secrets, go to your repository and hit Settings > Secrets.
In there, create a new repository secret and include the contents of the Constants.Secret.cs
file. Give it a memorable name (I will refer to it as from now on. You should use a different name) and save the secret.
Now it’s time to create the class file based on your repository secret in your action definition file so it can be created before the actual build happens.
The script could look like this: it will simply write the contents of the secret to a file.
steps:
- uses: actions/checkout@v2
- name: Create Constants
run: echo ${{secrets.<CONSTANTS>}} > "./<PATH>/Constants.Secret.cs"
# ...
Make sure you insert this step before any build steps that depend on your constants file, and remember to replace the placeholder with your actual secret’s name.
Commit the updated action file and your build should succeed.
Azure DevOps
Azure DevOps allows you to upload secure files. Secure files are a good solution for managing your secret files across your pipelines. It allows you to manage who can access the secure files and which pipelines can use them.
To manage all your secrets, make sure you enabled Pipelines for your project. Then, go to Pipelines > Library. The library allows you to reuse variables and store secure files.
In there, go to the Secure files tab, upload your secret constants class (Constants.Secret.cs
) and make sure you give it a good name - one that allows you to distinguish constant classes from each other in case you have more than one of them.
After that, it’s time to pull our secure file into our build pipeline. To consume secure files, you use the SecureFile task. It requires a task name and the secure file name as an input. The task name allows you to reference the file from all consecutive build steps:
steps:
- task: DownloadSecureFile@1
name: constantsFile
displayName: 'Download Constants for Integration Tests'
inputs:
secureFile: 'Constants.Secret.cs'
After that’s done, we need to copy the file to the location where it’s supposed to be. Otherwise, our build will fail because the compiler won’t find the class that’s referenced in our solution as mentioned before:
- script: cp $(constantsFile.secureFilePath) <PATH>/Constants.Secret.cs
displayName: Copy constants
And you’re done! This step will now copy the secure file to its destination.
Save the build definition and run it. Your build should succeed.
App Center
App Center allows you to run custom scripts at specific stages. You can read more about it over at the App Center documentation.
You will need to use a custom script to inject your constants before the build starts. However, because the build script contains your propagated constants and must be checked into your remote repository, it is advised that you either don’t include any sensitive details or propagate the contents from secure environment variables.
If you decide to go with the former approach which I will be showing, you will need to make sure your app will behave correctly even without any of the constant values.
If you plan to use App Center just for CI purposes, you will be fine. Though, if you plan to distribute your app package to your testers, you will need to find ways to propagate your constants.
In that case, consider making use of environment variables while you create the constants class or provide ways to propagate the values during runtime by the user (such as configuration files, etc.).
I built an UWP app and had to propagate a few constants to my app. To add the post clone script, add the script file to your repository’s root path.
For Windows-based Builds such as UWP (appcenter-post-clone.ps1
)
"namespace <PLACEHOLDER>
{
public static partial class Constants
{
static Constants() { }
}
}" | Out-File -FilePath ".\\<PATH>\\Constants.Secret.cs
For Mac-based Builds (appcenter-post-clone.sh
)
echo "namespace <PLACEHOLDER>\
{\
public static partial class Constants\
{\
static Constants() { } \
}\
}" > "<PATH>/Constants.Secret.cs.cs"
Both scripts write the embedded content to a file. Make sure you commit and push the file to your repository and let the build run. If you struggle with it, have a look at the official samples for reference purposes.
I hope this article helps and inspires you. Let me know what you think.
I recently became a Microsoft Learn Student Ambassador. If you would like me to publish more articles in the future, make sure to follow me on dev.to and Twitter. Check out my other profiles. Also, have a look at my other articles!
Cheers, and thanks for reading!
Top comments (4)
Wondering if
.env
files or doing the approach which you shared is a better way?That's a great question. Certainly, but it depends on your framework and toolset. Personally, I haven't stumbled upon any
.env
files in .NET projects -- but conceptionally, both environment files and the method I show in my article (by swapping out / injecting classes) are pretty much equal in the end.Worth noting: Some modern .NET projects solve this issue by utilizing app settings and user secrets. I'm sure other programming languages offer similar solutions.
Oh yes the
.env
files were used in my react projects.Yeah, that’s the recommended way of doing it in React projects if I recall correctly. 🙂