Authenticating user for IdentityServer on the WebApi side - asp.net-web-api

I am new at this. Can someone please help me, since I am going crazy over my problem for nearly a month now :(
In short: I have identity server project, an webapi project and angular client. Client request to authenticate and gets id_token and access_token (all good), access_token send to webapi project where I have:
var idServerBearerTokenAuthOptions = new IdentityServerBearerTokenAuthenticationOptions {
Authority = "https://localhost:11066/IdentityServer/identity",
ValidationMode = ValidationMode.ValidationEndpoint,
AuthenticationType = "Bearer",
RequiredScopes = new[] { "permissions", "openid" },
DelayLoadMetadata = true
};
app.UseIdentityServerBearerTokenAuthentication(idServerBearerTokenAuthOptions);
and I have Autofac which should get me the current logedin user
builder.RegisterApiControllers(Assembly.GetExecutingAssembly()).InstancePerRequest();
builder.Register(c => new ClaimsIdentityApiUser((ClaimsIdentity)Thread.CurrentPrincipal.Identity)).As<IApiUser>().InstancePerRequest();
BUT Thread.CurrentPrincipal.Identity has nothing, and
also ClaimsPrincipal.Current.Identity has nothing. What am I missing??
p.s. Similar problem to this question Protecting webapi with IdentityServer and Autofac - can't get claims but obviously not same solution nor set up.

a) user should always be retrieved from ApiController.User (or the RequestContext)
b) token validation might fail for some reason use this resource to enable logging for the token validation middleware:
https://identityserver.github.io/Documentation/docsv2/consuming/diagnostics.html
c) are you using JWTs or reference tokens? For JWTs you can set the ValidationMode to Local

Related

Azure AD B2C & Microsoft Identity Web - Sign In with multiple policies (.net Core 3.1)

I have an application using .NET Core 3.1 MVC Web App that uses Azure AD B2C to sign in users and I've just migrated it to use Microsoft Identity Web library.
We want to have two different policies for Sign In, one for regular users (B2C_1A_SignUpOrSignIn) and one for admin users (B2C_1A_SignInAdmin).
So, in the Appsettings, we have the following format:
"AzureAdB2C": {
"Instance": "https://url.b2clogin.com/tfp/",
"ClientId": "clientId",
"CallbackPath": "/signin-oidc",
"SignedOutCallbackPath": "/signout/B2C_1A_SignUpOrSignIn",
"Domain": "url.onmicrosoft.com",
"Domain_b2cLogin": "url", // Required by the Cookie Policy
"SignUpSignInPolicyId": "B2C_1A_SignUpOrSignIn",
"SignInAdminPolicyId": "B2C_1A_SignInAdmin",
"ResetPasswordPolicyId": "B2C_1A_PasswordReset",
"EditProfilePolicyId": "",
"ClientSecret": key,
"B2cExtensionAppClientId": "key"
},
In the Startup class, I just added the following:
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(Configuration, "AzureAdB2C");
And I also overrode the "AzureController", so, for a regular user, I use the same method obtained from
here Microsoft Identity Web - Account Controller "SignIn". However, for an admin user, I changed that method to use something similar to what is provided by the PasswordReset method, as the following:
public IActionResult SignInAdmin()
{
string scheme = OpenIdConnectDefaults.AuthenticationScheme;
var redirectUrl = Url.Content("~/");
var properties = new AuthenticationProperties { RedirectUri = redirectUrl };
properties.Items[Constants.Policy] = "B2C_1A_SignInAdmin";
return Challenge(properties, scheme);
}
So, as you can see, I'm using a different policy name for this method.
Everything seems to work fine, the user is redirected to the correct login page based on the policy and the token is issued by Azure and our application accepts the Token, in our method
options.Events.OnTokenValidated = context => {}
However, soon after that, something goes wrong with the authentication and the method
options.Events.OnRemoteFailure
is called with the following exception
"{"Message contains error: 'invalid_grant', error_description: 'AADB2C90088: The provided grant has not been issued for this endpoint. Actual Value : B2C_1A_SignUpOrSignIn and Expected Value : B2C_1A_SignInAdmin ..."
So, my question is, what do I have to do to be able to use two different policies to sign in? Or is there any configuration that I should do to be able to do that?
Thank you in advanced.
I guess the B2c Middleware validates the "tfp" claim. This should usually match the SignInPolicyId. You might have to override the TokenValidation to let both policies (tfp = trustedFrameworkPolicy) be valid

