OpenIddict based Identity server validates the token in its own [Authorized] Controllers, but it rejects the token when accessed from another Resource Server through /introspect endpoint.
Everything worked fine in the development machine. This is happening after deploying the service to a Linux server where the services are hosted on different ports of the same machine.
This is the actual exception in my Logs:
Microsoft.IdentityModel.Tokens.SecurityTokenInvalidIssuerException: IDX10205: Issuer validation failed. Issuer: 'System.String'. Did not match: validationParameters.ValidIssuer: 'System.String' or validationParameters.ValidIssuers: 'System.String'.
The setup is similar to this:
https://github.com/openiddict/openiddict-samples/blob/dev/samples/Zirku/Zirku.Server/Startup.cs
This is my openiddict setup:
services.AddOpenIddict()
.AddCore(options =>
{
// Configure OpenIddict to use the Entity Framework Core stores and models.
// Note: call ReplaceDefaultEntities() to replace the default OpenIddict entities.
options.UseEntityFrameworkCore()
.UseDbContext<ApplicationDbContext>();
}).AddServer(options =>
{
// Enable the authorization, logout, token and userinfo endpoints.
options.SetAuthorizationEndpointUris("/connect/authorize")
.SetLogoutEndpointUris("/connect/logout")
.SetTokenEndpointUris("/connect/token")
.SetIntrospectionEndpointUris("/connect/introspect")
.SetUserinfoEndpointUris("/connect/userinfo");
// Mark the "email", "profile" and "roles" scopes as supported scopes.
options.RegisterScopes(OpenIddictConstants.Scopes.Email, OpenIddictConstants.Scopes.Profile,
OpenIddictConstants.Scopes.Roles);
// Note: this sample only uses the authorization code flow but you can enable
// the other flows if you need to support implicit, password or client credentials.
options.AllowAuthorizationCodeFlow().RequireProofKeyForCodeExchange();
options.AllowClientCredentialsFlow();
options.AllowPasswordFlow();
options.AllowRefreshTokenFlow();
// Register the signing and encryption credentials.
options.AddDevelopmentEncryptionCertificate();
// .AddDevelopmentSigningCertificate();
// Encryption and signing of tokens
options.AddEphemeralEncryptionKey()
.AddEphemeralSigningKey();
options.RegisterScopes(ApplicationConstants.MobileApiResource);
options.SetAccessTokenLifetime(TimeSpan.FromMinutes(5));
options.SetIdentityTokenLifetime(TimeSpan.FromMinutes(15));
options.SetRefreshTokenLifetime(TimeSpan.FromHours(1));
// Register the ASP.NET Core host and configure the ASP.NET Core-specific options.
options.UseAspNetCore()
//todo remove the disable transport layer security
.DisableTransportSecurityRequirement()
.EnableAuthorizationEndpointPassthrough()
.EnableLogoutEndpointPassthrough()
.EnableTokenEndpointPassthrough()
.EnableUserinfoEndpointPassthrough()
.EnableStatusCodePagesIntegration();
options.DisableAccessTokenEncryption();
})
.AddValidation(options =>
{
// Import the configuration from the local OpenIddict server instance.
options.UseLocalServer();
// Register the ASP.NET Core host.
options.UseAspNetCore();
});
This is the setup on my API:
services.AddAuthentication(options =>
{
options.DefaultScheme = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme;
});
services.AddOpenIddict()
.AddValidation(options =>
{
// Note: the validation handler uses OpenID Connect discovery
// to retrieve the address of the introspection endpoint.
options.SetIssuer(identityUrl);
options.AddAudiences("client_id");
// Configure the validation handler to use introspection and register the client
// credentials used when communicating with the remote introspection endpoint.
options.UseIntrospection()
.SetClientId("client_id")
.SetClientSecret("secret");
// Register the System.Net.Http integration.
options.UseSystemNetHttp();
// Register the ASP.NET Core host.
options.UseAspNetCore();
});
Finally, with the help of my senior, we were able to diagnose the issue. This issue was related to nginx.
The server was only returning the issuer domain address but not the port, itwas returning xyz.com as the issuer instead of the actual issuer address xyz.com:5001
The proper resolution is to adjust the nginx conf proxy's Host header directive.
Changed this:
proxy_set_header Host $host;
To this:
proxy_set_header Host $http_host;
also added this to the Startup.cs of the identity server:
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
Related
After deploy asp.net core mvc 6 to iis server the user in case submit a true login not redirected to the controller and not authenticated, when I tracked the cookie in developer tools there is a ! mark on .AspNetCore.Identity.Application and when hover on it there is a message show (This cookie was blocked because it had the "Secure" attribute and the connection was not secure).
What I tried Before
Changing the cookie configuration
Exchange return LocalRedirect(returnUrl) To return RedirectionToAction("Index","Home")
Add [AllowAnonymous] attribute on LoginModel in Areas.Identity.Pages.Account
Remove Use.HttpsRedirection(); from program.cs
Because there are many apps host in the server so the browser blocked the cookie cause the other apps have the same cookie name which .AspNetCore.Identity.Application. So simply the issue can be solved by changing the name of the cookie:
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme;
});
builder.Services.ConfigureApplicationCookie(options => options.Cookie.Name = "AppNameForExample");
Your cookies are configured to require an HTTPS connection. When you try to set them on a non-secure connection, they will be rejected. You can try these steps:
Obtain a valid SSL certificate: You'll need a certificate that is trusted by the browsers you're targeting. Check your web.config file settings for:
<httpCookies requireSSL="true" />
Configure IIS to use HTTPS: This involves binding the SSL certificate to the IIS website and enabling HTTPS.
Update your ASP.NET Core application to use HTTPS: In the Startup.cs file, you can use the following code to redirect all HTTP traffic to HTTPS:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Rewrite;
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
var options = new RewriteOptions().AddRedirectToHttps();
app.UseRewriter(options);
// rest of the code...
}
I have set up an Authorization Server using OpenIddict 3.1.1 (porting over an existing one that was using the older ASOS package directly). I believe I am most of the way there, because when using the client application, I am able to log in, give consent, redirect back to the client, and exchange the authorization code for an access token.
However, when I try to do the same using Postman's OAuth 2.0 authentication support, I am able to log in (and give consent), but when it completes and returns the authorization code, I receive an HTTP 403 from the https://oauth.pstmn.io/v1/callback that I am redirected to:
403 ERROR
The request could not be satisfied.
This distribution is not configured to allow the HTTP request method that was used for this request. The distribution supports only cachable requests. We can't connect to the server for this app or website at this time. There might be too much traffic or a configuration error. Try again later, or contact the app or website owner.
If you provide content to customers through CloudFront, you can find steps to troubleshoot and help prevent this error by reviewing the CloudFront documentation.
Generated by cloudfront (CloudFront)
Request ID: UAXpago6ISiqbgm9U_SVPwh96qz1qoveZWFd0Cra-2FximeWZiY2aQ==
From what I can tell, this is because OpenIddict is issuing a POST request back to the callback url. This works for my client application, but evidently is not supported by Postman.
What configuration tweak do I need to make to OpenIddict to support this in postman?
OpenIddict related config in Startup.ConfigureServices:
services.AddOpenIddict()
.AddCore(options => {
options.AddApplicationStore<ClientStore>();
options.UseEntityFramework()
.UseDbContext<OAuthServerDbContext>()
.ReplaceDefaultEntities<Client, Authorization, OAuthScope, Token, long>()
;
})
.AddServer(options => {
options.RegisterClaims();
options.RegisterScopes(OpenIddictConstants.Scopes.OpenId,
OpenIddictConstants.Scopes.Email,
OpenIddictConstants.Scopes.OfflineAccess,
OpenIddictConstants.Scopes.Profile,
"user");
// flows
options.AllowAuthorizationCodeFlow();
options.AllowRefreshTokenFlow();
options.AllowPasswordFlow();
options.AllowHybridFlow();
// implicit is used by postman
options.AllowImplicitFlow();
var serviceProvider = options.Services.BuildServiceProvider();
var oauthConstants = serviceProvider.GetRequiredService<IOptions<OAuthConstants>>().Value;
var tokenLifetimes = serviceProvider
.GetRequiredService<IOptions<OpenIdConnectServerTokenLifetimeSettings>>().Value;
// security
options.SetAccessTokenLifetime(tokenLifetimes.AccessTokenLifetime)
.SetAuthorizationCodeLifetime(tokenLifetimes.AuthorizationCodeLifetime)
.SetIdentityTokenLifetime(tokenLifetimes.IdentityTokenLifetime)
.SetRefreshTokenLifetime(tokenLifetimes.RefreshTokenLifetime);
options.SetIssuer(new Uri("https://localhost/oauth/"));
// custom handlers added here
options.AddEventHandlers();
// certificate details hidden
options.AddEncryptionCertificate(certificate);
// endpoints
options.SetAuthorizationEndpointUris("/OpenIdConnect/Authorize");
options.SetLogoutEndpointUris("/OpenIdConnect/Logout", "/Account/Logout");
options.SetRevocationEndpointUris("/OpenIdConnect/Revoke");
options.SetTokenEndpointUris("/OpenIdConnect/Token");
options.SetCryptographyEndpointUris("/OpenIdConnect/JWKDoc");
options.SetUserinfoEndpointUris("/OpenIdConnect/UserInfo");
options.UseAspNetCore()
.EnableStatusCodePagesIntegration()
.EnableAuthorizationEndpointPassthrough()
//.EnableTokenEndpointPassthrough()
.EnableLogoutEndpointPassthrough()
.EnableUserinfoEndpointPassthrough()
;
})
.AddValidation(options => {
options.UseLocalServer();
options.UseAspNetCore();
var serviceProvider = options.Services.BuildServiceProvider();
var config = serviceProvider.GetRequiredService<IConfiguration>();
options.SetClientId(config.GetValue<string>(nameof(Settings.OAuthClientId)));
options.SetClientSecret(config.GetValue<string>(nameof(Settings.ClientSecret)));
// certificate details hidden
options.AddEncryptionCertificate(certificate);
});
Postman details:
Authorization
Token Name: Redacted
Grant Type: Authorization Code
Callback URL: disabled, https://oauth.pstmn.io/v1/callback
Authorize using browser: checked
Auth URL: https://localhost/oauth/OpenIdConnect/Authorize
Access Token URL: https://localhost/oauth/OpenIdConnect/Token
Client ID: redacted, but correct
Client Secret: redacted, but correct
Scope: openid offline_access
State:
Client Authentication: Send client credentials in body
edit: The response that it sends to the postman callback URI does include the authorization code in the body, but because of the 403 response, Postman doesn't parse that out and make the follow-up request to exchange the code for the token.
There is an option that you can set to control if the authorization code is received in the URL as a query string or in the body as a post. The option is response_mode and you control that as a client.
I believe if it is not set to response_mode=form_post, then you will get the code in the URL instead.
See the details about this parameter here.
I have a Blazor WASM project with a Blazor Client and ASP.NET core server. I can authenticate with user/password using the following code:
services
.AddDefaultIdentity<ApplicationUser>(
options => options.SignIn.RequireConfirmedAccount = true)
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services
.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
services
.AddAuthentication()
.AddIdentityServerJwt();
services.AddTransient<IProfileService, ProfileService>();
services.AddAuthorization(options =>
{
options.AddPolicy("ApiScope", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireClaim("scope", "api1");
});
});
When I add the following code, I can successfully authenticate with clientcredentials from a console client. But then the Blazor client user/password authentication stops working.
...
services
.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>()
+.AddInMemoryApiScopes(Config.ApiScopes)
+.AddClientStore<ClientStore>()
+.AddDeveloperSigningCredential();
services
.AddAuthentication()
.AddIdentityServerJwt();
+services
+ .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
+ .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
+ {
+ options.Authority = "https://localhost:44311";
+ options.TokenValidationParameters = new TokenValidationParameters
+ {
+ ValidateAudience = false,
+ };
+ });
...
In the browser while trying to authenticate in the Blazor client, the console prints:
info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2]
Authorization failed. These requirements were not met:
DenyAnonymousAuthorizationRequirement: Requires an authenticated user.
I have tried a lot, but I'm not able to make both work together. It seems that somehow this configuration requires authentication for everything, even the pages/controllers that are marked AllowAnonymous. So, when I try to authenticate, it gives me an error telling me the user must be authenticated: DenyAnonymousAuthorizationRequirement. The policy, "ApiScope" is only intended for the clientcredentials client, not for the Blazor client. If removed, the RequireAuthenticatedUser call doesn't make a difference, same error.
Any help is appreciated.
I have an Asp.Net Core project setup using openiddict. One of the packages I use (Asp.Net Odata) does not support endpoint routing, so I've disabled it in ConfigureServices
services.AddControllers(c=>c.EnableEndpointRouting = false)
The problem is when I do this the openiddict extension method GetOpenIddictServerRequest returns null. Everything works fine as long as endpoint routing is enabled
[HttpPost("~/oauth/token"), Produces("application/json")]
public async Task<IActionResult> Token()
{
var request = HttpContext.GetOpenIddictServerRequest();
...
I register openiddict as shown below
services.AddOpenIddict()
.AddCore(options =>
{
// Configure OpenIddict to use the Entity Framework Core stores and entities.
options.UseEntityFrameworkCore()
.UseDbContext<MiPayOnlineCoreContext>();
})
.AddServer(options =>
{
options.SetAccessTokenLifetime(TimeSpan.FromMinutes(5));
options.SetRefreshTokenLifetime(TimeSpan.FromMinutes(11));
options.SetTokenEndpointUris("/oauth/token");
options.AllowPasswordFlow();
options.AllowRefreshTokenFlow();
options.SetIssuer(new Uri(authSettings[nameof(JWTSettings.Issuer)]));
options.AddSigningCertificate(certificate);
options.AddEncryptionCertificate(certificate);
// Mark the "email", "profile" and "roles" scopes as supported scopes.
options.RegisterScopes(OpenIddictConstants.Scopes.Email,
OpenIddictConstants.Scopes.Profile,
OpenIddictConstants.Scopes.Roles);
options.UseAspNetCore()
.EnableStatusCodePagesIntegration()
.EnableAuthorizationEndpointPassthrough()
.EnableLogoutEndpointPassthrough()
.EnableTokenEndpointPassthrough()
.EnableUserinfoEndpointPassthrough();
});
Is it possible to make this work with endpoint routing disabled?
It's a bug in your code: you didn't put app.UseMvc() at the right place.
Make sure it appears after app.UseAuthentication() and app.UseAuthorization() and the error will go away.
I implemented a token server using Identity Server 4.
I added a custom API endpoint to the token server and struggle with the authentication. The custom endpoint is inherited from ControllerBase and has 3 methods (GET, POST, DELETE).
I intend to call the custom endpoint from within another API using a dedicated client with credentials (server to server) implemented as HttpClient in .NET Core. There is no user involved into this.
For getting the access token I use the IdentityModel DiscoveryClient and TokenEndpoint.
So in sum I did the following so far:
setup "regular" identity server and validate it works -> it works
implement custom endpoint and test it without authorizatio -> it works
add another api resource ("api.auth") with a custom scope "api.auth.endpoint1"
setup a client with client credentials allowing access to scope "api.auth.endpoint1".
implement the HttpClient and test setup -> I get an access token via the Identity Model Token Endpoint.
Now, when I call the endpoint using the HttpClient with the access token I received I get response code 200 (OK) but the content is the login page of the identity server.
The documentation of Identity Server 4 state the use of
services.AddAuthentication()
.AddIdentityServerAuthentication("token", isAuth =>
{
isAuth.Authority = "base_address_of_identityserver";
isAuth.ApiName = "name_of_api";
});
as well as the use of
[Authorize(AuthenticationSchemes = "token")]
Unfortunatly the compiler state that .AddIdentityServerAuthentication can't be found. Do I miss a special nuget?
The nugets I use on the token server so far are:
IdentityServer4 (v2.2.0)
IdentityServer4.AspNetIdentity (v2.1.0)
IdentityServer4.EntityFramework (v2.1.1)
Figured out that part. The missing nuget for AddIdentityServerAuthentication is:
IdentityServer4.AccessTokenValidation
Struggling with the authorization based on the custom scope.
Does anyone know how the security has to be configured?
Configure a client with ClientGrantTypes = client_credentials and your api like this:
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:5000";
options.ApiName = "api.auth";
});
Where ApiName is the name of the resource. Please note that resource != scope. In most samples the resource name is equal to the scope name. But not in your case, where resource name is api.auth and scope name is api.auth.endpoint1.
Configure the client to request the scope.
var tokenClient = new TokenClient(disco.TokenEndpoint, clientId, secret);
var tokenResponse = await tokenClient.RequestClientCredentialsAsync("api.auth.endpoint1");
IdentityServer will lookup the Resource name and add that to the token as audience (aud) while the scope is added as claim with type scope.
This should be enough to make it work. Also check the sample project.
Custom authentication scheme and scope based policies for different access rights bundled together looks like that:
// Startup.ConfigureServices
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication("CustomAuthEndpointsAuthenticationScheme", options =>
{
options.Authority = "http://localhost:5000";
options.ApiName = "api.auth"; //IdentityServer4.Models.ApiResource.Name aka Audience
});
services.AddAuthorization(options =>
{
options.AddPolicy("Endpoint1Policy", policy => {
policy.AddAuthenticationSchemes(new string[] { "CustomAuthEndpointsAuthenticationScheme" });
policy.RequireScope("api.auth.endpoint1"); } ); //IdentityServer4.Models.Scope.Name
options.AddPolicy("Endpoint2Policy", policy => {
policy.AddAuthenticationSchemes(new string[] { "CustomAuthEndpointsAuthenticationScheme" });
policy.RequireScope("api.auth.endpoint2"); } ); //IdentityServer4.Models.Scope.Name
} );
// securing the custom endpoint controllers with different access rights
[Authorize(AuthenticationSchemes = "CustomAuthEndpointsAuthenticationScheme", Policy = "Endpoint1Policy")]
It seems not to interfere with the IdentityServer4 default endpoints nor with the ASP.NET Core Identity part.