AddJwtBearer in multi-tenant application - asp.net-core

I need to implement multi-tenant REST API on asp.net core and use the Jwt Web token for authentication.
Asp.net core docs suggest using the following code in Startup.cs ConfigureServices method:
services.AddAuthentication().AddJwtBearer("Bearer", options =>
{
options.Audience = "MyAudience";
options.Authority = "https://myauhorityserver.com";
}
The issue is that my REST API application is multi-tenant. The tenant is discovered from the URL, e.g.
https://apple.myapi.com,
https://samsung.myapi.com,
https://google.myapi.com
So each of such URLs will eventually point to the same IP, but based on the first word in the URL the app discovers tenant on using the appropriate DB connection.
Each such tenant has its own Authority URL. We use Keycloak as an identity management server, so each tenant on it has its own REALM.
So the Authority URL per tenant is something like that:
https://mykeycloack.com/auth/realms/11111111,
https://mykeycloack.com/auth/realms/22222222,
https://mykeycloack.com/auth/realms/33333333
The API application should be able to add and remove tenants dynamically, without restarting the application so, setting all the tenants in the application startup is not a good idea.
I was trying to add more schemas with more calls to AddJwtBearer, however, all the calls go to the schema "Bearer", according to options.Events.OnAuthenticationFailed event. It's not clear how to make other schemas to handle calls with Bearer token in HTTP header. Even though if it's somehow possible with a help of custom middle-ware, as I mention before providing tenant-specific configuration for Bearer token authentication in the app startup is not a solution as new tenants need to be added dynamically.
Additional info:
According to the fiddler, the Authority URL is finally combined with
/.well-known/openid-configuration
and called when the first request comes to the API endpoint marked with
[Authorize]
If the to configuration fails, the API call fails too. If the call to configuration is successful, the application is not calling it again on the next API requests.

I have found a solution and made it a Nuget Package for reusability you can take a look.
Pretty much I replace the JwtBearerTokenHandler by a DynamicBearerTokenHandler, it gives the flexibility, to resolve OpenIdConnectOptions at runtime.
I've also made another package that cares care of that for you, the only thing you need is to resolve the authority for the ongoing request, you receive the HttpContext.
Here is the package:
https://github.com/PoweredSoft/DynamicJwtBearer
How to use it.
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpContextAccessor();
services.AddMemoryCache();
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddDynamicJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.TokenValidationParameters.ValidateAudience = false;
})
.AddDynamicAuthorityJwtBearerResolver<ResolveAuthorityService>();
services.AddControllers();
}
}
The service
internal class ResolveAuthorityService : IDynamicJwtBearerAuthorityResolver
{
private readonly IConfiguration configuration;
public ResolveAuthorityService(IConfiguration configuration)
{
this.configuration = configuration;
}
public TimeSpan ExpirationOfConfiguration => TimeSpan.FromHours(1);
public Task<string> ResolveAuthority(HttpContext httpContext)
{
var realm = httpContext.Request.Headers["X-Tenant"].FirstOrDefault() ?? configuration["KeyCloak:MasterRealm"];
var authority = $"{configuration["KeyCloak:Endpoint"]}/realms/{realm}";
return Task.FromResult(authority);
}
}
Whats different from the original one
// before
if (_configuration == null && Options.ConfigurationManager != null)
{
_configuration = await
Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted);
}
// after
var currentConfiguration = await this.dynamicJwtBearerHanderConfigurationResolver.ResolveCurrentOpenIdConfiguration(Context);
How its replaced
public static AuthenticationBuilder AddDynamicJwtBearer(this AuthenticationBuilder builder, string authenticationScheme, Action<JwtBearerOptions> action = null)
{
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<JwtBearerOptions>, JwtBearerPostConfigureOptions>());
if (action != null)
return builder.AddScheme<JwtBearerOptions, DynamicJwtBearerHandler>(authenticationScheme, null, action);
return builder.AddScheme<JwtBearerOptions, DynamicJwtBearerHandler>(authenticationScheme, null, _ => { });
}
Source of the Handler
public class DynamicJwtBearerHandler : JwtBearerHandler
{
private readonly IDynamicJwtBearerHanderConfigurationResolver dynamicJwtBearerHanderConfigurationResolver;
public DynamicJwtBearerHandler(IOptionsMonitor<JwtBearerOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, IDynamicJwtBearerHanderConfigurationResolver dynamicJwtBearerHanderConfigurationResolver) : base(options, logger, encoder, clock)
{
this.dynamicJwtBearerHanderConfigurationResolver = dynamicJwtBearerHanderConfigurationResolver;
}
/// <summary>
/// Searches the 'Authorization' header for a 'Bearer' token. If the 'Bearer' token is found, it is validated using <see cref="TokenValidationParameters"/> set in the options.
/// </summary>
/// <returns></returns>
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
string token = null;
try
{
// Give application opportunity to find from a different location, adjust, or reject token
var messageReceivedContext = new MessageReceivedContext(Context, Scheme, Options);
// event can set the token
await Events.MessageReceived(messageReceivedContext);
if (messageReceivedContext.Result != null)
{
return messageReceivedContext.Result;
}
// If application retrieved token from somewhere else, use that.
token = messageReceivedContext.Token;
if (string.IsNullOrEmpty(token))
{
string authorization = Request.Headers[HeaderNames.Authorization];
// If no authorization header found, nothing to process further
if (string.IsNullOrEmpty(authorization))
{
return AuthenticateResult.NoResult();
}
if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
{
token = authorization.Substring("Bearer ".Length).Trim();
}
// If no token found, no further work possible
if (string.IsNullOrEmpty(token))
{
return AuthenticateResult.NoResult();
}
}
var currentConfiguration = await this.dynamicJwtBearerHanderConfigurationResolver.ResolveCurrentOpenIdConfiguration(Context);
var validationParameters = Options.TokenValidationParameters.Clone();
if (currentConfiguration != null)
{
var issuers = new[] { currentConfiguration.Issuer };
validationParameters.ValidIssuers = validationParameters.ValidIssuers?.Concat(issuers) ?? issuers;
validationParameters.IssuerSigningKeys = validationParameters.IssuerSigningKeys?.Concat(currentConfiguration.SigningKeys)
?? currentConfiguration.SigningKeys;
}
List<Exception> validationFailures = null;
SecurityToken validatedToken;
foreach (var validator in Options.SecurityTokenValidators)
{
if (validator.CanReadToken(token))
{
ClaimsPrincipal principal;
try
{
principal = validator.ValidateToken(token, validationParameters, out validatedToken);
}
catch (Exception ex)
{
Logger.TokenValidationFailed(ex);
// Refresh the configuration for exceptions that may be caused by key rollovers. The user can also request a refresh in the event.
if (Options.RefreshOnIssuerKeyNotFound && Options.ConfigurationManager != null
&& ex is SecurityTokenSignatureKeyNotFoundException)
{
Options.ConfigurationManager.RequestRefresh();
}
if (validationFailures == null)
{
validationFailures = new List<Exception>(1);
}
validationFailures.Add(ex);
continue;
}
Logger.TokenValidationSucceeded();
var tokenValidatedContext = new TokenValidatedContext(Context, Scheme, Options)
{
Principal = principal,
SecurityToken = validatedToken
};
await Events.TokenValidated(tokenValidatedContext);
if (tokenValidatedContext.Result != null)
{
return tokenValidatedContext.Result;
}
if (Options.SaveToken)
{
tokenValidatedContext.Properties.StoreTokens(new[]
{
new AuthenticationToken { Name = "access_token", Value = token }
});
}
tokenValidatedContext.Success();
return tokenValidatedContext.Result;
}
}
if (validationFailures != null)
{
var authenticationFailedContext = new AuthenticationFailedContext(Context, Scheme, Options)
{
Exception = (validationFailures.Count == 1) ? validationFailures[0] : new AggregateException(validationFailures)
};
await Events.AuthenticationFailed(authenticationFailedContext);
if (authenticationFailedContext.Result != null)
{
return authenticationFailedContext.Result;
}
return AuthenticateResult.Fail(authenticationFailedContext.Exception);
}
return AuthenticateResult.Fail("No SecurityTokenValidator available for token: " + token ?? "[null]");
}
catch (Exception ex)
{
Logger.ErrorProcessingMessage(ex);
var authenticationFailedContext = new AuthenticationFailedContext(Context, Scheme, Options)
{
Exception = ex
};
await Events.AuthenticationFailed(authenticationFailedContext);
if (authenticationFailedContext.Result != null)
{
return authenticationFailedContext.Result;
}
throw;
}
}
}

