Local validation of IdentityServer - c#

I have Auth Service hosted on some url. All my microservices requested validation to auth on each requests. In StartUp.cs of each services I have
app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = WebConfigurationManager.AppSettings["IdentityServerURL"],
ValidationMode = ValidationMode.ValidationEndpoint,
//ValidationMode = ValidationMode.Local,
RequiredScopes = new[] { "user-api" },
});
It works fine! And in my controller's method in this case I have as you can see
{role: consumer}
But if I change
ValidationMode = ValidationMode.Local,
My request doesn't pass Authorization because values of roles has prefixes
And according to this my request doesn't pass autorization. What should I do in case
ValidationMode = ValidationMode.Local
to have normal value of claims role?

Microsoft apply a claims mapping when the token is received. To remove this default mapping, add this to your Configuration method at startup:
JwtSecurityTokenHandler.InboundClaimTypeMap.Clear();
For more information:
How to use InboundClaimTypeMap for claim mapping?
Update of System.IdentityModel.Tokens.Jwt causing breaking change in IdentityServer3 Client

Related

WebAPI making an extra trip for user claims using OIDC authentication handler

My Current Setup is:
I have an Identity server built using Duenede.IdentityServer package running at port 7025.
I have a WebApi which is Dotnet 6 based and below is its OIDC configuration.
AddOpenIdConnect("oidc", o =>
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
o.SaveTokens = true;
o.GetClaimsFromUserInfoEndpoint = true;
o.RequireHttpsMetadata = false;
o.ResponseType = "code";
o.Authority = "https://localhost:7025/";
o.ClientId = "some clientid";
o.ClientSecret = "some secret";
o.Scope.Clear();
o.Scope.Add("openid");
o.Scope.Add("profile");
o.Scope.Add("dotnetapi");
o.NonceCookie.SameSite = SameSiteMode.Unspecified;
o.CorrelationCookie.SameSite = SameSiteMode.Unspecified;
o.ClaimActions.MapUniqueJsonKey("role", "role");
o.ClaimActions.MapUniqueJsonKey("email", "email");
});
Now when web api request the token from the identityserver (OIDC is the challenge scheme and i have a cookie scheme set as default authentication scheme) it gets both id_token and access_token(verified using await HttpContext.GetTokenAsync("access_token"); await HttpContext.GetTokenAsync("id_token");). I can also find user claims in HttpContext.User.FindFirst("some claim");
But i have noticed that there is an extra call to the identity server from web api for the userinfo. I observed that it may be because of o.GetClaimsFromUserInfoEndpoint = true; when i omitted this line i found that user claims are not set, even though i am still getting both id and access token.
So my understanding is the OIDC client of dotnet is using userinfo endpoint to fetch the user claims. But my question is if i am already receiving the access_token why there is an extra call for the userinfo. Can this extra call be prevented?
is there any way so that i receive id_token at first and access_token is then fetched as it is doing now so that same information is not sent twice?
First, you can set this client config in IdentityServer to always include the user claims in the ID token
AlwaysIncludeUserClaimsInIdToken
When requesting both an id token and access token, should the user
claims always be added to the id token instead of requiring the client
to use the userinfo endpoint. Default is false.
The reason for not including it in the ID-token is that increases the size of the id-token and if you store the tokens in the asp.net session cookie, it also can become pretty big.
I wouldn't worry about the extra request that happens when the user authenticates.

No authenticationScheme was specified error even when JWT Authentication Specified

