So I created an ASP.NET Core website with IdentityServer authentication and published it to my Azure Web App but it complains about the certificate. I'm just using the default basic 1 tier web app with no custom domain. The web app is signed by a certificate out of the box so can't I just use that somehow?
Do I really need to buy a custom domain and my own certificate for this to work? I would prefer if I can just keep using the web app without a custom domain.
In the diagnostics dump I can see the error
Couldn't find a valid certificate with subject 'CN=MyApplication' on the 'CurrentUser\My'
at Microsoft.AspNetCore.ApiAuthorization.IdentityServer.SigningKeysLoader.LoadFromStoreCert(String subject, String storeName, StoreLocation storeLocation, DateTimeOffset currentTime)
at Microsoft.AspNetCore.ApiAuthorization.IdentityServer.ConfigureSigningCredentials.LoadKey()
at Microsoft.AspNetCore.ApiAuthorization.IdentityServer.ConfigureSigningCredentials.Configure(ApiAuthorizationOptions options)
Startup.cs
var identityserver = services.AddIdentityServer();
identityserver.AddApiAuthorization<ApplicationUser, AutheticationDbContext>();
identityserver.AddSigningCredentials();
services.AddAuthentication()
.AddIdentityServerJwt();
appSettings.json
"IdentityServer": {
"Clients": {
"MyWebProjectName.Client": {
"Profile": "IdentityServerSPA"
}
},
"Key": {
"Type": "Store",
"StoreName": "My",
"StoreLocation": "CurrentUser",
"Name": "CN=MyApplication"
}
}
When you deploy your webapp, you will get a url like: https://appname.azurewebsites.net.
Do I really need to buy a custom domain and my own certificate for this to work?
Then you can use powershell to generate self signed certificate. So you don't need to create a new domain or buy certificate.
After deployed app, you also need to upload your self signed certificate on portal, like Syarif Mathis's answer on below post.
How to configure key settings for IdentityServer in appsettings.json for aspnet core app running on IIS
Related
I created a new ASP.NET Core 6.0 MVC web application using Visual Studio 2022, and I define it to use Azure AD for authentication, as follows:
Then I was asked to create an owned application, so I created one named "ad" as follows:
Inside my application's appsetting.json I have these settings:
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "*****",
"TenantId": "***",
"ClientId": "***",
"CallbackPath": "/signin-oidc"
},
....
}
It seems Visual Studio did all the work for us.
But when I checked the "Certificate & Secrets" in the Azure portal for the generated Azure AD APP, I found that there is not anything assigned:
So now we are going to upload a certificate (.crt file), but i have those questions:-
Now the above ASP.NET Core MVC web application already have SSL certificate bought from Go-daddy, so can we use this certificate also inside our Azure Active directory App ?
Also, after uploading a certificate inside our Azure Active Directory App >> do we need to pass the certificate Thumbprint from our web application ? if the answer is yes, then what i need to do exactly , do we need to modify the Identity platfrom code?
If you used VS to integrate AAD and create resource for you, then the appsettings.json file should look like this. And it's also OK to add configurations manually.
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "xxx.onmicrosoft.com",
"TenantId": "tenant_id",
"ClientId": "client_id",
"CallbackPath": "/signin-oidc",
"ClientSecret": "Client secret from app-registration. Check user secrets/azure portal.",
//"ClientCertificates": []//I comment this line
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"MicrosoftGraph": {
"BaseUrl": "https://graph.microsoft.com/v1.0",
"Scopes": "user.read"
}
}
=============================================================
Firstly, the client secret is used for calling API, for example Ms graph API. Then in this answer, I demonstrate how to integrate Graph API in the APP, then you can get the client secret which is already generated for you.
After finishing all these steps, your project has already set up, going to Program.cs you can see code below, and it already read the configurations including the secret.
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
.AddMicrosoftGraph(builder.Configuration.GetSection("MicrosoftGraph"))
.AddInMemoryTokenCaches();
but you still need to go to appsettings.json to paste the client secret into "ClientSecret": "Client secret from app-registration. Check user secrets/azure portal.",. You'd better to comment "ClientCertificates": [] because you are using secret but not certificate.
By the way, the client secret can exist several valid secret at the same time, this is designed for avoid app crash because of secret expired. So you can have 2 client secrets, if one of the secret is about to expire, you can create a new one in Azure AD then paste the secret value into your project. This means, for example, you used the Visual Studio to generate the secret, but you didn't store the secret, you also create another secret manually in Azure portal and use it in your app.
I've built a basic Vue web app using Azure Static Web Apps, and I'm trying to configure custom authentication. I've already managed to get everything (mostly) working using Auth0 by following the documentation and referencing this handy blog post.
For Auth0, I added AUTH0_ID=<my-auth0-id> and AUTH0_SECRET=<my-auth0-secret> to the local.settings.json file. My staticwebapp.config.json looked like this:
...
"auth": {
"identityProviders": {
"customOpenIdConnectProviders": {
"auth0": {
"registration": {
"clientIdSettingName": "AUTH0_ID",
"clientCredential": {
"clientSecretSettingName": "AUTH0_SECRET"
},
"openIdConnectConfiguration": {
"wellKnownOpenIdConfiguration": "https://<my-auth0-tenant>/.well-known/openid-configuration"
}
},
"login": {
"nameClaimType": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name",
"scopes": ["openid", "profile"]
}
}
}
}
}
I'm now trying to set up authentication using Azure AD B2C. My understanding is that Azure Static Web Apps handles a portion of the authentication such that I should configure the ID provider to work with a web app rather than with a single page app framework. This is what I did when using Auth0 and it seemed to work.
I've added AADB2C_ID=<my-azure-ad-b2c-id> and AADB2C_SECRET=<my-azure-ad-b2c-secret> to the local.settings.json file. In staticwebapp.config.json I replaced ClientIdSettingName to AADB2C_ID, clientSecretSettingName to AADB2C_SECRET, and wellKnownOpenIdConfiguration to https://<my-azure-ad-b2>.b2clogin.com/<my-azure-ad-b2c>.onmicrosoft.com/v2.0/.well-known/openid-configuration?p=B2C_1_signupsignin1. This references the 'signupsignin' user flow on my B2C tenant.
At this point I can visit /login which points to /.auth/login/aadb2c, initiates the user flow, and lets me sign up and verify as expected. The test user is then created in my Azure AD B2C tenant. However, B2C then tries to redirect me to /.auth/complete which throws a 403 error:
We need an email address or a handle from your login service. To use
this login, please update your account with the missing info.
I've tried adding /.auth/complete as an allowed redirect URI in Azure AD B2C but this doesn't fix things. What am I missing here?
Try changing to this: "nameClaimType": "emails".
Sourced from staticwebapp.config.json in here: https://github.com/Azure/static-web-apps/issues/457
I am trying to use the .Net Core Clean Architecture App Template and get it running in containers and deployed through an azure CI/CD pipeline
I have the containerized version of the template running locally in linux container with port 5001 and everything works perfectly.
I have the azure pipeline build process working properly and it creats image in my container registry.
The problem is once I deploy/release to a Web App for Containers, the app fails and throws the following error:
Application startup exception
System.InvalidOperationException: Couldn't find a valid certificate with subject 'CN=localhost' on the 'CurrentUser\My' at
Microsoft.AspNetCore.ApiAuthorization.IdentityServer.SigningKeysLoader.LoadFromStoreCert(String
subject, String storeName, StoreLocation storeLocation, DateTimeOffset currentTime)
What I have done:
Following these docs from MS I have created a local dev cert:
dotnet dev-certs https -ep %USERPROFILE%\.aspnet\https\aspnetapp.pfx -p { password here }
dotnet dev-certs https --trust
I then imported this into the Web App as a private .pfx cert.
I added an application setting WEBSITE_LOAD_CERTIFICATES with the "thumb" value of the cert
I used the "hostname" of the imported cert in the Identity Server appSettings.json section (hostname=localhost in my case)
When the Web app loads, it shows :( Application error and the docker logs give me the error I quoted above.
I am pretty sure this is related to the Identity server set up and the appSettings.json values here:
"IdentityServer": {
"Key": {
"Type": "Store",
"StoreName": "My",
"StoreLocation": "CurrentUser",
"Name": "CN=localhost"
}
}
Can someone help me figure out how to resolve this error?
EDIT 1 - Manually specify file for IdentityServer Key
This is related to identity server for sure. I tried to manually set the Cert as a file in the appSettings.json like this:
"IdentityServer": {
"Key": {
"Type": "File",
"FilePath": "aspnetapp.pfx",
"Password": "Your_password123"
}
}
Now I get this error:
Loading certificate file at '/app/aspnetapp.pfx' with storage flags
''. Application startup exception System.InvalidOperationException:
There was an error loading the certificate. The file
'/app/aspnetapp.pfx' was not found.
Microsoft.AspNetCore.ApiAuthorization.IdentityServer.SigningKeysLoader.LoadFromFile
I added this to the dockerfile:
WORKDIR /app
COPY ["/aspnetapp.pfx", "/app"]
RUN find /app
And as you can see from the image below, the files are showing in the build directory for the app:
I also made sure that the aspnetapp.pfx is not getting ignored by the .gitignore or .dockerignore files.
I cannot figure out why it won't load this file. It appears like it exists right where it is supposed to.
EDIT 2 using cert thumb and updated path
So I used tnc1977 suggestion and had this as my setting for the identity key
"IdentityServer": {
"Key": {
"Type": "File",
"FilePath": "/var/ssl/private/<thumb_value>.p12",
"Password": "Your_password123"
}
}
However, this gave another error:
There was an error loading the certificate. Either the password is
incorrect or the process does not have permisions to store the key in
the Keyset 'EphemeralKeySet'
Interop+Crypto+OpenSslCryptographicException: error:23076071:PKCS12
routines:PKCS12_parse:mac verify failure
EDIT 3: Valid Azure App Certificate
I purchased an Azure App Certificate and added a custom domain with TSL set up and the same errors appear
EDIT 4: Load Cert in code startup.cs - new error:
I now know that I caanot use the cert store CurrentUser/My because that is for windows. Linux containers have to manually load the cert in code.
I am using the thumbprint of aa application certificate that has been added to the azure web app. It is a private azure app cert and it has been verified against a custom domain.
I added this code to my statup.cs configureservices (I know hardcoding these values is not best practice but I want to just see if it could load the cert, I will wsitch to env variables and key vault):
// linux file path for private keys
var cryptBytes = File.ReadAllBytes("/var/ssl/private/<thumbprint>.p12");
var cert = new X509Certificate2(cryptBytes, "");
services.AddIdentityServer().AddSigningCredential(cert);
I enter a blank password because I think that is what you are supposed to do. I am now getting the following error in my docker logs which leads me to believe the cert loaded and now the error is related to me using both services.AddIdentityServer().AddSigningCredential(cert); in startup.cs configureservices and app.UseIdentityServer() in startup.cs configure:
Unhandled exception. System.InvalidOperationException: Decorator already registered for type: IAuthenticationService.
I am not sure how to add the cert to the app.UseIdentityServer(); line.
EDIT 5
after a lot more digging, unfortunately #tnc1997 answer will not work.
IN asp.net core 3 calls to app.UseIdentityServer in my satrtup.cs internally reverence a method that will look for the identity server Key,File,Pass etc in the appsetting(environment).json file.
As a result, even if I loaded the cert in code like tnc1997 shows, the application still looks in the settings file. So the settings file has to contain the corect details for the IS4 key.
Also, azure does not place the cert in the typical trusted location in the linux container. From what I have read, it appears that the only way to do this is to mount a volume (in this case an azure storage file share) and use the cert uploaded to that file share.
I can confirm that this works locally, but now I am still having issues running the container, the front end loads and it appears that the web api project does not start. I am going to post another question to address that issue.
Original Answer
I think the problem could be that you are attempting to load a certificate in a Linux container using the Windows certificate store.
The documentation here gives a good overview regarding how you can use an app service private certificate in a Linux hosted app:
In the Azure portal, from the left menu, select App Services > <app-name>.
From the left navigation of your app, select TLS/SSL settings, then select Private Key Certificates (.pfx) or Public Key Certificates (.cer).
Find the certificate you want to use and copy the thumbprint.
To access a certificate in your app code, add its thumbprint to the WEBSITE_LOAD_CERTIFICATES app setting.
The WEBSITE_LOAD_CERTIFICATES app setting makes the specified certificate accessible to your Linux hosted apps (including custom container apps) as files. The files are found under the following directories:
Private certificates - /var/ssl/private (.p12 files)
Public certificates - /var/ssl/certs (.der files)
Use the code sample below to load the specified certificate into your Linux hosted apps (including custom container apps):
using System;
using System.IO;
using System.Security.Cryptography.X509Certificates;
var bytes = File.ReadAllBytes($"/var/ssl/private/{Configuration["WEBSITE_LOAD_CERTIFICATES"]}.p12");
var cert = new X509Certificate2(bytes);
Signing Credentials
Here are the steps that I used to generate signing credentials:
Install OpenSSL.
Generate private key and public certificate.
Run openssl req -x509 -newkey rsa:4096 -sha256 -nodes -keyout example.com.key -out example.com.crt -subj "/CN=example.com" -days 365 replacing example.com with name of the site.
Combine the above into a single PFX file.
Run openssl pkcs12 -export -out example.com.pfx -inkey example.com.key -in example.com.crt replacing example.com with the name of the site.
Upload the PFX file to Azure.
In the Azure portal, from the left menu, select App Services > <app-name>.
From the left navigation of your app, select TLS/SSL settings, then select Private Key Certificates (.pfx), then upload the above PFX file.
Configure app settings.
Add the thumbprint of the PFX file above to the WEBSITE_LOAD_CERTIFICATES app setting in the App Service.
IdentityServer
The below code sample shows a complete Startup.cs configuration which could be used to get an IdentityServer application up and running:
namespace IdentityServer
{
public class Startup
{
public Startup(IConfiguration configuration, IWebHostEnvironment environment)
{
Configuration = configuration;
Environment = environment;
}
public IConfiguration Configuration { get; }
public IWebHostEnvironment Environment { get; }
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
void ConfigureDbContext(DbContextOptionsBuilder builder)
{
builder.UseNpgsql(Configuration.GetConnectionString("DefaultConnection"));
}
var builder = services.AddIdentityServer()
.AddConfigurationStore(options => { options.ConfigureDbContext = ConfigureDbContext; })
.AddOperationalStore(options => { options.ConfigureDbContext = ConfigureDbContext; });
if (Environment.IsDevelopment())
{
builder.AddDeveloperSigningCredential();
}
else
{
try
{
var bytes = File.ReadAllBytes($"/var/ssl/private/{Configuration["WEBSITE_LOAD_CERTIFICATES"]}.p12");
var certificate = new X509Certificate2(bytes);
builder.AddSigningCredential(certificate);
}
catch (FileNotFoundException)
{
throw new Exception($"The certificate with the thumbprint \"{Configuration["WEBSITE_LOAD_CERTIFICATES"].Substring(0, 8)}...\" could not be found.");
}
}
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment()) app.UseDeveloperExceptionPage();
app.UseIdentityServer();
}
}
}
Clean Architecture
The below code sample shows a complete DependencyInjection.cs configuration which could be used to get a Clean Architecture application up and running:
namespace CleanArchitecture.Infrastructure
{
public static class DependencyInjection
{
public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration)
{
void ConfigureDbContext(DbContextOptionsBuilder builder)
{
if (configuration.GetValue<bool>("UseInMemoryDatabase"))
{
builder.UseInMemoryDatabase("CleanArchitectureDb");
}
else
{
builder.UseSqlServer(configuration.GetConnectionString("DefaultConnection"), b => b.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName));
}
}
services.AddDbContext<ApplicationDbContext>(ConfigureDbContext);
services.AddScoped<IApplicationDbContext>(provider => provider.GetService<ApplicationDbContext>());
services.AddScoped<IDomainEventService, DomainEventService>();
services.AddDefaultIdentity<ApplicationUser>()
.AddEntityFrameworkStores<ApplicationDbContext>();
var builder = services.AddIdentityServer()
.AddConfigurationStore(options => { options.ConfigureDbContext = ConfigureDbContext; })
.AddOperationalStore(options => { options.ConfigureDbContext = ConfigureDbContext; })
.AddAspNetIdentity<ApplicationUser>();
var bytes = File.ReadAllBytes($"/var/ssl/private/{Configuration["WEBSITE_LOAD_CERTIFICATES"]}.p12");
var certificate = new X509Certificate2(bytes);
builder.AddSigningCredential(certificate);
services.AddTransient<IDateTime, DateTimeService>();
services.AddTransient<IIdentityService, IdentityService>();
services.AddTransient<ICsvFileBuilder, CsvFileBuilder>();
services.AddAuthentication()
.AddIdentityServerJwt();
return services;
}
}
}
I think the problem is that your application in the container does not trust the locally created developer certificate. Its something you can only use on your machine because a dev root certificate is installed on your computer.
The container will never trust the certificate created by dotnet dev-certs.
You need to get a properly trusted certificate, for example from LetsEncrypt.
I faced this issue running a .net core spa template on a linux app service. I also created a self signed .pfx as described in tnc1997's answer. Although the answer can be pieced together, for me the gotchas were:
When referencing your certificate path, don't use the uploaded .pfx file name. Instead, as previously stated, your cert file gets a new name ".p12" and is (in linux container) found under "/var/ssl/private/".
Specify a blank password. Don't specify the password for the uploaded .pfx file. Instead set appsetting "IdentityServer__Key__Password" to "" (empty).
The .Net Clean Architecture is calling services.AddIdentityServer() .AddApiAuthorization<ApplicationUser, ApplicationDbContext>(); in DependencyInjection.cs which is extension method. This method internally calls bunch of other methods one of which is .AddSigningCredentials(). Unfortunately this default method will fail in Linux environment because it cannot read bare private keys. According to this issue you need to construct PFX yourself in Linux.
Solution in my view:
Remove .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
Write own method
var bytes = File.ReadAllBytes($"/var/ssl/private/{thump_print_goes_here}.p12");
var certificate = new X509Certificate2(bytes);
var builder = services.AddIdentityServer()
.AddAspNetIdentity<ApplicationUser>()
.AddOperationalStore<ApplicationDbContext>()
.AddIdentityResources()
.AddApiResources()
.AddClients()
.AddSigningCredential(certificate);
The solution provided in edit 2 works. I have tested and verified this with my .NET6 app in a Linux App Service on Azure.
My appsettings.Production.json looks like this:
{
"IdentityServer": {
"Key": {
"Type": "File",
"FilePath": "/var/ssl/private/{thumbprintGoesHere}.p12",
"Password": ""
}
}
}
I have created my SSL certificate and SigningKey certificate in Azure Key Vaults as self-signed keys following this guide https://learn.microsoft.com/en-us/aspnet/core/blazor/security/webassembly/hosted-with-identity-server?view=aspnetcore-5.0&tabs=visual-studio. I use the WEBSITE_LOAD_CERTIFICATES to load the certificates to the /var/ssl/private/ path.
I hope this helps someone struggling with the same issues.
Ive set up my AzureAD in the portal, and an appservice that uses the AD to authenticate following instructions from microsoft.
Ive made a .net core app that uses this authorisation. It works on my localhost. But when i publish it i get this error
AADSTS50011: The reply url specified in the request does not match the reply urls configured for the application: '614f66a9-xxxx-483a-8bc7-xxxxxxx'
What should i change and how come it works in my local but not when published?
This is current configuration of app:
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "lmyName.onmicrosoft.com",
"TenantId": "******-ebd5-40d8-829b-*********",
"ClientId": "*****-8eef-483a-8bc7-********",
"CallbackPath": "/signin-oidc"
},
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
}
}
When i followed the online intructions i was directed to configure the appservice in the portal to use reply URL: /.auth/login/aad/callback
Could that be the same as callbackPath?
For your case, you can change your reply URL in AAD Application to be <YourApplicaitonURL>/signin-oidc.
NOTE The base address in the Sign-on URL and Logout URL settings is http://localhost:port.
This localhost address allows the sample app to run insecurely from your local system. Port is the default port for the Kestrel server. Update the reply URL in your AAD Application if you configure the app for production use(If you publish your App to Azure Web App service).
For example, https://yourapp.azurewebsites.net/signin-oidc or https://www.contoso.com/signout-oidc
You can also refer to this Sample to Integrate Azure AD into an ASP.NET Core web app.
Please let me know if it helps!
I have an asp.net core web application hosted in azure as an app service. I've configured the application to use OpenId Connect with Azure AD as the authority. The authentication happens within my application (I am not configuring the app service itself to handle the authentication).
Everything works fine when I hit the app service directly (or even if I use a custom domain name).
I've enabled the CDN service within the app service. Once the initial propagation finished, I open a browser and navigate to the CDN address ([name].azureedge.net).
I get a redirect to Azure AD, but once I finish the authentication process, I get an error.
It looks like when the redirect to Azure AD came back from the CDN, the app service's URL was set as the return_url. So when Azure AD redirected me, I was no longer hitting the CDN. When the redirect to Azure AD happens, there's a cookie placed in my browser; I suspect my site is looking for that cookie, but the browser didn't send it because it was set by a different domain.
I've tried configuring the CallbackPath in the OpenIdConnectOptions to the full URL (schema, host, domain, etc), but when my application initializes, and error is thrown saying that the path must start with a '/' (presumably it's expecting a path from the root of the domain in the request).
Hopefully someone else has come across this problem and can tell me what I'm doing wrong.
Per request, here's my OIDC configuration:
var openIdOptions = new OpenIdConnectOptions
{
ClientId = adSettings.ClientId,
ClientSecret = adSettings.ClientSecret,
Authority = adSettings.Authority,
CallbackPath = adSettings.CallbackPath,
ResponseType = OpenIdConnectResponseType.CodeIdToken,
Events = new OpenIdConnectEvents { OnTicketReceived = AddApplicationRolesToUserClaimsAsync, OnAuthorizationCodeReceived = RedeemCodeAsync }
};
foreach (var scope in adSettings.Scopes.Concat(settings.MicrosoftGraph.Scopes))
openIdOptions.Scope.Add(scope);
application.UseOpenIdConnectAuthentication(openIdOptions);
adSettings is a POCO that is hydrated from the following appsettings.json:
"AzureAd": {
"AADInstance": "https://login.microsoftonline.com/",
"ClientSecret": "REDACTED",
"CallbackPath": "/signin-oidc",
"ClientId": "REDACTED",
"TenantId": "REDACTED",
"Scopes": [
"openid",
"profile",
"offline_access"
]
}
adSettings.Authority is defined in the POCO as:
public string Authority => $"{AADInstance}{TenantId}/v2.0";
After digging around a bit, I found the answer.
The OpenIdConnectOptions.Events property allows you to hook into various events that happen throughout the lifecycle of authentication. One callback is called OnRedirectToIdentityProvider. It provides a RedirectContext. On that object you can read/write to a property called ProtocolMessage.RedirectUri. That property allows you to specify a full URL which is used as the return_url when the user is forwarded to AAD.
It's worth noting that I'm using the Microsoft.AspNetCore.Authorization.OpenIdConnect packge from Nuget. There are other packages available that provide similar functionality that do allow you to set a full URL in the options object.