If you're still trying to figure this out you can try Finbuckle.
https://www.finbuckle.com/MultiTenant/Docs/Authentication

Related

Jwt Token audience validation failed when deployed

I am using ASPNET core 5.0 for both front-end and back-end API. It worked perfectly on the local machine, but I deploy both the front-end and API application it always gives me audience validation failure. here is the code I am using.
"Jwt": {
"Issuer": "RestaurantPortal",
"Audience": "http://mansoor0786-001-site1.ctempurl.com/",
"Key": "ASAscethtCVdAQAAAAEAACcQAAAAEDhnGasldjaslkjdleEnGunGWR4Z79AvrtgIjYXhcWZx4OqpvWbsdsdsdSafcV/ZuPw25KbhKWhg1SIXXU2Ad7maaGAk******"
},
I have kept this in appSettings of both front end and API applications. Here is API startup code
services.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser().Build();
});
services.AddAuthentication()
.AddCookie()
.AddJwtBearer(config =>
{
config.TokenValidationParameters = new TokenValidationParameters()
{
ValidIssuer = JwtConfiguration.JWTIssuer,
ValidAudience = JwtConfiguration.JWTAudience,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(JwtConfiguration.JWTKey)),
ClockSkew = TimeSpan.Zero
};
});
Here is the validation I am doing on API end when user wants to login.
public bool ValidateToken(string token)
{
var tokenHandler = new JwtSecurityTokenHandler();
try
{
tokenHandler.ValidateToken(token, new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidIssuer = JwtConfiguration.JWTIssuer,
ValidAudience = JwtConfiguration.JWTAudience,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(JwtConfiguration.JWTKey)),
ClockSkew = TimeSpan.Zero
}, out SecurityToken validatedToken);
}
catch (Exception)
{
return false;
}
return true;
}
Locally it works fine but when deploy these both applications it gives me an error and when I try to login it doesn't allow me to login into system. Here are the URL for both API and front-end application. This where I generate token
public string GenerateAccessToken(IEnumerable<Claim> claims)
{
var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(JwtConfiguration.JWTKey));
var signinCredentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256);
var tokeOptions = new JwtSecurityToken(
issuer: JwtConfiguration.JWTIssuer,
audience: JwtConfiguration.JWTAudience,
claims: claims,
expires: DateTime.Now.AddHours(24),
signingCredentials: signinCredentials
);
var tokenString = new JwtSecurityTokenHandler().WriteToken(tokeOptions);
return tokenString;
}
In this the configuration gets information from appSettings.json
public static class JwtConfiguration
{
public static readonly string JWTIssuer = Utils._config["Jwt:Issuer"];
public static readonly string JWTAudience = Utils._config["Jwt:Audience"];
public static readonly string JWTKey = Utils._config["Jwt:Key"];
}
This is my response from when I log in the user
if (apiResponseModel != null && apiResponseModel.Data != null && apiResponseModel.Data.Status == 1)
{
var claims = new List<Claim>
{
new Claim(AuthKeys.AccessToken, apiResponseModel.Data.AccessToken),
new Claim(AuthKeys.RefreshToken, apiResponseModel.Data.RefreshToken)
};
var claimsIdentity = new ClaimsIdentity(
claims, CookieAuthenticationDefaults.AuthenticationScheme);
var authProperties = new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTime.UtcNow.AddMinutes(30),
};
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity),
authProperties);
if (apiResponseModel.Data.RoleName == UserRole.Roles.Customer.GetEnumDescription())
{
return RedirectToAction("index", "Home");
}
return RedirectToAction("index", "dashboard");
}
After that it redirected to dashboard index page where I wrote base controller and added attribute on top of basecontroller which does the following.
[ServiceFilter(typeof(JWT_Authentication))]
public class BaseController : Controller
{
public readonly IOptions<AppSettingDTO> _appSetting;
protected readonly IUserProfileInfo _userService;
public readonly IHttpContextAccessor _httpContextAccessor;
protected readonly IHttpNetClientService _apiService;
public BaseController(IOptions<AppSettingDTO> AppSetting, IHttpNetClientService HttpService, IUserProfileInfo UserInfo, IHttpContextAccessor HttpContext)
{
_appSetting = AppSetting;
_apiService = HttpService;
_userService = UserInfo;
_httpContextAccessor = HttpContext;
}
}
Here is my JWT_Authentication
public class JWT_Authentication : ActionFilterAttribute
{
private readonly IHttpContextAccessor _httpContextAccessor;
protected readonly IUserProfileInfo _userService;
public JWT_Authentication(IHttpContextAccessor HttpContext, IUserProfileInfo UserInfo)
{
_httpContextAccessor = HttpContext;
_userService = UserInfo;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
string actionName = context.RouteData.Values["Action"].ToString().ToLower();
string controllerName = context.RouteData.Values["Controller"].ToString().ToLower();
if (
controllerName != "account" && actionName != "logout")
{
string accessTokens = _userService.GetToken(_httpContextAccessor);
if (!_userService.ValidateToken(accessTokens))
{
}
else
{
return;
}
context.Result = new RedirectToRouteResult(new RouteValueDictionary(){
{ "action", "LogOut" },
{ "controller", "Account" }
});
return;
}
}
}
API
http://mansoor00786-001-site1.gtempurl.com/
Front-End
http://mansoor0786-001-site1.ctempurl.com/
I am calling login API from the front-end application which is also in asp net core 5.0 but it doesn't log me into the dashboard because of validation failure and that is because of the audience.
Well, as far as i saw it, here is some points I spot
There won't ever be an exception was throw when calling ValidateToken
Cause we put it on try catch block, so where does it throw audience validation failure ? It cannot be during deployment cause catch block doesn't have logging support anywhere, therefore, it might just be your assumption. And behavior on production state should be always redirect to Login page after logout.
The way MVC project handle Jwt Token was cumbersome
As we handmade Jwt Token and validate them ourself, such thing as validation failure with the same setting (Issuer, audience,...) should not exists. If that was fine on the client, have faith and logging those setting out from production state.
And for current approach, We can validate Jwt token and restrict them from access our resource fine, but HttpContext.User object still be null, therefore Authorization process became mostly, unusable.
Instead, how about consider to write our own Authentication scheme ?
What might be the problem here ?
public class JWT_Authentication : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
//... Some upper process
if (!_userService.ValidateToken(accessTokens))
{
// Doing something if the jwt invalid ?
}
else
{
return;
}
//... Some below process
}
}
If my block code idea was right, take a look at string accessTokens = _userService.GetToken(_httpContextAccessor);, log it out, as there might be a null here, due to you passing down a IHttpContextAccessor, which was singleton, not a HttpContext which scope for each request (localhost would be fine, cause we have only one client).