I'm new to AzureAD authentication. I setup my Web API with below settings in startup.cs
services.AddAuthentication(sharedopt => sharedopt.DefaultScheme = JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer("AzureAd", options =>
{
options.Audience = Configuration.GetValue<string>("AzureAd:Audience");
options.Authority = Configuration.GetValue<string>("AzureAd:Instance")
+ Configuration.GetValue<string>("AzureAd:TenantId");
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters()
{
ValidIssuer = Configuration.GetValue<string>("AzureAd:Issuer"),
ValidAudience = Configuration.GetValue<string>("AzureAd:Audience")
};
});
I am expecting my Client App (Angular) will attach Authorization header in its requests and thus it will get access to API
But when I execute the Web API and trying to open any API with Authorize, it triggers this error
InvalidOperationException: No authenticationScheme was specified, and
there was no DefaultChallengeScheme found. The default schemes can be
set using either AddAuthentication(string defaultScheme) or
AddAuthentication(Action configureOptions).
I already specified JWTBearerDefaults.AuthenticationScheme. Still why its not accepting?
Please remove the first "AzureAd" parameter from AddJwtBearer call.
TLDR: When you call AddAuthentication you set the default scheme to JwtBearerDefaults.AuthenticationScheme which is string "Bearer".
This tells the authentication middleware to authenticate all requests (unless specified otherwise e.g. via Authorize attribute with schemes) to use a set of handlers and configurations organized by the shceme name "Bearer".
However you didn't register that scheme. Your call to AddJwtBearer registers a scheme named "AzureAd" instead of "Bearer".
Authentication middleware cannot find the matching scheme and hence the error.
If you don't specify the "AzureAd" parameter, below version of AddJwtBearer is invoked:
builder.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, configureOptions);
As we can see, it registers the JwtBearer authentication with scheme "Bearer" matching your default scheme.
You might have missed to register
services.ConfigureApiAuthentication(Configuration); in the startup class
Make sure you don't have an [Authorize] attribute above your Controller class definition or any associated methods for which you don't want any Authentication. I copy/pasted a Controller class and didn't notice it had an [Authorize] attribute above the class definition. I removed that and the problem was resolved.

IdentityServer4 ValidIssuers

