30

Note: this issue has now been resolved - see my Update 3 below for the solution.

I have an ASP.NET Core 2 web app which needs to connect to a SQL Server database. As per my Update 2 below I'm debugging the app with IIS.

I'm loading the configuration in my Program class (because I need it for setting up logging) like this:

public static IConfiguration Configuration => new ConfigurationBuilder()
    .SetBasePath(Directory.GetCurrentDirectory())
    .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
    .AddJsonFile($"appsettings.{EnvName ?? "Production"}.json", optional: true)
    .AddUserSecrets<Startup>(false)
    .Build();

My BuildWebHost method looks like this:

public static IWebHost BuildWebHost(string[] args)
{
    return WebHost.CreateDefaultBuilder(args)
        .UseStartup<Startup>()
        .UseConfiguration(Configuration)
        .UseSerilog()
        .Build();
}

My appSettings.json file has this section:

{
  "ConnectionStrings": {
    "DefaultConnection": "*****" // secret
  }
}

I've added a user secrets file to the project using the context menu in Visual Studio, duplicating the above section but with real connection string.

With this all in place my code throws an exception about the format of the connection string. However, if I replace "*****" in my main appSettings.json file with the real connection string the applications works fine. So I assume it is not loading my user secrets.

Now, I thought using the overload of AddUserSecrets passing the argument false would cause the code to break if user secrets couldn't be loaded. But it's not breaking here. I'm not sure what else I can do. What would cause ASP.NET Core to fail to load the user secrets?

Update 1

When debugging I can see inside my Configuration property that it has the 3 providers that I'd expect: appsettings.json, appsettings.Development.json, and secrets.json. However, the file root of the secrets provider is my debug path, not the location of my secrets file i.e. C:\Users[username]\AppData\Roaming\Microsoft\UserSecrets...

enter image description here

Update 2

I've realised that the Debug settings of the web project is pointed at an IIS site which uses an application pool running under an ApplicationPoolIdentity user. Could this mean the user secrets need to be under C:\Users[app-pool-user]\AppData\Roaming\Microsoft\UserSecrets rather than my own user account? I've tried literally copying the GUID-named secrets.json folder over to this location but that hasn't helped. I have, however, tried changing to run under IIS Express and this time the user secrets are loaded. But for various reasons I need to be able to debug this application under a specific domain name so how can I get my user secrets to load in my IIS context? I have tried changing the app pool to use my main Windows user instead of AppPoolIdentity but this hasn't helped.

Update 3: Solved

Well, I've learned something today! Eventually it was the answer here which solved my problem, but not in a way I expected. I moved on from my original issue - the loading of user secrets - because I realised by hosting on IIS I was essentially working with a deployment rather than a temporary debug session. So I moved my user secrets to environment variables (e.g. in my connection string example, adding a system environment variable ConnectionStrings:DefaultConnection) and adding an AddEnvironmentVariables() to my config setup. But I was still finding that for some reason these weren't being loaded into my configuration. Finally I discovered thanks to this SO post that IIS has a place for adding local environment variables hidden deep in a thing called Configuration Editor. Adding my variables here solved the problem and means I can now host and debug locally in IIS whilst keeping my secrets safe.

Tom Troughton
  • 3,941
  • 2
  • 37
  • 77

3 Answers3

19

I found that when running under IIS, the secrets.json is expected to be in the Physical Path of the site.

Jared Dickson
  • 538
  • 3
  • 7
  • 8
    Wow. That seems to totally negate the point of the secrets file. – Grandizer Aug 25 '18 at 16:09
  • 1
    Yeah ... This worked for me too .. So what's the point of the "Manage User Secrets" stroring in User's AppData folder? – Chris Hammond Oct 24 '18 at 09:30
  • Curious, how did you find this out? Agree that it doesn't make sense to store them right in the root folder of the web app... – silkfire Feb 20 '19 at 22:19
  • @silkfire - you can run Process Monitor from Sysinternals Suite and add 'Path' filter that contains 'secrets.json'. You'll see that devenv.exe process uses the correct path (in c:\users\user\appdata\roaming\...), but w3wp.exe process uses the physical path of the site. – sventevit Dec 19 '19 at 14:01
  • 1
    Yes - this does work becuase if the correct directory %APPDATA%\Microsoft\UserSecrets\\secrets.json cannot be found or accessed it defaults to this directory. If you find the appdata evironment variable for your worker process and set permissions then it works as expected. See my full explanation below. – stuartm9999 Dec 27 '19 at 15:40
