Does knowing the Identity ID compromise security? - c#

I am looking at a Unit Test project, which contains the following code:
public const string IDENTITY_ID = "9e326389-1ae6-4672-9dc4-7868ab7b7a09";
private readonly RequestDelegate _next;
public AutoAuthorizeMiddleware(RequestDelegate rd)
{
_next = rd;
}
public async Task Invoke(HttpContext httpContext)
{
var identity = new ClaimsIdentity("cookies");
identity.AddClaim(new Claim("sub", IDENTITY_ID));
identity.AddClaim(new Claim("unique_name", IDENTITY_ID));
httpContext.User.AddIdentity(identity);
await _next.Invoke(httpContext);
}
Please note that I have generated a random Guid for the question.
It appears that theunit test invokes the web api (inside the Invoke method) and is able to access a method, which has an Authorise attribute.
Does forumating an identity like this mean the user is authorised?
I am trying to learn more about how Identity Server works and hence the reason for the question.

Related

How to prevent ClaimsTransformerService from running multiple times per user login?

I'm using windows authentication and IClaimsTransformation in my Blazor ServerApp (.NET 6.0)
I don't know why the ClaimsTransformerService runs 4 times per user login or per page refresh. Is there any way to prevent this?
The following code basically tries to check if a username is available in a database and if it is, record the login event in another table.
namespace MyServerApp.Services
{
public class ClaimsTransformerService : IClaimsTransformation
{
private IUserService _userService;
private ILoginRecordService _loginRecordService;
public ClaimsTransformerService(IUserService userService, ILoginRecordService loginRecordService)
{
_userService = userService;
_loginRecordService = loginRecordService;
}
public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
var user = await _userService.GetUser(principal.Identity.Name.ToLower());
((ClaimsIdentity)principal.Identity).AddClaim(new Claim(ClaimTypes.Role, "MyUser"));
return principal;
}
}
}
I've read this thread and it didn't help.
The Microsoft documentation states that the TransformAsync() method may get called multiple times. From the docs:
The IClaimsTransformation interface can be used to add extra claims to the ClaimsPrincipal class. The interface requires a single method TransformAsync. This method might get called multiple times. Only add a new claim if it does not already exist in the ClaimsPrincipal. A ClaimsIdentity is created to add the new claims and this can be added to the ClaimsPrincipal.
So you should write your AsyncMethod defensively and only add claims if they do not already exist:
public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
if (!principal.HasClaim(claim => claim.Type == ClaimTypes.Role && claim.Value == "MyUser"))
{
var user = await _userService.GetUser(principal.Identity.Name.ToLower());
((ClaimsIdentity)principal.Identity).AddClaim(new Claim(ClaimTypes.Role, "MyUser"));
}
return principal;
}

Passing HttpContext in a ActionBlock

I am doing AzureAD implict flow for authenticating on a ASP.NET Core (2.2) api. For a specific reference to a library the application is in .net 4.8 but the api is hosted inside asp.net core.
The API makes a service to service call using the TPL dataflow blocks. The access token received when the UI(react) makes the request has got the roles and scopes and is stored inside HttpContext for downstream api calls (OnBehalfOf flow).
The HttpContextAccessor.HttpContext is null inside the pipeline. Outside of the pipeline I have access to the HttpContext. It looks to be really ugly to pass the context into every method call as a parameter.
Is there any other way to do this? I know I can use Client Credential flow, where I could configure the AD and use daemon app and get a token. But if I am stuck doing an OnBehalfOf flow, what could be the solution.
public class AuthenticationHandler : AuthenticationHandler<JwtBearerOptions>
{
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (!Request.Headers.ContainsKey(HeaderNames.Authorization))
{
return Task.FromResult(AuthenticateResult.Fail("Header Not Found."));
}
var requestHeader = Request.Headers["Authorization"];
var token = requestHeader.FirstOrDefault()?.Split(' ').Last();
Request.HttpContext.Items.Add("tokenfordownstream", token); // retrieve from context for api to api calls
return Task.FromResult(AuthenticateResult.Success(ticket));
}
}
Microsoft.Identity.Web could not be installed because of a number of package downgrade. I think it is conflicting with binaries in Microsoft.Identity.Client
using Microsoft.Identity.Client;
public class RiskPipelineFactory : IRiskPipelineFactory
{
private readonly IHttpContextAccessor _httpContextAccessor;
public RiskPipelineFactory(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor; //httpContext is NOT null
}
public IRiskPipeline Create()
{
var preLoaderAction = new ActionBlock<(pricingExercise, risks)>(LoadStep, loadOptions);
return new RiskSlicePipeline(preLoaderAction);
//I don't want to pass the httpContext here, because there are also other blocks that
//gets stitched togther and many method calls, so it will be quite messy
private async Task LoadStep((PricingExercise pricingExercise, Risks risks) input)
{
//HttpContext is null here, if _httpContextAccessor is not passed as a param
//do the other api call here for the sake of simplicity, but retrieving the access token will fail
}
}
}