Is there any way to tell IdentityServer4's authentication system to allow multiple issuers for the tokens?
I have an application that is using Identity Server to issue bearer tokens, and as long as the front end and the back end use the same URL to get tokens from authentication works fine.
However, I now have a need to have the same site accessed through multiple CNAMEs, meaning that the client will request tokens from two different URLs.
The error that is sent to the logs is:
info: Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerMiddleware[7]
Bearer was not authenticated. Failure message: IDX10205: Issuer validation failed. Issuer: 'http://domainb.com'. Did not match: validationParameters.ValidIssuer: 'http://domaina.com' or validationParameters.ValidIssuers: 'null'.
The presence of a ValidIssuers collection seems to indicate that you can set multiple places from which the API will accept tokens, but I cannot find anything like that exposed in options exposed by UseIdentityServerAuthentication.
I am aware of the Authority option, but that only allows me to set a single valid authority.
Is there are any way of setting multiple valid issuers, or setting it to use something other than the hostname as the issuer id?
UPDATE
My identity server configuration on the server side looks like this:
services.AddIdentityServer(options => {
options.IssuerUri = "http://authserver"; })
.AddAspNetIdentity<ApplicationUser>();
this is from the auth server side of things.
On the client API, the UseIdentityServerAuthentication call looks like this:
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions()
{
Authority = AppSettingsConfigurationRoot["Authentication:AuthorityEndpoint"],
RequireHttpsMetadata = false,
ApiName = "rqapi",
AutomaticAuthenticate = true,
ClaimsIssuer = "http://localhost:5001"
});
The address in the {{AppSettingsConfigurationROot["Authentication:AuthorityEndpoint"] is usually set at the public DNS name of the server so that the token issuer as seen by AngularJS matches the URL of the IdentityServer from the point of view of the C# API.
As Original Poster wrote in a comment, the (now, 2020, deprecated) IdentityServer4.AccessTokenValidation package doesn't expose the right options. To read more about the recent deprecation check this blogpost, but if you still are using it, here's how I solved this issue.
The AddIdentityServerAuthentication(...) extension method is a wrapper (the code is super readable!) to combine two authentication schemes:
JwtBearer
OAuth2Introspection
It uses its own configuration class, and simply doesn't expose all the JwtBearer options (possibly just an omission, possibly because some options are not valid for both schemes.
If -like me- you only need JwtBearer you might get away with simply using just that, and using the ValidIssuers array. So:
services.AddAuthentication("Bearer")
.AddJwtBearer(options =>
{
options.Authority = "https://example.org";
options.Audience = "foo-api"; // options.ApiName in the IDS4 variant
options.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuers = new[]
{
"https://example.org", // first issuer
"https://example.com", // some other issuer
},
NameClaimType = "name", // To mimick IDS4's variant
RoleClaimType = "role", // To mimick IDS4's variant
};
});
As far as I understand, this will use example.org as the Authority and get the openid-configuration and so forth from that domain. But any JWT token offered to this API would be accepted as long as one of the ValidIssuers is the iss (issuer claim) in the token.

swagger oauth security definition

On my current webapi project I have set a swagger oauth security definition with implicit flow and authorize url https://login.microsoftonline.com/ + tenant Id. The scopes are the same as in the github exapmle for the Swashbuckle.AspNetCore nuget , this is the link https://github.com/domaindrivendev/Swashbuckle.AspNetCore. But when i try to authenticate on swagger online editor, this one https://editor.swagger.io/, I can't get the token back and get a 404 exception. What do I need to set in my azure portal registered app to return a token back to the online swagger editor ?
According to your description, I created my .Net Core 2.0 Web API application and created the AAD app on Azure Portal. The configuration under ConfigureServices would look like this:
services.AddAuthentication(options =>
{
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
// Configure the app to use Jwt Bearer Authentication
.AddJwtBearer(jwtOptions =>
{
jwtOptions.Authority = string.Format(CultureInfo.InvariantCulture, "https://sts.windows.net/{0}/", Configuration["AzureAd:TenantId"]);
jwtOptions.Audience = Configuration["AzureAd:WebApiApp:ClientId"];
});
For Swagger UI, I also created a new AAD app on Azure Portal and add permissions to access the Web API app as follows:
Then, I added the following code snippet for defining the OAuth2.0 scheme as follows:
// Define the OAuth2.0 scheme that's in use (i.e. Implicit Flow)
c.AddSecurityDefinition("oauth2", new OAuth2Scheme
{
Type = "oauth2",
Flow = "implicit",
AuthorizationUrl = string.Format(CultureInfo.InvariantCulture, "https://login.microsoftonline.com/{0}/oauth2/authorize", Configuration["AzureAd:TenantId"]),
Scopes = new Dictionary<string, string>
{
{ "user_impersonation", "Access Bruce-WebAPI-NetCore" }
}
});
// Enable operation filter based on AuthorizeAttribute
c.OperationFilter<SecurityRequirementsOperationFilter>();
And use the following code for initializing the middleware to serve swagger-ui.
// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.), specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
c.ConfigureOAuth2(
Configuration["AzureAd:SwaggerApp:ClientId"],
Configuration["AzureAd:SwaggerApp:ClientSecret"],
Configuration["AzureAd:SwaggerApp:RedirectUri"], //http://localhost:30504/swagger/o2c.html
"Bruce-WebAPI-NetCore-Swagger",
additionalQueryStringParameters: new Dictionary<string, string>(){
{ "resource",Configuration["AzureAd:WebApiApp:ClientId"]}
});
});
Test:
but it still returns AADSTS50001 Resource identifier is not provided
During my processing, I encountered the similar issue. At last, I found that the resource parameter is not specified. Then, I set the additionalQueryStringParameters parameter for ConfigureOAuth2. Here is my code sample WebAPI-Swagger-NetCore, you could refer to it.
Moreover, for adding access scopes to your resource application (Web API), you could follow the Adding access scopes to your resource application section under here. Also, my SecurityRequirementsOperationFilter did not assign the scope requirements to operations based on AuthorizeAttribute provided here. You could specific the supported scopes under AddSecurityDefinition, then for your controller or action you could mark it as [Authorize(AuthenticationSchemes = "Bearer", Policy = "{scope}")]. Details, you could follow this sample.

OWIN OpenID Connect Middleware Not Replacing Current User with ClaimsPrincipal

I have an existing MVC5 application I am converting from using AspNetIdentity to utilize ThinkTecture Identity Server 3 v2. The OpenID provider is not the biggest issue I'm having, as it seems to be working great. The security token is validated and I'm handling the SecurityTokenValidated notification in a method in order to get additional user info claims and add system-specific permission claims to the claim set, similar to the code below:
OWIN Middleware
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
Authority = "https://localhost:44300/identity",
Caption = "My Application",
ClientId = "implicitclient",
ClientSecret = Convert.ToBase64String(SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes("secret"))),
RedirectUri = "http://localhost:8080/",
ResponseType = "id_token token",
Scope = "openid profile email roles",
SignInAsAuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
UseTokenLifetime = false,
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = ClaimsTransformer.GenerateUserIdentityAsync
}
});
Claims Transformer
public static async Task GenerateUserIdentityAsync(SecurityTokenValidatedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
var identityUser = new ClaimsIdentity(
notification.AuthenticationTicket.Identity.Claims,
notification.AuthenticationTicket.Identity.AuthenticationType,
ClaimTypes.Name,
ClaimTypes.Role);
var userInfoClient = new UserInfoClient(new Uri(notification.Options.Authority + "/connect/userinfo"),
notification.ProtocolMessage.AccessToken);
var userInfo = await userInfoClient.GetAsync();
identityUser.AddClaims(userInfo.Claims.Select(t => new Claim(t.Item1, t.Item2)));
var userName = identityUser.FindFirst("preferred_username").Value;
var user = MembershipProxy.GetUser(userName);
var userId = user.PersonID;
identityUser.AddClaim(new Claim(ClaimTypes.Name, userId.ToString(), ClaimValueTypes.Integer));
// Populate additional claims
notification.AuthenticationTicket = new AuthenticationTicket(identityUser, notification.AuthenticationTicket.Properties);
}
The problem is that the ClaimsIdentity assigned to the authentication ticket at the end of the transformer is never populated in the System.Web pipeline. When the request arrives to my controller during OnAuthenticationChallenge, I inspect the User property to find an anonymous WindowsPrincipal with a similar WindowsIdentity instance assigned to the Identity property, as if the web config's system.web/authentication/#mode attribute were set to None (at least I believe that's the behavior for that mode).
What might cause a failure by the middleware to set the principal for the user, or for it to be replaced during System.Web's processing with an anonymous Windows identity? I haven't been able to track this down.
EDIT: This occurs irrespective of whether the SecurityTokenValidated notification is handled and claims augmented.
EDIT 2: The cause appears to be making use of ASP.NET State Service session state server in my web.config with cookies. The configuration entry is:
<sessionState mode="StateServer" stateConnectionString="tcpip=127.0.0.1:42424" timeout="30" cookieless="UseCookies" />
This looks to be related to a reported issue in Microsoft.Owin.Host.SystemWeb #197 where cookies are not persisted from the OWIN request context into the System.Web pipeline. There are an assortment of workarounds suggested but I'd like someone to authoritatively point me to a properly vetted solution for this problem.
If you were to capture a fiddler trace, do you see the any .AspNet.Cookies.
I suspect the cookies are not being written. You can use a distributed cache and do away with cookies.
I think you need to include cookie authentication middleware because .AspNet.Cookies thing is written by that middleware. This is how you can integrate that middleware
app.UseCookieAuthentication(new CookieAuthenticationOptions());
Note: Please make sure it should be on top of openid connect middleware
for more details about CookieAuthenticationOptions please goto this link https://msdn.microsoft.com/en-us/library/microsoft.owin.security.cookies.cookieauthenticationoptions(v=vs.113).aspx.

Categories