6

Short answer - under IIS the worker process will have its own "%APPDATA%" environment variable. On my desktop it was "C:\WINDOWS\system32\config\systemprofile\AppData\Roaming" If I then copy the direcory with my secrets to here AND grant the appropariate asp.net worker process (e.g. "IIS AppPool\AppPoolName" - substitute your app pool name) read access to this directory then it should work.

Longer answer

Thanks to the wonders of open source you can find the code here - https://github.com/dotnet/aspnetcore/blob/52eff90fbcfca39b7eb58baad597df6a99a542b0/src/Tools/dotnet-user-secrets/src/Internal/SecretsStore.cs

When you look at this you can see why @kraihn 's answer works - if the directory doesn't exist (or can't be read because of permissions - which caught me out for a while) then "fileProvider" is set to null - so it looks in the application directory - just like the other config files.

The directory itself is set in the "PathHelper" (https://github.com/aspnet/Extensions/blob/master/src/Configuration/Config.UserSecrets/src/PathHelper.cs) - but as long as there is an "APPDATA" setting it will pick that up.

You can verify all this with sysinternals Checking the directory exists -

15:04:16.0759461    w3wp.exe    26308   CreateFile  C:\Windows\System32\config\systemprofile\AppData\Roaming\Microsoft\UserSecrets\c89b3c54-eda4-4b3b-97ca-b7d3d3699622 SUCCESS Desired Access: Read Attributes, Disposition: Open, Options: Open Reparse Point, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, OpenResult: Opened
15:04:16.0759965    w3wp.exe    26308   QueryNetworkOpenInformationFile C:\Windows\System32\config\systemprofile\AppData\Roaming\Microsoft\UserSecrets\c89b3c54-eda4-4b3b-97ca-b7d3d3699622 SUCCESS CreationTime: 27/12/2019 14:31:05, LastAccessTime: 27/12/2019 14:31:05, LastWriteTime: 27/12/2019 14:31:05, ChangeTime: 27/12/2019 15:03:45, AllocationSize: 01/01/1601 00:00:00, EndOfFile: 01/01/1601 00:00:00, FileAttributes: D
15:04:16.0760114    w3wp.exe    26308   CloseFile   C:\Windows\System32\config\systemprofile\AppData\Roaming\Microsoft\UserSecrets\c89b3c54-eda4-4b3b-97ca-b7d3d3699622 SUCCESS 
15:04:16.0762556    w3wp.exe    26308   CreateFile  C:\Windows\System32\config\systemprofile\AppData\Roaming\Microsoft\UserSecrets\c89b3c54-eda4-4b3b-97ca-b7d3d3699622 SUCCESS Desired Access: Read Attributes, Disposition: Open, Options: Open Reparse Point, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, OpenResult: Opened
15:04:16.0762940    w3wp.exe    26308   QueryNetworkOpenInformationFile C:\Windows\System32\config\systemprofile\AppData\Roaming\Microsoft\UserSecrets\c89b3c54-eda4-4b3b-97ca-b7d3d3699622 SUCCESS CreationTime: 27/12/2019 14:31:05, LastAccessTime: 27/12/2019 14:31:05, LastWriteTime: 27/12/2019 14:31:05, ChangeTime: 27/12/2019 15:03:45, AllocationSize: 01/01/1601 00:00:00, EndOfFile: 01/01/1601 00:00:00, FileAttributes: D
15:04:16.0763067    w3wp.exe    26308   CloseFile   C:\Windows\System32\config\systemprofile\AppData\Roaming\Microsoft\UserSecrets\c89b3c54-eda4-4b3b-97ca-b7d3d3699622 SUCCESS 

Reading the config files secrets file is not read from IIS directory.

15:04:16.0759461    w3wp.exe    26308   CreateFile  C:\Windows\System32\config\systemprofile\AppData\Roaming\Microsoft\UserSecrets\c89b3c54-eda4-4b3b-97ca-b7d3d3699622 SUCCESS Desired Access: Read Attributes, Disposition: Open, Options: Open Reparse Point, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, OpenResult: Opened
15:04:16.0759965    w3wp.exe    26308   QueryNetworkOpenInformationFile C:\Windows\System32\config\systemprofile\AppData\Roaming\Microsoft\UserSecrets\c89b3c54-eda4-4b3b-97ca-b7d3d3699622 SUCCESS CreationTime: 27/12/2019 14:31:05, LastAccessTime: 27/12/2019 14:31:05, LastWriteTime: 27/12/2019 14:31:05, ChangeTime: 27/12/2019 15:03:45, AllocationSize: 01/01/1601 00:00:00, EndOfFile: 01/01/1601 00:00:00, FileAttributes: D
15:04:16.0760114    w3wp.exe    26308   CloseFile   C:\Windows\System32\config\systemprofile\AppData\Roaming\Microsoft\UserSecrets\c89b3c54-eda4-4b3b-97ca-b7d3d3699622 SUCCESS 
15:04:16.0762556    w3wp.exe    26308   CreateFile  C:\Windows\System32\config\systemprofile\AppData\Roaming\Microsoft\UserSecrets\c89b3c54-eda4-4b3b-97ca-b7d3d3699622 SUCCESS Desired Access: Read Attributes, Disposition: Open, Options: Open Reparse Point, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, OpenResult: Opened
15:04:16.0762940    w3wp.exe    26308   QueryNetworkOpenInformationFile C:\Windows\System32\config\systemprofile\AppData\Roaming\Microsoft\UserSecrets\c89b3c54-eda4-4b3b-97ca-b7d3d3699622 SUCCESS CreationTime: 27/12/2019 14:31:05, LastAccessTime: 27/12/2019 14:31:05, LastWriteTime: 27/12/2019 14:31:05, ChangeTime: 27/12/2019 15:03:45, AllocationSize: 01/01/1601 00:00:00, EndOfFile: 01/01/1601 00:00:00, FileAttributes: D
15:04:16.0763067    w3wp.exe    26308   CloseFile   C:\Windows\System32\config\systemprofile\AppData\Roaming\Microsoft\UserSecrets\c89b3c54-eda4-4b3b-97ca-b7d3d3699622 SUCCESS 
NovaDev
  • 2,737
  • 5
  • 29
  • 43
stuartm9999
  • 139
  • 1
  • 8
  • @Soleil - appreciate the code formatting - but the code here is the actual .Net Extension code - so should be as is! The links to the git repository for the code are there - if you want to correct the original. – stuartm9999 Dec 27 '19 at 17:02
  • 1
    I corrected not the code but the formatting, in order to improve the reading. – Soleil Dec 27 '19 at 18:05
  • Understood @Soleil. Do you think it might read better if I just referenced the source code rather than copying it into the solution? – stuartm9999 Dec 30 '19 at 09:17
  • What is your opinion ? – Soleil Dec 30 '19 at 11:13
  • @Soleil I think I'll remove the code snippets and simply leave the references to the source. – stuartm9999 Jan 03 '20 at 12:30
3

Most of the documentation assumes you are using IIS Express and does not cover the scenario with local IIS instance on a development machine.

Given that IIS application pool runs with limited privileges (e.g. imagine what would happen if it can read [user]\desktop\ExpenseReport.xls by default). Hence it is expected to have the secrets.json be somewhere within the web site folder. Just make sure you'd add it to your .gitignore file (or alternative) and it will still be a 'secret'.

Milen
  • 634
  • 9
  • 23
  • 1
    This is really the better answer. I created an appsettings.localhost.json file, .gitingored it, and I'm good. – NovaDev Jan 21 '22 at 15:04