Hi all,
when using Microsoft.Identity.Web we usually inject all the necessary configuration with the following code:
services.AddMicrosoftIdentityWebAppAuthentication(Configuration, "AzureAd");
This works as long as a applicationConfig.json file is present.
If I publish my solution as an Azure Web Application I don麓t want to use the applicationConfig.json file but rather the environment variables of the Web Application or KeyVault for storing secrets. Sadly the function AddMicrosoftIdentityWebAppAuthentication does not support this.
Is there a possiblity to not having the "AzureAD" section present or the read all the necessary information from env variables and KeyVault, build the configurationObject by myself and then load it into the method?
Any help would be highly appreciated!
Thanks a lot!
Tom
Hi again,
ok if someone has the same issue or problem like me I found a solution. I麓m not quite happy with it but it might help someone.
I first created the following two classes:
public class AzureConfig
{
public AzureAD AzureAD { get; set; }
}
```
public class AzureAD
{
public string Instance { get; set; }
public string Domain { get; set; }
public string TenantId { get; set; }
public string ClientId { get; set; }
public string CallbackPath { get; set; }
}
In startup I created an Instance of the AzureConfig and of the AzureAD object. Read all the variables of the configuration section, parsed them to json into a string and then converted them into a stream object. I created a IConfiguration Object which I can now put into the `AddMicrosoftIdentityWebAppAuthentication` method. Looks like the following:
public void ConfigureServices(IServiceCollection services)
{
AzureConfig azureAdConfig = new AzureConfig();
azureAdConfig.AzureAD = new AzureAD();
azureAdConfig.AzureAD.ClientId = Configuration.GetSection("ClientId").Value;
azureAdConfig.AzureAD.TenantId = Configuration.GetSection("TenantId").Value;
azureAdConfig.AzureAD.Domain = Configuration.GetSection("Domain").Value;
azureAdConfig.AzureAD.Instance = Configuration.GetSection("Instance").Value;
azureAdConfig.AzureAD.CallbackPath = Configuration.GetSection("CallbackPath").Value;
//serialize to json
string jsonFile = Newtonsoft.Json.JsonConvert.SerializeObject(azureAdConfig);
// convert string to stream
byte[] byteArray = Encoding.UTF8.GetBytes(jsonFile);
MemoryStream stream = new MemoryStream(byteArray);
//create configuration object
var azureConfigurationBuilder = new ConfigurationBuilder()
.AddJsonStream(stream);
IConfiguration azureConfiguration = azureConfigurationBuilder.Build();
services.AddMicrosoftIdentityWebAppAuthentication(azureConfiguration, "AzureAd");
//more code afterwards
....
}
```
Maybe that will be helpful to someone with the same situation like me.
If any other solution is present I will still be quite happy to know it!
Thanks a lot!
Tom
@spotnick
The way to do is to use AddAuthentication followed by a different override of AddMicrosoftIdentityWebApp which takes delegates:
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(microsoftIdentityOptions=>
{
options.ClientId = GetClientIdFromEnvironmentVariable();
options.TenantId = GetClientIdFromEnvironmentVariable();
options.ClientSecret = GetClientSecretFromKeyVault();
/// etc ...
})
@jmprieur
Awesome!! Thank you so much! That worked instant 馃憤
Can I get an example of which options need to be set for use with downstream api token acquisition please?
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(microsoftIdentityOptions =>
{
microsoftIdentityOptions.Instance = "https://login.microsoftonline.com/";
microsoftIdentityOptions.Domain = "";
microsoftIdentityOptions.TenantId = "";
microsoftIdentityOptions.ClientId = "";
microsoftIdentityOptions.ClientSecret = "";
microsoftIdentityOptions.CallbackPath = "";
microsoftIdentityOptions.SignedOutCallbackPath = "";
})
.EnableTokenAcquisitionToCallDownstreamApi(confidentialClientApplicationOptions =>
{
confidentialClientApplicationOptions.Instance = "";
confidentialClientApplicationOptions.TenantId = "";
confidentialClientApplicationOptions.ClientId = "";
confidentialClientApplicationOptions.ClientSecret = "";
}, new string[] { Constants.ScopeUserRead, Constants.ScopeDirectoryReadAll })
.AddDistributedTokenCaches();
I'm concerned that storing a ClientSecret in appsettings.json isn't secure (it gets checked into source control), so I'd like to override the method and insert the client secret from keyvault. This also prevents a need to publish a new version of the site when the client secret expires, so is a preferred implementation.
I'm trying to avoid changing the behavior in comparison to the default implementation,
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi(new string[] { Constants.ScopeUserRead, Constants.ScopeDirectoryReadAll })
.AddDistributedTokenCaches();
Are there any specific options for the downstream api that need to be configured / should be left alone?
@madshaun1984
You have to solutions to avoid having the client secret in the appsettings.json:
Finally you could always initialize parameters from the configuration, and then initialize the secret
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(microsoftIdentityOptions =>
{
Configuration.GetSection("AzureAD").Bind(microsoftIdentityOptions);
microsoftIdentityOptions.ClientSecret = "";
})
You could even do it after, using services.Configure<MicrosoftIdentityOptions>().
See https://github.com/AzureAD/microsoft-identity-web/wiki/customization#microsoftidentityoptions
@jmprieur thanks for the tips, however the snippet I provided above is working, I'm more concerned here with the need to then override ".EnableTokenAcquisitionToCallDownstreamApi".
The concern is that setting an instance (for example) or not setting an option may introduce issues further down the line.
Are there any caveats I should be aware of here, or would I be OK setting the options shown in the example provided in my previous comment?
What about AddMicrosoftIdentityWebApi(AuthenticationBuilder, Action<JwtBearerOptions>, Action<MicrosoftIdentityOptions>, String, Boolean)? Not clear what needs to be configured in configureJwtBearerOptions vs configureMicrosoftIdentityOptions. Thanks.
This is the same thing, @MarcAnnous
In general, Microsoft.Identity.Web does not require you to initialize the same thing twice, though.
It does work this way:
C#
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(options => {}, options =>
{
options.Instance = instance;
options.Domain = domain;
options.TenantId = tenantId;
options.ClientId = clientId;
options.SignUpSignInPolicyId = signUpSignInPolicyId;
options.CallbackPath = new PathString("/signin-oidc");
options.SignedOutCallbackPath = new PathString("/signout-callback-oidc");
});
@jmprieur I was confused about having to pass to AddMicrosoftIdentityWebApi something for configureJwtBearerOptions (doing nothing) in addition to configureMicrosoftIdentityOptions whereas AddMicrosoftIdentityWebApp takes only configureMicrosoftIdentityOptions which is enough.
Most helpful comment
@spotnick
The way to do is to use
AddAuthenticationfollowed by a different override ofAddMicrosoftIdentityWebAppwhich takes delegates: