I have two services implemented as Web APIs in ASP.NET Core, dockerized and orchestrated (docker-compose and Kubernetes). One service provides authentication and authorization (authNZ, IdentityService), and the other provides resources to authNZ'd users (ResourceService).
Any authenticated user (OIDC-based authentication against Google) has a JWT token, which they can use to add as a bearer token to their API calls to the ResourceService.
Q1: Should ResourceService validate every token calling IdentityService?
Supposing the answer is yes, the authorization middleware of the ResourceService fails with the following error when validating authNZ to an API endpoint.
Authorization failed. These requirements were not met:
DenyAnonymousAuthorizationRequirement: Requires an authenticated user.
Exception occurred while processing message.
System.InvalidOperationException: IDX20803: Unable to obtain configuration from: 'https://identity/.well-known/openid-configuration'.
Q2: Do I need to implement the .well-known endpoints in the IdentityService to validate the JWT tokens?
I am aware of the complexities and challenges associated with implementing an Identity service without leveraging dedicated libraries (e.g., IdentityServer4). However, given some licensing issues, I cannot leverage such libraries.
I am sharing some setups I find most relevant, but happy to share other parts of the code if needed.
AuthNZ configuration of the ResourceService:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.Authority = "https://identity";
options.Audience = "https://resource";
});
AuthNZ configuration of the IdentityService:
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.Authority = "https://identity";
options.Audience = "https://resource";
})
.AddCookie(options =>
{
options.LoginPath = "/api/v1/authnz/signin";
})
.AddGoogle(GoogleDefaults.AuthenticationScheme, options =>
{
options.ClientId = "...";
options.ClientSecret = "...";
options.CallbackPath = "/authnz/google/callback";
});
Clients and APIs needs to be able to download the configuration document at https://identity/.well-known/openid-configuration and it should be a public document that is not protected in anyway.
When you get this error:
Exception occurred while processing message. System.InvalidOperationException: IDX20803: Unable to obtain configuration from
It is typically because the service can't reach the IdentityProvider. Typically it s a HTTPS or networking issue in your backend.
Every service that provides tokens should preferably expose a configuration document. If you are using IdentityServer, this is built-in.
The alternative is that you provide the public signing key manually to each service.
To fix Q1, the it all depends on the deployment and network setup of your services. It is typically either due to HTTPS certificate issues or if one service can't talk to other services behind the firewall. Sometimes you might need to use a different "service name" when you need to send HTTP(s) requests behind the firewall.
Related
I would like to make an identity service with IdServer4 that outsources the 'authentication' part to Auth0 - Auth0 deals with Single Sign On and other stuff and does a great job - so no need to reinvent the wheel. But I would like to embed this in an identity server (pref. IdentityServer4), that handles authentication via Auth0 and handles authorization itself (claims and scopes) for users & machines.
Machines would acquire their token through the tokenClient via so-called Client Credentials (https://docs.identityserver.io/en/latest/quickstarts/1_client_credentials.html).
public static IEnumerable<Client> Clients =>
new List<Client>
{
new Client
{
ClientId = "client",
// no interactive user, use the clientid/secret for authentication
AllowedGrantTypes = GrantTypes.ClientCredentials,
// secret for authentication
ClientSecrets =
{
new Secret("secret".Sha256())
},
// scopes that client has access to
AllowedScopes = { "api1" }
}
};
The machine 2 machine auth works. But how can the identity server make sure that 'users' log in via Auth0 (SSO) and then get an access token from IdentityServer4 itself (just like the machines), instead of getting the token from Auth0 itself. I have implemented Auth0 as a external ID Provider:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect("Auth0", options => {
options.Authority = "auth0domain";
options.ClientId = "clientId";
options.ClientSecret = "secret";
...
});
For the rest, see : https://auth0.com/blog/using-csharp-extension-methods-for-auth0-authentication/
When triggering the Authentication via await HttpContext.ChallengeAsync(); the user can login. And afterwards he or she can logout. This works fine. But the user acquires an access token from Auth0 itself and I would like to replace it by a token generated by IdSrv4. Is this possible?
You need to use Identity Server as the base authentication server and configure SSO as an external login. Just like when you login a website using google, facebook, etc. The only consideration is that the SSO server should support a standard like OIDC. Take a look at
https://docs.duendesoftware.com/identityserver/v6/ui/login/external/
https://docs.identityserver.io/en/latest/topics/signin_external_providers.html
You can do any authentication stuff (e.g. adding claims) at login callback handler
Are you tied to IdentityServer4? Might be worth looking into OpenIddict as an alternative. I've just implemented this and an API secured by it using the provided tokens - worked a treat.
Apologies if I've missed the point of your question
I am trying to authenticate CORS origin requests and set Claims principle with the user of internal company single sign on utility. I have the current setting so far, the cookie will never get created on the domain set at the authentication setup.
I have an Angular client application and .Net Core 3.0 Webapi, the requirement is for the client to be able to set authentication for future api calls.
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.Cookie.Name = "access_token";
options.Cookie.SameSite = SameSiteMode.None;
options.Cookie.Domain = "localhost:xxxx";
});
//CORS
services.AddCors(options =>
{
options.AddPolicy(
"AllowOrigin",
builder => builder.WithOrigins("localhost:xxxx")
.AllowCredentials()
.AllowAnyHeader()
.AllowAnyMethod());
});
//Sign In
HttpContext.SignInAsync(
scheme: CookieAuthenticationDefaults.AuthenticationScheme,
principal: new ClaimsPrincipal(new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme)),
properties: new AuthenticationProperties { ExpiresUtc = DateTime.Now.Add(120) });
I am testing this all on local so both URLS are localhost with different ports
Angular is hosted: http://localhost:xxxx
WebAPi is hosted :http://localhost:xxx2
http request from Angular to webapi is http://localhost:xxx2/api/auth which has the SignInAsync call, the company single sign does a username but the cookie never gets created. If I remove the options.Cookie.Domain = "localhost:xxxx"; the cookie does get created on the webapi domain http://localhost:xxx2. I must be missing something here.
After reading up some other posts on stackoverflow , it tuned out that AllowAllOrigins will only fix this problem but poses a threat.
So I ended up fixing this issue with JWT - setting authorization token for every request sent from client interface. This issue was caused due to fact that the client and WebApi are hosted on different domains.
Im currently struggling to connect a ASP.NET Core 2.2 Web API to an existing Azure AD. I based my configuration upon this sample code by the ASP.NET Core team. Cookies were replaced with JWTs.
Unable to retrieve document from metadata adress
Now I face the following error message:
IOException: IDX10804: Unable to retrieve document from: {MetadataAdress}.
- Microsoft.IdentityModel.Protocols.HttpDocumentRetriever+<GetDocumentAsync>d__8.MoveNext()
- System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
- System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
- Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectConfigurationRetriever
+<GetAsync>d__3.MoveNext()
- System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
- System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
- Microsoft.IdentityModel.Protocols.ConfigurationManager+<GetConfigurationAsync>d__24.MoveNext()
When I call the URL directly, I receive an instant response with the configuration file. However, the code does not seem to be able to do it. Im not sure what the reason could be.
Azure AD Configuration Syntax
The most likely cause of this issue is a configuration mistake. Maybe I have mistaken a field's syntax or am missing an important value.
Connection Info Fields
The connection info fields are provided like this:
TenantId: {Tenant-GUID}
Authority: https://login.microsoftonline.com/{TenantId}
Resource: https://{resource-endpoint}.{resource-domain}
ClientId: {Client-GUID}
ClientSecret: {ClientSecret}
Service Configuration
The authentication service configuration in the Startup.cs looks like this:
services
.AddAuthentication(options => {
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddJwtBearer()
.AddOpenIdConnect(options => {
options.ClientId = this.ClientId;
options.ClientSecret = this.ClientSecret;
options.Authority = this.Authority;
options.Resource = this.Resource;
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
options.SignedOutRedirectUri = "/signed-out";
options.Events = new OpenIdConnectEvents()
{
OnAuthorizationCodeReceived = async context =>
{
var request = context.HttpContext.Request;
var currentUri = UriHelper.BuildAbsolute(
request.Scheme, request.Host, request.PathBase, request.Path
);
var credentials = new ClientCredential(this.ClientId, this.ClientSecret);
var authContext = new AuthenticationContext(
this.Authority,
AuthPropertiesTokenCache.ForCodeRedemption(context.Properties)
);
var result = await authContext.AcquireTokenByAuthorizationCodeAsync(
context.ProtocolMessage.Code,
new System.Uri(currentUri),
credentials,
this.Resource
);
context.HandleCodeRedemption(result.AccessToken, result.IdToken);
}
};
// Custom
options.MetadataAddress = $"{this.Authority}/federationmetadata/2007-06/federationmetadata.xml";
options.RequireHttpsMetadata = false; // Dev env only
}
Existing APIs
There is a bunch of existing Web APIs that connect to this Azure AD. Sadly, they are all using the full .NET Framework. They use the UseWindowsAzureActiveDirectoryBearerAuthentication method from the Microsoft.Owin.Security.ActiveDirectory namespace's WindowsAzureActiveDirectoryBearerAuthenticationExtensions.
Another thing they use is the HostAuthenticationFilter with an authentication type of Bearer.
Questions
What is the problem?
How can I resolve this issue?
How can I use these components together?
ASP.NET Core 2.2
JWT Bearer Authentication
Azure AD (token validation + claim extraction only - creation is handled by other service)
You are using OpenIDConnect libraries and point them to WS-Federation metadata (/federationmetadata/2007-06/federationmetadata.xml). This is not going to work.
The correct metadata endpoint for OpenIDConnect is /.well-known/openid-configuration. This is described here. Change that first, and then return cookies.
UPDATE
What I oversaw, was that you are protecting WebAPI. You say the middleware to use JwtBearer as default authentication cheme, but you also include a challenge scheme to be OIDC. That doesn't really make sense for me. Why do you want an OIDC challenge scheme for an WebAPI?
Here you can find the ASP.NET Core samples about JwtBearer. Here the Azure AD samples demoing WebApp calling WebApi (also bearer for the WebAPI, OIDC for the App FrontEnd.
There are no samples for JWT Bearer Auth using OIDC challenge. Why do you want to implement that? What is the case? You might be looking at implementing multiple Authentication schemes, which is possible. But not having one scheme for Authentication and another for challenge...
If by updating/removing the wrong metata changes the error message, include that in your original question. As it is now - the pure error message is that OIDC Middleware cannot parse WS-Federation metadata. Which is expected.
Source of the problem
After some testing I managed to identify the problem: Apparently the main cause of this issue was network related. When I switched from our company's to an unrestricted network the authentication was a success.
The fix
I had to configure a proxy and provide it to the JwtBearer and OpenIdConnect middleware. This looks like this:
var proxy = new HttpClientHandler
{
Proxy = new WebProxy("{ProxyUrl}:{ProxyPort}") { UseDefaultCredentials = true; },
UseDefaultCredentials = true
};
services
.AddJwtBearer(options => {
// ... other configuration steps ...
options.BackchannelHttpHandler = proxy;
})
.AddOpenIdConnect(options => {
// ... other configuration steps ...
options.BackchannelHttpHandler = proxy;
})
Metadata adress
#astaykov was right that the metadata adress is indeed incorrect. I had this feeling as well but kept it as previous APIs were running successfully with it. During problem testing I removed it, too, but it would not make a difference due to the network issues.
After the network issues were resolved, using the default metadata adress worked. The custom one failed - as expected when using a different authentication schema.
I'm creating a new WebApp using ASP.NET Core 2.2 and Razor Pages and I want to authenticate users using AzureAD. This works fine locally with localhost and I can sign in and out with no problem. But when I publish it to azure I can not sign in. After the microsoft sign in pages I am redirected to my WebApp to the page "/.auth/login/done" that says: "You have successfully signed in. Return to the website" and I can return to my website but I am not logged in.
In the App Registration in Azure I have configured the redirect Urls for localhost and for the application. For localhost it looks like "https://localhost:44321/.auth/login/aad/callback" and for the app something like "https://maywebapp.azurewebsites.net/.auth/login/aad/callback".
I configured the App to use always https to make sure that the Url is the same as the configured in Azure.
This is my Service Configuration:
services.AddAuthentication(options =>
{
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddOpenIdConnect(options =>
{
options.Authority = Configuration["Microsoft:Authority"];
options.ClientId = Configuration["Microsoft:ClientId"];
options.CallbackPath = Configuration["Microsoft:CallbackPath"];
options.ResponseType = OpenIdConnectResponseType.IdToken;
options.SignedOutRedirectUri = Configuration["Microsoft:SignedOutRedirectUri"];
options.TokenValidationParameters.NameClaimType = "name";
})
.AddCookie();
I expect the same behaviour as when I am running the working App locally.
To make all accesses to pages in this folder be authenticated, we could add the Authorize attribute to all page model classes, but we can do better than that. Going back to the Startup class, we can add some Razor Pages conventions to make our lives easier.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizeFolder("/Account");
});
...
}
Here is an article about authenticate with asp.net razor.
I have an ASP Net core API app with Identity Core for user and claim management and Microsoft.AspNetCore.Authentication as an Authentication middle-ware. I am using JWT middleware to issue bearer tokens.
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(cfg =>
{
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = Configuration["JwtIssuer"],
ValidAudience = Configuration["JwtIssuer"],
IssuerSigningKey = issuerSigningKey,
ClockSkew = TimeSpan.Zero // remove delay of token when expire
};
});
This works fine for Username/Password scenarios. Now I want to use another API (Azure Logic App) to make some REST calls to the ASP Core API, which has protected endpoints.
I am looking for guidance on how to achieve this. I have a couple of ideas:
Create a user in the identity table which will act as a service/automation account, same as a normal human user. Advantage - no change required. Downside - sounds a bit hacky.
Create some sort of an encrypted string/token, save it in API app.settings, and ask the consumer APIs to pass it in the header. Advantage - Easy to implement, downside - not sure how it will work with JWT bearer authentication pipeline. Also, sounds kind of static and insecure.
Implement something like an App ID and Secret. Advantage - Seems like a good practice, is scale-able for multiple Apps (consumers). Downside - I have no idea how to implement it using ASP core Identity and that too along with JWT pipeline.
I would really appreciate some guidance.