OpenIddict Roles/Policy returns 403 Forbidden

Good morning. I am having an issue with Authorize in controllers and using Roles and / or Policy set up. It is always returning a 403 forbidden, with the following in the logs:
info: OpenIddict.Server.OpenIddictServerDispatcher[0]
The request address matched a server endpoint: Token.
info: OpenIddict.Server.OpenIddictServerDispatcher[0]
The token request was successfully extracted: {
"grant_type": "password",
"username": "Administrator#MRM2Inc.com",
"password": "[redacted]"
}.
info: OpenIddict.Server.OpenIddictServerDispatcher[0]
The token request was successfully validated.
info: OpenIddict.Server.OpenIddictServerDispatcher[0]
The response was successfully returned as a JSON document: {
"access_token": "[redacted]",
"token_type": "Bearer",
"expires_in": 3600
}.
info: OpenIddict.Validation.OpenIddictValidationDispatcher[0]
The response was successfully returned as a challenge response: {
"error": "insufficient_access",
"error_description": "The user represented by the token is not allowed to perform the requested action.",
"error_uri": "https://documentation.openiddict.com/errors/ID2095"
}.
info: OpenIddict.Validation.AspNetCore.OpenIddictValidationAspNetCoreHandler[13]
AuthenticationScheme: OpenIddict.Validation.AspNetCore was forbidden.
If I remove the Roles = or Policy = from the Authorize Tag it works. I have the project setup as follows:
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddDbContext<IdentDbContext>(options =>
{
options.UseSqlServer(
Configuration.GetConnectionString("IdentityDB"));
options.UseOpenIddict();
});
// Add the Identity Services we are going to be using the Application Users and the Application Roles
services.AddIdentity<ApplicationUsers, ApplicationRoles>(config =>
{
config.SignIn.RequireConfirmedEmail = true;
config.SignIn.RequireConfirmedAccount = true;
config.User.RequireUniqueEmail = true;
config.Lockout.MaxFailedAccessAttempts = 3;
}).AddEntityFrameworkStores<IdentDbContext>()
.AddUserStore<ApplicationUserStore>()
.AddRoleStore<ApplicationRoleStore>()
.AddRoleManager<ApplicationRoleManager>()
.AddUserManager<ApplicationUserManager>()
.AddErrorDescriber<ApplicationIdentityErrorDescriber>()
.AddDefaultTokenProviders()
.AddDefaultUI();
services.AddDataLibrary();
// Configure Identity to use the same JWT claims as OpenIddict instead
// of the legacy WS-Federation claims it uses by default (ClaimTypes),
// which saves you from doing the mapping in your authorization controller.
services.Configure<IdentityOptions>(options =>
{
options.ClaimsIdentity.UserNameClaimType = Claims.Name;
options.ClaimsIdentity.UserIdClaimType = Claims.Subject;
options.ClaimsIdentity.RoleClaimType = Claims.Role;
});
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme;
});
// Add in the email
var emailConfig = Configuration.GetSection("EmailConfiguration").Get<EmailConfiguration>();
services.AddSingleton(emailConfig);
services.AddEmailLibrary();
services.AddAuthorization(option =>
{
option.AddPolicy("SiteAdmin", policy => policy.RequireClaim("Site Administrator"));
});
services.AddOpenIddict()
// Register the OpenIddict core components.
.AddCore(options =>
{
// Configure OpenIddict to use the Entity Framework Core stores and models.
// Note: call ReplaceDefaultEntities() to replace the default entities.
options.UseEntityFrameworkCore()
.UseDbContext<IdentDbContext>()
/*.ReplaceDefaultEntities<Guid>()*/;
})
// Register the OpenIddict server components.
.AddServer(options =>
{
// Enable the token endpoint. What other endpoints?
options.SetLogoutEndpointUris("/api/LogoutPost")
.SetTokenEndpointUris("/Token");
// Enable the client credentials flow. Which flow do I need?
options.AllowPasswordFlow();
options.AcceptAnonymousClients();
options.DisableAccessTokenEncryption();
// Register the signing and encryption credentials.
options.AddDevelopmentEncryptionCertificate()
.AddDevelopmentSigningCertificate();
// Register the ASP.NET Core host and configure the ASP.NET Core options.
options.UseAspNetCore()
.EnableLogoutEndpointPassthrough()
.EnableTokenEndpointPassthrough();
})
// Register the OpenIddict validation components.
.AddValidation(options =>
{
// Import the configuration from the local OpenIddict server instance.
options.UseLocalServer();
// Register the ASP.NET Core host.
options.UseAspNetCore();
});
// Register the Swagger generator, defining 1 or more Swagger documents
services.AddSwaggerGen(swagger =>
{
swagger.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
{
Name = "Authorization",
Type = SecuritySchemeType.Http,
Scheme = "Bearer",
BearerFormat = "JWT",
In = ParameterLocation.Header,
Description = "JWT Authorization header using the Bearer scheme. \r\n\r\n Enter 'Bearer'[space] and then your token in the text input below.\r\n\r\nExample: \"Bearer 12345abcdef\""
});
swagger.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
new string[] {}
}
});
swagger.OperationFilter<SwaggerDefaultValues>();
swagger.OperationFilter<AuthenticationRequirementOperationFilter>();
// Set the comments path for the Swagger JSON and UI.
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
swagger.IncludeXmlComments(xmlPath);
});
services.AddApiVersioning();
services.AddVersionedApiExplorer(options =>
{
options.GroupNameFormat = "'v'VVVV";
options.DefaultApiVersion = ApiVersion.Parse("0.6.alpha");
options.AssumeDefaultVersionWhenUnspecified = true;
});
services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IApiVersionDescriptionProvider provider)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
// Enable middleware to serve generated Swagger as a JSON endpoint.
app.UseSwagger();
// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.),
// specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c =>
{
c.DisplayOperationId();
var versionDescription = provider.ApiVersionDescriptions;
foreach (var description in provider.ApiVersionDescriptions.OrderByDescending(_ => _.ApiVersion))
{
c.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json", $"MRM2 Identity API {description.GroupName}");
}
});
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
AuthorizationController.cs
/// <summary>
/// Controls the Authorization aspects of the API
/// </summary>
[Route("api/[controller]/[action]")]
[ApiController]
[ApiVersion("0.8.alpha")]
[Produces(MediaTypeNames.Application.Json)]
public class AuthorizationController : ControllerBase
{
private readonly IConfiguration _configuration;
private readonly IdentDbContext _context;
private readonly ApplicationUserManager _userManager;
private readonly ApplicationRoleManager _roleManager;
private readonly IOpenIddictApplicationManager _applicationManager;
private readonly IOpenIddictAuthorizationManager _authorizationManager;
private readonly IOpenIddictScopeManager _scopeManager;
private readonly SignInManager<ApplicationUsers> _signInManager;
private HttpClient _client;
public AuthorizationController(IConfiguration configuration, IdentDbContext context, ApplicationUserManager userManager,
ApplicationRoleManager roleManager, IOpenIddictApplicationManager applicationManager, IOpenIddictAuthorizationManager authorizationManager,
IOpenIddictScopeManager scopeManager, SignInManager<ApplicationUsers> signInManager)
{
_configuration = configuration;
_context = context;
_userManager = userManager;
_roleManager = roleManager;
_applicationManager = applicationManager;
_authorizationManager = authorizationManager;
_scopeManager = scopeManager;
_signInManager = signInManager;
}
[HttpPost("/token"), Produces("application/json")]
public async Task<IActionResult> Exchange()
{
var request = HttpContext.GetOpenIddictServerRequest() ??
throw new InvalidOperationException("The OpenID Connect request cannot be retrieved.");
ClaimsPrincipal claimsPrincipal;
if (request.IsPasswordGrantType())
{
var user = await _userManager.FindByNameAsync(request.Username);
var roleList = await _userManager.GetRolesListAsync(user);
var databaseList = await _userManager.GetDatabasesAsync(user);
string symKey = _configuration["Jwt:Symmetrical:Key"];
string jwtSub = _configuration["Jwt:Subject"];
string issuer = _configuration["Jwt:Issuer"];
string audience = _configuration["Jwt:Audience"];
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Sub, jwtSub, issuer),
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString(), issuer),
new Claim(ClaimTypes.Name, user.UserName, issuer)
};
foreach (var role in roleList)
{
claims.Add(new Claim(ClaimTypes.Role, role.Name));
}
foreach (var database in databaseList)
{
claims.Add(new Claim(type: "DatabaseName", database));
}
var identity = new ClaimsIdentity(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
identity.AddClaim(OpenIddictConstants.Claims.Name, user.UserName, OpenIddictConstants.Destinations.AccessToken);
identity.AddClaim(OpenIddictConstants.Claims.Subject, jwtSub, OpenIddictConstants.Destinations.AccessToken);
identity.AddClaim(OpenIddictConstants.Claims.Audience, audience, OpenIddictConstants.Destinations.AccessToken);
foreach (var cl in claims)
{
identity.AddClaim(cl.Type, cl.Value);
}
claimsPrincipal = new ClaimsPrincipal(identity);
// Set the list of scopes granted to the client application.
claimsPrincipal.SetScopes(new[]
{
Scopes.OpenId,
Scopes.Email,
Scopes.Profile,
Scopes.Roles
}.Intersect(request.GetScopes()));
foreach (var claim in claimsPrincipal.Claims)
{
claim.SetDestinations(GetDestinations(claim, claimsPrincipal));
}
return SignIn(claimsPrincipal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
if (request.IsAuthorizationCodeGrantType() || request.IsRefreshTokenGrantType())
{
// Retrieve the claims principal stored in the authorization code/device code/refresh token.
var principal = (await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)).Principal;
// Retrieve the user profile corresponding to the authorization code/refresh token.
// Note: if you want to automatically invalidate the authorization code/refresh token
// when the user password/roles change, use the following line instead:
// var user = _signInManager.ValidateSecurityStampAsync(info.Principal);
var user = await _userManager.GetUserAsync(principal);
if (user == null)
{
return Forbid(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: new AuthenticationProperties(new Dictionary<string, string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The token is no longer valid."
}));
}
// Ensure the user is still allowed to sign in.
if (!await _signInManager.CanSignInAsync(user))
{
return Forbid(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: new AuthenticationProperties(new Dictionary<string, string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The user is no longer allowed to sign in."
}));
}
foreach (var claim in principal.Claims)
{
claim.SetDestinations(GetDestinations(claim, principal));
}
// Returning a SignInResult will ask OpenIddict to issue the appropriate access/identity tokens.
return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
throw new InvalidOperationException("The specified grant type is not supported.");
}
private IEnumerable<string> GetDestinations(Claim claim, ClaimsPrincipal principal)
{
// Note: by default, claims are NOT automatically included in the access and identity tokens.
// To allow OpenIddict to serialize them, you must attach them a destination, that specifies
// whether they should be included in access tokens, in identity tokens or in both.
switch (claim.Type)
{
case Claims.Name:
yield return Destinations.AccessToken;
if (principal.HasScope(Scopes.Profile))
yield return Destinations.IdentityToken;
yield break;
case Claims.Email:
yield return Destinations.AccessToken;
if (principal.HasScope(Scopes.Email))
yield return Destinations.IdentityToken;
yield break;
case Claims.Role:
yield return Destinations.AccessToken;
if (principal.HasScope(Scopes.Roles))
yield return Destinations.IdentityToken;
yield break;
// Never include the security stamp in the access and identity tokens, as it's a secret value.
case "AspNet.Identity.SecurityStamp": yield break;
default:
yield return Destinations.AccessToken;
yield break;
}
}
}
RolesController.cs
/// <summary>
/// Controls the actions for roles within the API
/// </summary>
/// <response code="401">If the user did not login correctly or
/// does not have the correct permissions</response>
[Route("api/[controller]")]
[ApiController]
[ApiVersion("0.8.alpha")]
[Produces(MediaTypeNames.Application.Json)]
[Consumes(MediaTypeNames.Application.Json)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[Authorize(AuthenticationSchemes = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme, Roles = "Site Administrator")] //If I change this to Policy = "SiteAdmin" still does not work. If I remove the Roles completely it works.
public class RolesController : ControllerBase
{
private readonly ApplicationRoleManager _roleManager;
private readonly ILogger<RolesController> _logger;
private readonly IApplicationDatabaseData _databaseData;
public RolesController(ApplicationRoleManager roleManager, ILogger<RolesController> logger, IApplicationDatabaseData databaseData)
{
_roleManager = roleManager;
_logger = logger;
_databaseData = databaseData;
}
/// <summary>
/// Gets a List of all the Roles
/// </summary>
/// <returns>A list of ApplicationRoles</returns>
/// <response code="200">Returns the list</response>
/// <response code="404">If the list is empty</response>
[HttpGet("ListRoles", Name = nameof(ListRolesAsync))]
[Authorize(AuthenticationSchemes = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme/*, Roles = "Site Administrator"*/)] //Currently commented out until Roles work.
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<IList<ApplicationRoles>>> ListRolesAsync()
{
var roles = await _roleManager.GetAllRoles();
if (!roles.Any())
{
return NotFound();
}
else
{
var output = roles;
return Ok(output);
}
}
}
What I noticed that when stepping through this in debugging except for the Claims.Name when going through GetDestinations all claims hit the default switch case. So I am not sure where in the startup or authorizationcontoller I went wrong. But I am pretty sure my issue is within there.
What have I missed to get my Roles and / or Policy to work correctly within the Controllers?
Update to the AuthorizationController allowed this to work. New section of the AuthorizationContoller for the Exchange method is as follows (still a work in progress, but now a working work in progress):
[HttpPost("/token"), Produces("application/json")]
public async Task<IActionResult> Exchange()
{
var request = HttpContext.GetOpenIddictServerRequest() ??
throw new InvalidOperationException("The OpenID Connect request cannot be retrieved.");
ClaimsPrincipal claimsPrincipal;
if (request.IsClientCredentialsGrantType())
{
var identity = new ClaimsIdentity(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
identity.AddClaim(OpenIddictConstants.Claims.Subject, request.ClientId ?? throw new InvalidOperationException());
identity.AddClaim("some-claim", "some-value", OpenIddictConstants.Destinations.AccessToken);
claimsPrincipal = new ClaimsPrincipal(identity);
claimsPrincipal.SetScopes(request.GetScopes());
}
if (request.IsPasswordGrantType())
{
var user = await _userManager.FindByNameAsync(request.Username);
var roleList = await _userManager.GetRolesListAsync(user);
var databaseList = await _userManager.GetDatabasesAsync(user);
string symKey = _configuration["Jwt:Symmetrical:Key"];
string jwtSub = _configuration["Jwt:Subject"] + " " + user.Id;
string issuer = _configuration["Jwt:Issuer"];
string audience = _configuration["Jwt:Audience"];
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Sub, jwtSub, issuer),
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString(), issuer),
new Claim(ClaimTypes.Name, user.UserName, issuer)
};
foreach (var role in roleList)
{
claims.Add(new Claim(ClaimTypes.Role, role.Name, ClaimValueTypes.String, issuer));
}
foreach (var database in databaseList)
{
claims.Add(new Claim(type: "DatabaseName", database, ClaimValueTypes.String, issuer));
}
var identity = new ClaimsIdentity(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
identity.AddClaim(OpenIddictConstants.Claims.Name, user.UserName, OpenIddictConstants.Destinations.AccessToken);
identity.AddClaim(OpenIddictConstants.Claims.Subject, jwtSub, OpenIddictConstants.Destinations.AccessToken);
identity.AddClaim(OpenIddictConstants.Claims.Audience, audience, OpenIddictConstants.Destinations.AccessToken);
foreach (var cl in claims)
{
if (cl.Type == ClaimTypes.Role)
{
identity.AddClaim(OpenIddictConstants.Claims.Role, cl.Value, OpenIddictConstants.Destinations.AccessToken, OpenIddictConstants.Destinations.IdentityToken);
}
identity.AddClaim(cl.Type, cl.Value, OpenIddictConstants.Destinations.IdentityToken);
}
claimsPrincipal = new ClaimsPrincipal(identity);
// Set the list of scopes granted to the client application.
claimsPrincipal.SetScopes(new[]
{
Scopes.OpenId,
Scopes.Email,
Scopes.Profile,
Scopes.Roles
}.Intersect(request.GetScopes()));
foreach (var claim in claimsPrincipal.Claims)
{
claim.SetDestinations(GetDestinations(claim, claimsPrincipal));
}
return SignIn(claimsPrincipal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
if (request.IsAuthorizationCodeGrantType() || request.IsRefreshTokenGrantType())
{
// Retrieve the claims principal stored in the authorization code/device code/refresh token.
var principal = (await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)).Principal;
// Retrieve the user profile corresponding to the authorization code/refresh token.
// Note: if you want to automatically invalidate the authorization code/refresh token
// when the user password/roles change, use the following line instead:
// var user = _signInManager.ValidateSecurityStampAsync(info.Principal);
var user = await _userManager.GetUserAsync(principal);
if (user == null)
{
return Forbid(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: new AuthenticationProperties(new Dictionary<string, string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The token is no longer valid."
}));
}
// Ensure the user is still allowed to sign in.
if (!await _signInManager.CanSignInAsync(user))
{
return Forbid(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: new AuthenticationProperties(new Dictionary<string, string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The user is no longer allowed to sign in."
}));
}
foreach (var claim in principal.Claims)
{
claim.SetDestinations(GetDestinations(claim, principal));
}
// Returning a SignInResult will ask OpenIddict to issue the appropriate access/identity tokens.
return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
throw new InvalidOperationException("The specified grant type is not supported.");
}