IdentityServer4 GetProfileDataAsync is not called for Identity Token, only Access Token

Using IdentityServer 4 (4.1.2), I've added a class implementing the IProfileService interface. The method GetProfileDataAsync is supposed to be called several times (for each token), but my method is only called for the Access Token (ClaimsProviderAccessToken).
public class LocalUserProfileService : IProfileService
{
private readonly IIdentityProviderUserService _identityProviderUserService;
public LocalUserProfileService(IIdentityProviderUserService identityProviderUserService)
{
_identityProviderUserService = identityProviderUserService ??
throw new ArgumentNullException(nameof(identityProviderUserService));
}
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var subjectId = context.Subject.GetSubjectId();
var claims = (await _identityProviderUserService.GetUserClaimsBySubjectAsync(subjectId)).ToList();
Debug.WriteLine($"Adding claims to {context.Caller}");
context.IssuedClaims.AddRange(claims);
}
public async Task IsActiveAsync(IsActiveContext context)
{
var subjectId = context.Subject.GetSubjectId();
context.IsActive = await _identityProviderUserService.IsUserActiveAsync(subjectId);
}
}
I could manage to only use my access token to get the custom claims, but I would like to know why the code is not called for the identity_token as I prefer to have the claims in both tokens.
Have you set the AlwaysIncludeUserClaimsInIdToken flag to true? Otherwise the claims for the ID-token will be provided through the UserInfo endpoint instead.
A common way to reduce the size of the ID-token is to not include all the claims in the ID-token. Large tokens will also result in large session cookies. By default, the tokens are stored inside the session cookie.

Get reference to requested Controller and Action in custom middleware in ASP.NET Core

I'm developing a custom middleware for authenticating clients that invokes an API.
I use an attribute to define if an Action requires authentication, but I can't figure out how to get a reference to the requested Controller and Action inside the Invoke method.
Below is my code so far
AuthenticateClient.cs:
public class AuthenticateClient
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
private readonly GenericUnitOfWork _worker;
public AuthenticateClient(RequestDelegate next, ApiDbContext db, IHttpContextAccessor httpContext, IHostingEnvironment env, ILoggerFactory loggerFactory, IOptions<Utility.LCLog.Settings> settings)
{
_next = next;
_logger = loggerFactory.CreateLogger(settings.Value.ApplicationName);
_worker = new GenericUnitOfWork(new AppHelper(httpContext, db, env));
}
public async Task Invoke(HttpContext context)
{
if (!context.Request.Headers.Keys.Contains("ClientAuth"))
{
_logger.LogWarning("ClientAuth missing in request", new string[] { "Host: " + context.Request.Host, "IP: " + context.Request.HttpContext.Connection.RemoteIpAddress });
context.Response.StatusCode = 400;
await context.Response.WriteAsync("ClientAuth missing from request header values");
return;
}
else
{
string[] tmp = context.Request.Headers["ClientAuth"].ToString().Split("/");
if (tmp.Length != 2)
{
context.Response.StatusCode = 400;
await context.Response.WriteAsync("The format of the ClientAuth value is wrong");
return;
}
else
{
Client client;
string key, pass;
key = tmp[0];
pass = tmp[1];
client = await _worker.GetRepo<Client>().SingleOrDefault(clnt => clnt.Active && clnt.Key.Equals(key) && clnt.Password.Equals(pass));
if (client == null)
{
_logger.LogWarning("Client authentication failed", new string[] { "Key: " + key, "Password: " + pass, "Host: " + context.Request.Host, "IP: " + context.Request.HttpContext.Connection.RemoteIpAddress });
context.Response.StatusCode = 401;
await context.Response.WriteAsync("Authentication failed");
return;
}
}
}
await _next.Invoke(context);
}
}
ClientAuthenticationAttribute.cs:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class ClientAuthenticationAttribute : Attribute
{
private readonly bool _authRequired;
public ClientAuthenticationAttribute(bool authRequired = true)
{
_authRequired = authRequired;
}
public bool AuthRequired { get { return _authRequired; } }
}
I'd recommend you to split your logic for authentication and authorization and keep them in different places.
To recap from here:
Authentication is the process of verifying who you are.
Authorization is the process of verifying that, given that we know who you are, you have access to the specific resource.
What you're currently trying to do, is to both authenticate and authorize your user in the middleware component. Although you could probably get it to work by moving all such logic into filters which you register with the api framework (be it ASP.NET Core MVC, Web API 2 or something else), that would mean that none of your other middleware components have access to the user data (which, I'm guessing, is one of the reasons you chose to implement it in a middleware in the first place).
Given your new knowledge of the separation of authentication and authorization, a possible solution would be to do the following:
Middleware for authentication only
In your middleware, concern yourself only with authentication, and leave authorization up to components later in the pipeline. In practice, this means that your middleware should do the following:
Look for user tokens, cookies or whatever you use for the users to authenticate their request
If not present, treat the request as anonymous, and call the next pipeline component without attaching a user to the request context.
If a valid token is present, resolve the user data from it (e.g. parse the user's claims from a JWT, look up roles in a database, etc...) and store it on the request context. I've found it useful both to create an IPrincipal and set context.Request.User to it, as well as adding information to the context dictionary directly.
With the user registered in the request context, call the next pipeline component.
Authorization assuming an authenticated user
You can now re-write your authorization logic to assume that there's already an authenticated user registered on the request context.
In an ASP.NET Web API 2 application, you'd implement a custom filter attribute inheriting from AuthorizationFilterAttribute, to make sure it runs first of the filters. In my current application, for example, we have the following attribute to authorize that a user has a specific claim. Note that it doesn't do any work to figure out who the user is; if a user is not attached to the context, the response is simply Unauthorized. You could be more sophisticated here, and treat anonymous requests differently from authenticated requests for users who lack access, and, for example, redirect anonymous requests to the login form, while redirecting users lacking access to an error page stating as much.
[AttributeUsage(validOn: AttributeTargets.Method)]
public class AuthorizeClaimsFilterAttribute : AuthorizationFilterAttribute
{
public AuthorizeClaimsFilterAttribute(string claimType, string claimValue)
{
ClaimType = claimType;
ClaimValue = claimValue;
}
public string ClaimType { get; }
public string ClaimValue { get; }
public override void OnAuthorization(HttpActionContext actionContext)
{
if (!(actionContext.RequestContext.Principal is ClaimsPrincipal principal)
|| !principal.HasClaim(x => x.Type == ClaimType && x.Value == ClaimValue))
{
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
}
}
}
To use it, we just decorate the action method with it:
[AuthorizeClaimsFilter("urn:ourapp:claims:admin", true)]
public IHttpActionResults OnlyAdminsCanAccess() { /* ... */ }

ASP.NET Core Middleware Passing Parameters to Controllers

I am using ASP.NET Core Web API, where I have Multiple independent web api projects. Before executing any of the controllers' actions, I have to check if the the logged in user is already impersonating other user (which i can get from DB) and can pass the impersonated user Id to the actions.
Since this is a piece of code that gonna be reused, I thought I can use a middleware so:
I can get the initial user login from request header
Get the impesonated User Id if any
Inject that ID in the request pipeline to make it available to the api being called
public class GetImpersonatorMiddleware
{
private readonly RequestDelegate _next;
private IImpersonatorRepo _repo { get; set; }
public GetImpersonatorMiddleware(RequestDelegate next, IImpersonatorRepo imperRepo)
{
_next = next;
_repo = imperRepo;
}
public async Task Invoke(HttpContext context)
{
//get user id from identity Token
var userId = 1;
int impersonatedUserID = _repo.GetImpesonator(userId);
//how to pass the impersonatedUserID so it can be picked up from controllers
if (impersonatedUserID > 0 )
context.Request.Headers.Add("impers_id", impersonatedUserID.ToString());
await _next.Invoke(context);
}
}
I found this Question, but that didn't address what I am looking for.
How can I pass a parameter and make it available in the request pipeline? Is it Ok to pass it in the header or there is more elegant way to do this?
You can use HttpContext.Items to pass arbitrary values inside the pipeline:
context.Items["some"] = "value";
A better solution would be to use a scoped service. Take a look at this: Per-request middleware dependencies
Your code should look like:
public class MyMiddleware
{
private readonly RequestDelegate _next;
public MyMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext, IImpersonatorRepo imperRepo)
{
imperRepo.MyProperty = 1000;
await _next(httpContext);
}
}
And then register your ImpersonatorRepo as:
services.AddScoped<IImpersonatorRepo, ImpersonatorRepo>()

Categories

Resources