Read Session Value In .Net Core Web Api [duplicate]

This question already has answers here:
Accessing Session Using ASP.NET Web API
(13 answers)
Closed 3 years ago.
In my Web api when a user login successfully I set session with some values like
HttpContext.Session.SetObject("CurrentUserID", user.Id);
HttpContext.Session.SetObject("CurrentUserRoles",user.Roles);
and just return token and some values to save in cookie
return Ok(new
{
Id = user.Id,
Username = user.UserName,
FirstName = user.FirstName,
LastName = user.LastName,
Token = tokenString,
role = user.Roles
});
But when the client hit api action which has this line
List<string> userRolesList = HttpContext.Session.GetObject<List<string>>("CurrentUserRoles");
Then always get null value even I have added session inside Startup >Configure
like
app.UseSession();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller}/{action=Index}/{id?}");
});
and ConfigureService also
services.AddSession(options =>
{
// Set a short timeout for easy testing.
options.IdleTimeout = TimeSpan.FromSeconds( 60 * 60);
options.Cookie.HttpOnly = true;
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
but does work still... Please help.
HTTP is a stateless protocol. Sessions are fake state, enabled by both a server-side and client-side component. The client-side component is a cookie: specifically a Set-Cookie response header. In order for the session to be restored on the next request, the value of this Set-Cookie response header must be sent back via the Cookie request header with each request. A web browser (the client) will do all this automatically, including persisting the cookie locally. However, a thin client like HttpClient, Postman, etc. will not. You would need to independently persist the cookie from the response header and then attach it to each request via the Cookie header in order to maintain the session between requests.
That said, this is a major reason why APIs typically do not, and honestly should not make use of sessions. It's simply a pattern that doesn't make much sense in an API context, and only adds a potential point of failure, since clients must pay attention to the cookie headers, and take manual actions to handle the cookies.

How to do Role-based Web API Authorization using Identity Server 4 (JWT)

This is all new to me and I'm still trying to wrap my head around it. I've got an IDP (Identity Server 4) set up, and I was able to configure a client to authenticate to it (Angular 6 App), and further more to authenticate to an API (Asp.Net Core 2.0). It all seems to work fine.
Here's the client definition in the IDP:
new Client
{
ClientId = "ZooClient",
ClientName = "Zoo Client",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RequireConsent = true,
RedirectUris = { "http://localhost:4200/home" },
PostLogoutRedirectUris = { "http://localhost:4200/home" },
AllowedCorsOrigins = { "http://localhost:4200" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
IdentityServerConstants.StandardScopes.Phone,
IdentityServerConstants.StandardScopes.Address,
"roles",
"ZooWebAPI"
}
}
I'm requesting the following scopes in the client:
'openid profile email roles ZooWebAPI'
The WebAPI is set up as such:
public void ConfigureServices(IServiceCollection services)
{
services
.AddMvcCore()
.AddJsonFormatters()
.AddAuthorization();
services.AddCors();
services.AddDistributedMemoryCache();
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = "https://localhost:44317";
options.RequireHttpsMetadata = false;
options.ApiName = "ZooWebAPI";
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseCors(policy =>
{
policy.WithOrigins("http://localhost:4200");
policy.AllowAnyHeader();
policy.AllowAnyMethod();
policy.AllowCredentials();
policy.WithExposedHeaders("WWW-Authenticate");
});
app.UseAuthentication();
app.UseMvc();
}
By using [Authorize] I was successfully able to secure the API:
[Route("api/[controller]")]
[Authorize]
public class ValuesController : Controller
{
// GET api/values
[HttpGet]
public ActionResult Get()
{
return new JsonResult(User.Claims.Select(
c => new { c.Type, c.Value }));
}
}
Everything works fine, if client is not authenticated, browser goes to IDP, requires authentication, redirects back with access token, access token is then used for API calls that are successfully made.
If I look at the Claims in the User object, I can see some information, but I don't have any user information. I can see the scopes, and etc, but no roles for example. From what I read, that is to be expected, and the API should not care about what user is calling it, but how would I go by restricting API calls based on roles? Or would that be completely against specs?
The IDP has an userinfo end point that returns all the user information, and I thought that would be used in the WebAPI, but again, from some reading, it looks like the intention is for that end point to be called from the client only.
Anyway, I would like to restrict Web API calls based on the roles for a specific user. Does anyone have any suggestions, comments? Also, I would like to know what user is making the call, how would I go by doing that?
JWT example:
Thanks
From what I can learn from your information, I can tell the following.
You are logging in through an external provider: Windows Authentication.
You are defining some scopes to pass something to the token that indicates access to specific resources.
The User object you speak of, is the User class that gets filled in from the access token. Since the access token by default doesn't include user profile claims, you don't have them on the User object. This is different from using Windows Authentication directly where the username is provided on the User Principle.
You need to take additional action to provide authorization based on the user logging in.
There a couple of points where you can add authorization logic:
You could define claims on the custom scopes you define in the configuration of Identityserver. This is not desirable IMHO because it's fixed to the login method and not the user logging in.
You could use ClaimsTransformation ( see links below). This allows you to add claims to the list of claims availible at the start of your methods. This has the drawback ( for some people an positive) that those extra claims are not added to the access token itself, it's only on your back-end where the token is evaluated that these claims will be added before the request is handled by your code.
How you retrieve those claims is up to your bussiness requirements.
If you need to have the user information, you have to call the userinfo endpoint of Identityserver to know the username at least. That is what that endpoint is intended for. Based on that you can then use your own logic to determine the 'Roles' this user has.
For instance we created an separate service that can configure and return 'Roles' claims based upon the user and the scopes included in the accesstoken.
UseClaimsTransformation .NET Core
UseClaimsTransformation .NET Full framework

Running IdentityServer4 and API and a Web App from a single 'site'

We have a legacy system written in Core MVC 1, using IdentityServer4 for API access and Identity for user management. The site includes a set of API controllers as well, which a mobile application connects to for authentication and data.
We have been having general stability issues, which we have not been able to get to the bottom of. We have decided to upgrade the system to the latest version of MVC Core and in the process IdentityServer4 requires an upgrade.
The problem is that the authentication pipeline has changed dramatically between versions (Core MVC 1 - 2 and Identity 1 - 2) and we are unable to determine a configuration that works.
In short we need:
Cookie Authentication for web site access
OAuth 2 password grant flow for app access
However, despite this setup working on the legacy version, it does not seem to want to play ball on the newer setup. It seems we can have one or the other, but not both. There doesn't appear to be any example projects available anywhere that demonstrate such a setup.
I understand this setup is not ideal in that these systems should be split out, and I am going to be making a recommendation as such. I have seen hints of routing api requests through a pipeline setup for Bearer authentication using MapFrom but haven't managed to determine a working setup.
UPDATE: Startup.cs
services.AddIdentity<ApplicationUser, IdentityRole>(o =>
{
o.Password.RequireDigit = false;
o.Password.RequireLowercase = false;
o.Password.RequireUppercase = false;
o.Password.RequireNonAlphanumeric = false;
o.Password.RequiredLength = 6;
})
.AddEntityFrameworkStores<ApplicationDbContext>().AddDefaultTokenProviders();
var AuthServerConfig = new IdentityServerConfig(Configuration.GetSection("IdentityServer"));
var IdentityCert = AuthServerConfig.GetCerttificate();
var IdentityConfig = services.AddIdentityServer()
.AddInMemoryIdentityResources(AuthServerConfig.GetIdentityResources())
.AddInMemoryApiResources(AuthServerConfig.GetApiResources())
.AddInMemoryClients(AuthServerConfig.GetClients())
.AddAspNetIdentity<ApplicationUser>();
//to secure the API
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = AuthServerConfig.Settings.RootUrl;
options.RequireHttpsMetadata = false;
options.ApiName = AuthServerConfig.Settings.Scope;
});
And in the Configure Method we have:
app.UseIdentityServer();
app.UseAuthentication();
The stage we are at now is that IdentityServer seems to be operational in that a token can be requested. You can call into an API endpoint and gain access so long as that endpoint had the following attribute:
[Authorize(AuthenticationSchemes = "Bearer")]
However, we want the API to be authenticated using both Identity Cookies as well as Bearer tokens, as there is a swagger UI for querying the API when logged in.
Using just the [Authorize] attribute will allow it to be accessed via cookies, but not access tokens through Postman (401)
Not sure if this is solved but what i personally done to enable API to be authenticated via cookie and bearer token is implement both schemes:
e.g.
services.AddAuthentication()
.AddCookie(options => {
options.LoginPath = "/Account/Unauthorized/";
options.AccessDeniedPath = "/Account/Forbidden/";
})
.AddJwtBearer(options => {
options.Audience = "http://localhost:5001/";
options.Authority = "http://localhost:5000/";
});
In the api controller i use [Authorize(AuthenticationSchemes = AuthSchemes)]
[Authorize(AuthenticationSchemes = AuthSchemes)]
public class TodoController: Controller
More details on different schemes and usages of them you can have a look here:
https://learn.microsoft.com/en-us/aspnet/core/security/authorization/limitingidentitybyscheme?tabs=aspnetcore2x
Hope that helps.