Issue with custom Authentication filter asp.net core

I'm trying to create a custom authentication filter in ASP.NET Core. Need to use it in the controller to authenticate the JWT provided to me and create a Claim Principal. However when I place the authentication tag above the controller, nothing happens and the controller is getting processed without the authentication.
The following are the steps which were done:
Added the app.UseAuthentication() in the startup.cs under
Configure(IApplicationBuilder app, IHostingEnvironment env)
{
......
......
app.UseAuthentication();
}
Created a new class file ProcessAuth in the same project, containing the AuthenticationAsync and ChallengeAsync
public class ProcessAuth : Attribute, IAuthenticationFilter
{
public bool AllowMultiple { get { return false; } }
public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
HttpRequestMessage request = context.Request;
AuthenticationHeaderValue authorization = request.Headers.Authorization;
// More code to be added for validating the JWT
}
public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
{
throw new NotImplementedException(); //sample code
}
}
Addded a reference of this new file in the controller
Placed the tag [ProcessAuth] at the top of the controller
[ProcessAuth]
[Route("api/[controller]")]
[ApiController]
Used Postman to send a JSON data, along with the Authorization Header containing a valid JWT token as "Bearer "
The code just ignores the filter and processes code in the controller and returns the result
More info:
If I add [Authorize] to the controller, Postman just returns a 401 Unauthorized error
Also checked this URL, but couldn't find the issue.
Update: I checked the answers to the similar Stack Overflow questions and followed the same but still the issue remains the same.
Nuget Packages installed:
Microsoft.AspNet.WebApi.Core and also Microsoft.AspNet.WebApi
Namespaces used:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Filters;
using System.Web.Http.Controllers;
Similar Issue - Link
How do I get this to work? Am I missing something?
According to your codes, I found you have used asp.net authentication filter in asp.net core application. This is will not work.
In asp.net core, we should use JWT bear authentication middleware to achieve your requirement.
You could create custom OnChallenge to validate the jwt token and OnTokenValidated to add the claims.
More details, you could refer to below codes:
services.AddAuthentication(auth =>
{
auth.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
auth.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(token =>
{
token.RequireHttpsMetadata = false;
token.SaveToken = true;
token.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
//Same Secret key will be used while creating the token
IssuerSigningKey = new SymmetricSecurityKey(SecretKey),
ValidateIssuer = true,
//Usually, this is your application base URL
ValidIssuer = "http://localhost:45092/",
ValidateAudience = true,
//Here, we are creating and using JWT within the same application.
//In this case, base URL is fine.
//If the JWT is created using a web service, then this would be the consumer URL.
ValidAudience = "http://localhost:45092/",
RequireExpirationTime = true,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
token.Events = new JwtBearerEvents {
OnChallenge = async ctx => {
},
OnTokenValidated = async ctx =>
{
//Get the calling app client id that came from the token produced by Azure AD
string clientId = ctx.Principal.FindFirstValue("appid");
//Get EF context
//var db = ctx.HttpContext.RequestServices.GetRequiredService<AuthorizationDbContext>();
//Check if this app can read confidential items
bool canReadConfidentialItems = await db.Applications.AnyAsync(a => a.ClientId == clientId && a.ReadConfidentialItems);
if (canReadConfidentialItems)
{
//Add claim if yes
var claims = new List<Claim>
{
new Claim("ConfidentialAccess", "true")
};
var appIdentity = new ClaimsIdentity(claims);
ctx.Principal.AddIdentity(appIdentity);
}
}
};
});
Edit:
You could create the AuthenticationHandler and AuthenticationSchemeOptions class like below and register the class in the startup.cs. Then you could use [Authorize(AuthenticationSchemes = "Test")] to set the special AuthenticationSchemes.
More details, you could refer to below codes sample:
public class ValidateHashAuthenticationSchemeOptions : AuthenticationSchemeOptions
{
}
public class ValidateHashAuthenticationHandler
: AuthenticationHandler<ValidateHashAuthenticationSchemeOptions>
{
public ValidateHashAuthenticationHandler(
IOptionsMonitor<ValidateHashAuthenticationSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock)
: base(options, logger, encoder, clock)
{
}
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
//TokenModel model;
// validation comes in here
if (!Request.Headers.ContainsKey("X-Base-Token"))
{
return Task.FromResult(AuthenticateResult.Fail("Header Not Found."));
}
var token = Request.Headers["X-Base-Token"].ToString();
try
{
// convert the input string into byte stream
using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(token)))
{
// deserialize stream into token model object
//model = Serializer.Deserialize<TokenModel>(stream);
}
}
catch (System.Exception ex)
{
Console.WriteLine("Exception Occured while Deserializing: " + ex);
return Task.FromResult(AuthenticateResult.Fail("TokenParseException"));
}
//if (model != null)
//{
// // success case AuthenticationTicket generation
// // happens from here
// // create claims array from the model
// var claims = new[] {
// new Claim(ClaimTypes.NameIdentifier, model.UserId.ToString()),
// new Claim(ClaimTypes.Email, model.EmailAddress),
// new Claim(ClaimTypes.Name, model.Name) };
// // generate claimsIdentity on the name of the class
// var claimsIdentity = new ClaimsIdentity(claims,
// nameof(ValidateHashAuthenticationHandler));
// // generate AuthenticationTicket from the Identity
// // and current authentication scheme
// var ticket = new AuthenticationTicket(
// new ClaimsPrincipal(claimsIdentity), this.Scheme.Name);
// // pass on the ticket to the middleware
// return Task.FromResult(AuthenticateResult.Success(ticket));
//}
return Task.FromResult(AuthenticateResult.Fail("Model is Empty"));
}
}
public class TokenModel
{
public int UserId { get; set; }
public string Name { get; set; }
public string EmailAddress { get; set; }
}
Startup.cs add below codes into the ConfigureServices method:
services.AddAuthentication(options =>
{
options.DefaultScheme
= "Test";
})
.AddScheme<ValidateHashAuthenticationSchemeOptions, ValidateHashAuthenticationHandler>
("Test", null);
Controller:
[Authorize(AuthenticationSchemes = "Test")]

Adding a custom AuthenticationHandler to a list of external login providers

I'm using ASP.NET Core 3.1, and I have multiple external login providers configured:
services.AddAuthentication()
.AddDeviantArt(d => {
d.Scope.Add("feed");
d.ClientId = Configuration["Authentication:DeviantArt:ClientId"];
d.ClientSecret = Configuration["Authentication:DeviantArt:ClientSecret"];
d.SaveTokens = true;
})
.AddTwitter(t => {
t.ConsumerKey = Configuration["Authentication:Twitter:ConsumerKey"];
t.ConsumerSecret = Configuration["Authentication:Twitter:ConsumerSecret"];
t.SaveTokens = true;
});
I'd like to create a custom AuthenticationProvider that, instead of redirecting to another website, asks for that website's "API key" and treats that as the access token. (The website in question does not support any version of OAuth.)
Before actually hooking it up, I want to test that I can get a custom AuthenticationProvider working at all, so I found one that implements HTTP Basic authentication:
public class CustomAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions> {
public CustomAuthenticationHandler(
IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock)
: base(options, logger, encoder, clock) { }
protected override async Task<AuthenticateResult> HandleAuthenticateAsync() {
if (!Request.Headers.ContainsKey("Authorization")) {
return AuthenticateResult.NoResult();
}
if (!AuthenticationHeaderValue.TryParse(Request.Headers["Authorization"], out AuthenticationHeaderValue headerValue)) {
return AuthenticateResult.NoResult();
}
if (!"Basic".Equals(headerValue.Scheme, StringComparison.OrdinalIgnoreCase)) {
return AuthenticateResult.NoResult();
}
byte[] headerValueBytes = Convert.FromBase64String(headerValue.Parameter);
string userAndPassword = Encoding.UTF8.GetString(headerValueBytes);
string[] parts = userAndPassword.Split(':');
if (parts.Length != 2) {
return AuthenticateResult.Fail("Invalid Basic authentication header");
}
string user = parts[0];
string password = parts[1];
bool isValidUser = true;
if (!isValidUser) {
return AuthenticateResult.Fail("Invalid username or password");
}
var claims = new[] { new Claim(ClaimTypes.Name, user) };
var identity = new ClaimsIdentity(claims, Scheme.Name);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name);
return AuthenticateResult.Success(ticket);
}
protected override async Task HandleChallengeAsync(AuthenticationProperties properties) {
Response.Headers["WWW-Authenticate"] = $"Basic realm=\"Custom realm name here\", charset=\"UTF-8\"";
await base.HandleChallengeAsync(properties);
}
}
I added this to Startup.cs:
.AddScheme<AuthenticationSchemeOptions, CustomAuthenticationHandler>("Test", "Testing", o => { })
The problem is that the HandleAuthenticateAsync method is never called. The other solutions I find to this problem generally say you need to make it the "default" authentication scheme, but I don't want this to interfere with how the other external login providers are set up.
Is there a way I can get this working without changing the default authentication scheme or adding additional attributes to my controllers or actions?
You need add a default Scheme when you add AddAuthentication
like services.AddAuthentication("Test")