IdentityServer 3: How to validate the ClaimsIdentity in ASP.NET WebAPI

I use the IdentityServer 3 with the ResourceOwner Flow.
It seems to work. I can see in fiddler the token is accessed and validated by my WebAPI.
app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = "http://server/IdentityServer/",
ValidationMode = ValidationMode.ValidationEndpoint,
RequiredScopes = new[] { "MyFramework" },
SigningCertificate = Certificate.Get(),
DelayLoadMetadata = true
});
If I configure:
ValidationMode = ValidationMode.ValidationEndpoint
There is an extra request.
With ValidationMode.Local there is not. (This is good because IdentityServer is embedded in the WebAPI).
But if I use ValidationMode.Local, there are TWO requests to the IdentityServer.
http://server/IdentityServer/.well-known/openid-configuration
http://server/IdentityServer/.well-known/jwks
Why is this reccessary? The IdentityServer is embedded and this information is accessible in-memory. Even if the setting is "ValidationMode.ValidationEndpoint" this two calls do not appear.
But back to my primary question:
How can I validate the ClaimsIdentity in my WebAPI?
I have an "AuthenticationHandler" (ASP.NET-DelegatingHandler) that handles the validation for my old basic-authentication.
I figured out I can use the HttpContext.Current.User to access the IPrincipal.
Now I check it is of the type "ClaimsIdentity" and the Identity.IsAuthenticated==true.
Is this a correct approach?
And how can I check the User-ID?
On the server-side the ID of the user is put in the "sub"-claim (aka 'Subject').
Do I have to access this specific property to check which user is logged in?
I could access it like this:
ClaimsIdentity.Claims.FirstOrDefault(c => c.Type.Equals("sub"));
BUT only with the setting "ValidationMode.ValidationEndpoint".
Then the AuthenticationType of the IPrincipal is "Bearer" and the 'sub'-claim is present.
With the setting "ValidationMode.Local" the AuthenticationType is "JWT" and the claim is not present.
Why does the ValidationMode changes the IPrincipal? Do I have to check this for different cases?

Resources