WEB API 2 OAuth Client Credentials Authentication, How to add additional parameters?

I am developing a Web API 2 with "OAuth Client Credentials" flow where, in the first call for the authorization token, I need an additional parameter to be accessed after in the controller without the need to traffics it all the time in requests.
What is the best way to pass an additional parameter for the authentication process and uses it later in controllers without the need for traffics it in future requests and considering security issues ?
I found developers passing these parameters in the URL, and adding it to the OWinContext:
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
string clientId;
string clientSecret;
if (context.TryGetBasicCredentials(out clientId, out clientSecret) ||
context.TryGetFormCredentials(out clientId, out clientSecret))
{
try
{
if (context.Parameters.Any(x => x.Key == "Participant"))
{
// Get Participant(Additional Parameters)
string participant = context.Parameters.First(x => x.Key == "participant").Value[0];
//Validating Client
Microsoft.AspNet.Identity.PasswordHasher passwordHaser = new Microsoft.AspNet.Identity.PasswordHasher();
Client client = _clientAppService.GetById(Guid.Parse(clientId));
if (client != null && passwordHaser.VerifyHashedPassword(client.SecretHash, clientSecret) == Microsoft.AspNet.Identity.PasswordVerificationResult.Success)
{
context.OwinContext.Set<Client>("oauth:client", client);
//Store Participant(Additional Parameters)
context.OwinContext.Set<Participant>("urn:participant", new Participant { Document = participant });
context.Validated();
}
else
{
context.SetError("invalid_client", "Client credentials are invalid.");
context.Rejected();
}
}
else
{
context.SetError("invalid_request", "Participant are invalid.");
context.Rejected();
}
}
catch (Exception)
{
context.SetError("server_error");
context.Rejected();
}
}
else
{
context.SetError("invalid_client", "Client credentials could not be retrieved through the Authorization header.");
context.Rejected();
}
return Task.FromResult(0);
}
And I am trying to get it via an ModelBinder, unfortunately the context always returns null:
public class ParticipantModelBinder : IModelBinder
{
public bool BindModel(System.Web.Http.Controllers.HttpActionContext actionContext, ModelBindingContext bindingContext)
{
//Returning null
Participant participant = HttpContext.Current.GetOwinContext().Get<Participant>("urn:participant");
bindingContext.Model = participant;
return true;
}
}
Controller:
[Authorize]
public class ParticipantController : ApiController
{
public decimal GetBalance([ModelBinder(BinderType=typeof(ParticipantModelBinder))] Participant participant)
{
return 0;
}
}
And another solution I found on the web is to add a new Claim on the method GrantClientCredentials, but i dont know a way to get the value on the controllers:
public override Task GrantClientCredentials(OAuthGrantClientCredentialsContext context)
{
//Client validated, generate token
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
Client client = context.OwinContext.Get<Client>("oauth:client");
if (client.AllowedGrant == OAuthGrant.Client)
{
ClaimsIdentity identity = new ClaimsIdentity(new GenericIdentity(context.ClientId, OAuthDefaults.AuthenticationType));
identity.AddClaim(new Claim(ClaimTypes.Role, "User"));
// Add an custum Claim with the additional parameter
identity.AddClaim(new Claim("Participant", context.OwinContext.Get<Participant>("urn:participant").Document));
context.Validated(identity);
}
else
{
context.SetError(
"unauthorized_client",
"The authenticated client is not authorized to use this authorization grant type");
}
return Task.FromResult(0);
}