I'm working with an API and using the session feature in .Net Core 2.1 with the standard dependency injection to store and retrieve values in the session store. My issue is that I'm able to set and store string values into the Session.store but when I then try to retrieve those values in another method, the store is empty.
Here is my Startup.cs setting up the IHttpContextAccessor for DI and enabling session storage.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non- // essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
// Configure app settings to inject in other classes.
services.AddOptions();
services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
// Services to be injected.
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<IAuthenticationService, AuthenticationService>();
services.AddSingleton<IConstituentsService, ConstituentsService>();
services.AddSingleton<ISessionService, SessionService>();
// Add MVC.
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
// Configure session.
services.AddMemoryCache();
services.AddDistributedMemoryCache();
services.AddSession(options => {
options.IdleTimeout = TimeSpan.FromMinutes(10);
options.Cookie.Name = ".AuthCodeFlowTutorial.Session";
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseSession();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
Here is the class I'm using where I'm storing in the session store and then the method right after to retrieve. SetTokens() and TryGetString() respectively.
using System;
using System.Collections.Generic;
using System.Net.Http;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
namespace CHS.SkyApiAuthCodeFlow
{
/// <summary>
/// Sets, gets, and destroys session variables.
/// </summary>
public class SessionService : ISessionService
{
private const string ACCESS_TOKEN_NAME = "token";
private const string REFRESH_TOKEN_NAME = "refreshToken";
private readonly IHttpContextAccessor _httpContextAccessor;
public SessionService(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
/// <summary>
/// Destroys access and refresh tokens from the session.
/// </summary>
public void ClearTokens()
{
try
{
_httpContextAccessor.HttpContext.Session.Remove(ACCESS_TOKEN_NAME);
_httpContextAccessor.HttpContext.Session.Remove(REFRESH_TOKEN_NAME);
}
catch (Exception error)
{
Console.WriteLine("LOGOUT ERROR: " + error.Message);
}
}
/// <summary>
/// Return access token, if saved, or an empty string.
/// </summary>
public string GetAccessToken()
{
return TryGetString(ACCESS_TOKEN_NAME);
}
/// <summary>
/// Return refresh token, if saved, or an empty string.
/// </summary>
public string GetRefreshToken()
{
return TryGetString(REFRESH_TOKEN_NAME);
}
/// <summary>
/// Sets the access and refresh tokens based on an HTTP response.
/// </summary>
public void SetTokens(HttpResponseMessage response)
{
if (response.IsSuccessStatusCode)
{
string jsonString = response.Content.ReadAsStringAsync().Result;
Dictionary<string, string> attrs = JsonConvert.DeserializeObject<Dictionary<string, string>>(jsonString);
_httpContextAccessor.HttpContext.Session.SetString(ACCESS_TOKEN_NAME, attrs["access_token"]);
_httpContextAccessor.HttpContext.Session.SetString(REFRESH_TOKEN_NAME, attrs["refresh_token"]);
}
}
/// <summary>
/// Return session value as a string (if it exists), or an empty string.
/// </summary>
private string TryGetString(string name)
{
byte[] valueBytes = new Byte[700];
bool valueOkay = _httpContextAccessor.HttpContext.Session.TryGetValue(name, out valueBytes);
if (valueOkay)
{
return System.Text.Encoding.UTF8.GetString(valueBytes);
}
return null;
}
}
}
Below is the AuthenticationService that calls on the SessionService methods.
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using Microsoft.Extensions.Options;
namespace CHS.SkyApiAuthCodeFlow
{
/// <summary>
/// Contains business logic and helper methods that interact with the authentication provider.
/// </summary>
public class AuthenticationService : IAuthenticationService
{
private readonly IOptions<AppSettings> _appSettings;
private ISessionService _sessionService;
public AuthenticationService(IOptions<AppSettings> appSettings, ISessionService sessionService)
{
_appSettings = appSettings;
_sessionService = sessionService;
}
/// <summary>
/// Fetches access/refresh tokens from the provider.
/// <param name="requestBody">Key-value attributes to be sent with the request.</param>
/// <returns>The response from the provider.</returns>
/// </summary>
private HttpResponseMessage FetchTokens(Dictionary<string, string> requestBody)
{
using (HttpClient client = new HttpClient())
{
// Build token endpoint URL.
string url = new Uri(new Uri(_appSettings.Value.AuthBaseUri), "token").ToString();
// Set request headers.
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/x-www-form-urlencoded"));
client.DefaultRequestHeaders.TryAddWithoutValidation(
"Authorization", "Basic " + Base64Encode(_appSettings.Value.AuthClientId + ":" + _appSettings.Value.AuthClientSecret));
// Fetch tokens from auth server.
HttpResponseMessage response = client.PostAsync(url, new FormUrlEncodedContent(requestBody)).Result;
// Save the access/refresh tokens in the Session.
_sessionService.SetTokens(response);
return response;
}
}
/// <summary>
/// Fetches a new set of access/refresh tokens (from an authorization code).
/// <param name="code">The authorization code contained within the provider's authorization response.</param>
/// </summary>
public HttpResponseMessage ExchangeCodeForAccessToken(string code)
{
return FetchTokens(new Dictionary<string, string>(){
{ "code", code },
{ "grant_type", "authorization_code" },
{ "redirect_uri", _appSettings.Value.AuthRedirectUri }
});
}
/// <summary>
/// Refreshes the expired access token (from the refresh token stored in the session).
/// </summary>
public HttpResponseMessage RefreshAccessToken()
{
return FetchTokens(new Dictionary<string, string>(){
{ "grant_type", "refresh_token" },
{ "refresh_token", _sessionService.GetRefreshToken() }
});
}
/// <summary>
/// Builds and returns a string representative of the provider's authorization URI.
/// </summary>
public Uri GetAuthorizationUri()
{
return new Uri(
new Uri(_appSettings.Value.AuthBaseUri), "authorization" +
"?client_id=" + _appSettings.Value.AuthClientId +
"&response_type=code" +
"&redirect_uri=" + _appSettings.Value.AuthRedirectUri
);
}
/// <summary>
/// Determines if the session contains an access token.
/// </summary>
public bool IsAuthenticated()
{
return (_sessionService.GetAccessToken() != null);
}
/// <summary>
/// Destroys the access/refresh tokens stored in the session.
/// </summary>
public void LogOut()
{
_sessionService.ClearTokens();
}
/// <summary>
/// Encodes a string as Base64.
/// </summary>
private static string Base64Encode(string plainText)
{
byte[] bytes = System.Text.Encoding.UTF8.GetBytes(plainText);
return System.Convert.ToBase64String(bytes);
}
}
}
Related
I'm attempting to sign in to an MVC application using IdentityServer4 along with Asp.Net Identity.
User is redirected to the identity server, they enter their credentials and they are redirected back to the MVC app when successful.However it appears that the MVC app isn't saving the token and is redirecting back to IdentityServer.When I add an Event Handler OnTokenValidated within the MVC app, I can see that there is a User Principle and they are Authorized, after the cycle is complete, OnRedirectToIdentityProvider is invoked and the cycle starts again.
MVCC - Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add Custom Services.
services.AddCustomAuthorization(Configuration);
// Add Required Services.
services.AddControllersWithViews(options =>
{
AuthorizationPolicy policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
})
.AddRazorRuntimeCompilation()
.AddMicrosoftIdentityUI();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy(new CookiePolicyOptions()
{
Secure = CookieSecurePolicy.Always,
HttpOnly = Microsoft.AspNetCore.CookiePolicy.HttpOnlyPolicy.Always,
});
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}")
.RequireAuthorization();
});
}
}
public static class Extensions
{
/// <summary>
///
/// </summary>
/// <param name="services"></param>
/// <param name="configuration"></param>
/// <returns></returns>
public static IServiceCollection AddCustomAuthorization(this IServiceCollection services, IConfiguration configuration)
{
JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = "oidc";
})
.AddCookie(IdentityServerConstants.ExternalCookieAuthenticationScheme)
.AddOpenIdConnect("oidc", "IdentityServer", options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.SignOutScheme = IdentityServerConstants.SignoutScheme;
options.SaveTokens = true;
options.Authority = "https://localhost:44308/";
options.ClientId = "3468";
options.ClientSecret = "##";
options.ResponseType = "code";
options.ReturnUrlParameter = "https://localhost:44346/Home/Foo";
options.GetClaimsFromUserInfoEndpoint = true;
options.Events = new OpenIdConnectEvents()
{
// Events Added To Inspect Data.
OnTokenResponseReceived = async context =>
{
await Task.Yield();
},
OnRedirectToIdentityProvider = async context =>
{
await Task.Yield();
},
OnTokenValidated = async context =>
{
await Task.Yield();
},
OnTicketReceived = async context =>
{
await Task.Yield();
},
OnMessageReceived = async context =>
{
await Task.Yield();
},
OnUserInformationReceived = async content =>
{
await Task.Yield();
},
};
});
return services;
}
}
IdentityServer- Startup.cs
public class Startup
{
/// <summary>
///
/// </summary>
public static string CertificatePath { get; set; }
/// <summary>
///
/// </summary>
/// <param name="configuration"></param>
/// <param name="environment"></param>
public Startup(IConfiguration configuration, IHostEnvironment environment)
{
Configuration = configuration;
CertificatePath = System.IO.Path.Combine(environment.ContentRootPath, "##.pfx");
}
/// <summary>
///
/// </summary>
public static IConfiguration Configuration { get; set; }
/// <summary>
///
/// </summary>
public static readonly bool IsIIS = NativeMethods.IsAspNetCoreModuleLoaded;
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews().AddRazorRuntimeCompilation();
services.AddCustomIdentityServer(Configuration);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
// Removed To Shorten Sample.
EnsureDatabasesAsync(app).Wait();
app.UseStaticFiles();
app.UseRouting();
app.UseIdentityServer();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
}
public static class Extensions
{
/// <summary>
///
/// </summary>
/// <param name="services"></param>
/// <param name="configuration"></param>
/// <returns></returns>
public static IServiceCollection AddCustomIdentityServer(this IServiceCollection services, IConfiguration configuration)
{
string migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
// Add Asp Identity DbContext.
services.AddDbContext<ApplicationDbContext>(options =>
{
options.UseSqlServer(connectionString, sql =>
{
sql.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery);
sql.EnableRetryOnFailure(maxRetryCount: 5, System.TimeSpan.FromSeconds(10), errorNumbersToAdd: null);
sql.MigrationsAssembly(migrationsAssembly);
});
});
// Add Asp Identity.
services.AddIdentity<AppIdentityUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
// Builder IdentityServer4
// Using Users From AspNetIdentity.
IIdentityServerBuilder builder = services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
})
.AddAspNetIdentity<AppIdentityUser>()
.AddConfigurationStore(options =>
{
options.ConfigureDbContext = b =>
b.UseSqlServer(connectionString, sql =>
{
sql.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery);
sql.EnableRetryOnFailure(maxRetryCount: 5, System.TimeSpan.FromSeconds(10), errorNumbersToAdd: null);
sql.MigrationsAssembly(migrationsAssembly);
});
})
.AddOperationalStore(options =>
{
options.ConfigureDbContext = b =>
b.UseSqlServer(connectionString, sql =>
{
sql.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery);
sql.EnableRetryOnFailure(maxRetryCount: 5, System.TimeSpan.FromSeconds(10), errorNumbersToAdd: null);
sql.MigrationsAssembly(migrationsAssembly);
});
options.EnableTokenCleanup = true;
});
#if DEBUG
builder.AddDeveloperSigningCredential();
#else
X509Certificate2 x509Certificate = null;
x509Certificate = new X509Certificate2(Startup.CertificatePath);
builder.AddSigningCredential(x509Certificate);
#endif
services.AddAuthentication()
.AddMicrosoftAccount(options =>
{
options.ClientId = "##";
options.ClientSecret = "##";
options.SaveTokens = true;
});
return services;
}
}
IdentityServer - Account Controller
[AllowAnonymous]
public class AccountController : Controller
{
private readonly UserManager<AppIdentityUser> _userManager;
private readonly SignInManager<AppIdentityUser> _signInManager;
private readonly IIdentityServerInteractionService _interaction;
private readonly IClientStore _clientStore;
private readonly IAuthenticationSchemeProvider _schemeProvider;
private readonly IEventService _events;
/// <summary>
///
/// </summary>
/// <param name="userManager"></param>
/// <param name="signInManager"></param>
public AccountController(IIdentityServerInteractionService interaction,
UserManager<AppIdentityUser> userManager,
SignInManager<AppIdentityUser> signInManager,
IClientStore clientStore,
IAuthenticationSchemeProvider schemeProvider,
IEventService events)
{
this._userManager = userManager ?? throw new ArgumentNullException(nameof(userManager));
this._signInManager = signInManager ?? throw new ArgumentNullException(nameof(signInManager));
this._interaction = interaction ?? throw new ArgumentNullException(nameof(interaction));
this._clientStore = clientStore ?? throw new ArgumentNullException(nameof(clientStore));
this._schemeProvider = schemeProvider ?? throw new ArgumentNullException(nameof(schemeProvider));
this._events = events ?? throw new ArgumentNullException(nameof(events));
}
/// <summary>
///
/// </summary>
/// <param name="returnUrl"></param>
/// <returns></returns>
public IActionResult Login(string returnUrl)
{
// Check There Is A ReturnUrl So We Dont Forever Loop.
if (User.Identity.IsAuthenticated && returnUrl == "/") return Ok();
// Redirect To The ReturnUrl.
return User.Identity.IsAuthenticated ? Redirect(returnUrl) : View();
}
/// <summary>
///
/// </summary>
/// <param name="identityLoginModel"></param>
/// <returns></returns>
[HttpPost]
#if !DEBUG
[ValidateAntiForgeryToken]
#endif
public async Task<IActionResult> Login([FromForm] IdentityLoginModel identityLoginModel)
{
try
{
AppIdentityUser identityUser = await _userManager.FindByEmailAsync(identityLoginModel.EmailAddress);
Microsoft.AspNetCore.Identity.SignInResult signinResult =
await _signInManager.PasswordSignInAsync(identityUser.UserName, identityLoginModel.Password, identityLoginModel.IsPersistent, false);
if (signinResult.Succeeded)
{
await _events.RaiseAsync(new UserLoginSuccessEvent(identityLoginModel.EmailAddress, identityUser.Id, identityUser.UserName));
return Redirect(IdentityLoginModel.ReturnUrl);
}
else
{
// TODO: Return Model With Unauthorized Message Attached.
return Unauthorized();
}
}
catch (Exception ex)
{
// TODO: Log error(s)
return Problem(statusCode: 500);
}
}
}
I am developing a web-api which uses some authorization against another of our applications to verify that a user is still logged in and may continue with the action they've requested. But the server responds with a 500 internal server error when the user is not Authenticated, stack trace can be found below.
I seem to be unable to wrestle the configuration into a correct state. From the stack trace, I can tell that it tries to fall back to some default authentication scheme, which I haven't defined and ideally aren't interested in as we always wish to authorize against the 3rd party instead and simply fail if it returns false.
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IAuthService>(_ =>
new AuthService(damInfo.BaseUrl, damInfo.SystemUsername, damInfo.SystemPassword));
services.AddScoped<IAuthorizationHandler, AuthenticationHandler>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddAuthorization(options =>
{
options.AddPolicy("ServicePolicy", policy =>
policy.Requirements.Add(new ServiceRequirement("")));
/*options.DefaultPolicy = new AuthorizationPolicyBuilder().AddAuthenticationSchemes(Constants.NoOp).RequireAuthenticatedUser().Build(); // keep#1#*/
options.InvokeHandlersAfterFailure = false;
});
services.AddAuthentication(options =>
{
options.DefaultScheme = Constants.NoOp;
options.DefaultChallengeScheme = Constants.NoOp;
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
app.UseDeveloperExceptionPage();
else
app.UseHsts();
app.UseCors(builder =>
{
builder.AllowAnyHeader();
builder.AllowAnyMethod();
builder.AllowAnyOrigin();
});
app.UseHttpsRedirection();
app.UseMvc();
}
Policy:
using Microsoft.AspNetCore.Authorization;
namespace Core.Services.Authentication
{
public class ServiceRequirement : IAuthorizationRequirement
{
public string CurrentToken { get; private set; }
public ServiceRequirement(string token)
{
CurrentToken = token;
}
}
}
Authorize attribute:
[Authorize(Policy = "ServicePolicy")]
Auth handler:
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Digizuite.Services.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
namespace Core.Services.Authentication
{
public class AuthenticationHandler : AuthorizationHandler<ServiceRequirement>
{
readonly IAuthService _authService;
/// <summary>
/// Authentication handler that check if user is logged in
/// </summary>
/// <param name="authService"></param>
public AuthenticationHandler(IAuthService authService)
{
_authService = authService;
}
/// <summary>
/// Handle requirement and check isLoggedIn
/// </summary>
/// <param name="context"></param>
/// <param name="requirement"></param>
/// <returns></returns>
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context,
ServiceRequirement requirement)
{
var authFilterCtx = (Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext)context.Resource;
var httpContext = authFilterCtx.HttpContext;
var headers = httpContext.Request.Headers[HeaderNames.Authorization];
if (StringValues.IsNullOrEmpty(headers) == true)
{
context.Fail();
}
else
{
var auth = AuthenticationHeaderValue.Parse(headers);
var loginResponse = _authService.IsLoggedInRequest(auth.Parameter).Result;
if (loginResponse.IsLoggedIn == false)
{
context.Fail();
}
else
{
context.Succeed(requirement);
}
}
}
}
}
StackTrace:
fail: Microsoft.AspNetCore.Server.Kestrel[13]
Connection id "0HLOC48NR5MP4", Request id "0HLOC48NR5MP4:00000001": An unhandled exception was thrown by the application.
System.InvalidOperationException: No authenticationScheme was specified, and there was no DefaultChallengeScheme found.
at Microsoft.AspNetCore.Authentication.AuthenticationService.ChallengeAsync(HttpContext context, String scheme, AuthenticationProperties properties)
at Microsoft.AspNetCore.Mvc.ChallengeResult.ExecuteResultAsync(ActionContext context)
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeResultAsync(IActionResult result)
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAlwaysRunResultFilters()
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeFilterPipelineAsync()
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAsync()
at Microsoft.AspNetCore.Builder.RouterMiddleware.Invoke(HttpContext httpContext)
at Microsoft.AspNetCore.Cors.Infrastructure.CorsMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at Digizuite.Logging2.LogClient.LoggerRequestMiddleware.InvokeAsync(HttpContext context, RequestDelegate next) in C:\dev\digizuite.core\Libs\LogClient\LoggerRequestMiddleware.cs:line 40
at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.<>c__DisplayClass5_1.<<UseMiddlewareInterface>b__1>d.MoveNext()
I am working on .NET Core Web API localization and I have done all configurations but I am unable to get local specific values.
Created 3 resource files for de-DE, fr-FR and English languages.
Created a common Middleware to set the current culture based on the
query string value.
Trying to get the respective local key but it is always giving English value.
Can anyone suggest and tell did I miss anything in the configuration.
Middleware
namespace i18n
{
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
/// <summary>
/// The LocalizationMiddleware class.
/// </summary>
public class LocalizationMiddleware
{
private readonly RequestDelegate _next;
/// <summary>
/// Initializes a new instance of the <see cref="LocalizationMiddleware"/> class.
/// </summary>
/// <param name="next">The next.</param>
public LocalizationMiddleware(RequestDelegate next)
{
_next = next;
}
/// <summary>
/// Invokes the specified context.
/// </summary>
/// <param name="context">The context.</param>
/// <returns>Task.</returns>
public async Task Invoke(HttpContext context)
{
var localValue = "en-US";
if (context.Request.Query.ContainsKey("locale"))
{
localValue = context.Request.Query["locale"];
}
Thread.CurrentThread.CurrentCulture = new CultureInfo(localValue);
Thread.CurrentThread.CurrentUICulture = new CultureInfo(localValue);
await _next.Invoke(context);
}
}
/// <summary>
/// The LocalizationMiddlewareExtensions class.
/// </summary>
public static class LocalizationMiddlewareExtensions
{
/// <summary>
/// Uses the localization middleware.
/// </summary>
/// <param name="builder">The builder.</param>
/// <returns>The ApplicationBuilder.</returns>
public static IApplicationBuilder UseLocalizationMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<LocalizationMiddleware>();
}
} }
Startup class
I have registered the Middleware.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseLocalizationMiddleware();
}
Web API Controller
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
var name = Employees.Name; // Always Name is coming english value
return new string[] { name };
}
This is my solution structure:
I have a custom AuthorizationFilter Attribute on my Web Api project like this
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class GenericAuthenticationFilter : AuthorizationFilterAttribute
{
/// <summary>
/// Public default Constructor
/// </summary>
public GenericAuthenticationFilter()
{
}
private readonly bool _isActive = true;
/// <summary>
/// parameter isActive explicitly enables/disables this filter.
/// </summary>
/// <param name="isActive"></param>
public GenericAuthenticationFilter(bool isActive)
{
_isActive = isActive;
}
/// <summary>
/// Checks basic authentication request
/// </summary>
/// <param name="filterContext"></param>
public override void OnAuthorization(HttpActionContext filterContext)
{
if (!_isActive) return;
var identity = FetchAuthHeader(filterContext);
if (identity == null)
{
ChallengeAuthRequest(filterContext);
return;
}
var genericPrincipal = new GenericPrincipal(identity, null);
Thread.CurrentPrincipal = genericPrincipal;
if (!OnAuthorizeUser(identity.Name, identity.Password, filterContext))
{
ChallengeAuthRequest(filterContext);
return;
}
base.OnAuthorization(filterContext);
}
My StartUpClass is like this
public class Startup
{
public void Configuration(IAppBuilder app)
{
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=316888
// Get your HttpConfiguration. In OWIN, you'll create one
// rather than using GlobalConfiguration.
var config = new HttpConfiguration();
WebApiConfig.Register(config);
IoC.Instance.RegisterApiControllers(Assembly.GetExecutingAssembly());
config.DependencyResolver =
new AutofacWebApiDependencyResolver(IoC.Instance.GetComponentsContainer());
// Register your Web Api controllers.
IoC.Instance.RegisterApiControllers(Assembly.GetExecutingAssembly());
IoC.Instance.RegisterWebApiModelBinders(Assembly.GetExecutingAssembly());
IoC.Instance.RegisterWebApiModelBinderProvider();
IoC.Instance.RegisterWebApiFilterProvider(config);
// Register the Autofac middleware FIRST, then the Autofac Web API middleware,
// and finally the standard Web API middleware.
app.UseAutofacMiddleware(IoC.Instance.GetComponentsContainer());
app.UseAutofacWebApi(config);
app.UseWebApi(config);
}
}
and my IoC class where all dependencies are resolved is like this
public class IoC : ContainerBuilder
{
/// <summary>
///
/// </summary>
private readonly static IoC _instance = new IoC();
/// <summary>
///
/// </summary>
private static object _lock;
/// <summary>
///
/// </summary>
private IContainer _componentsContainer;
/// <summary>
///
/// </summary>
public static IoC Instance
{
get
{
return _instance;
}
}
/// <summary>
///
/// </summary>
/// <returns></returns>
public IContainer GetComponentsContainer()
{
if (_componentsContainer == null)
{
lock (_lock)
{
if (_componentsContainer == null)
_componentsContainer = this.Build();
}
}
return _componentsContainer;
}
/// <summary>
///
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public T Resolve<T>() where T : class
{
return GetComponentsContainer().Resolve<T>();
}
/// <summary>
///
/// </summary>
/// <returns></returns>
public ILifetimeScope BeginLifetimeScope()
{
return GetComponentsContainer().BeginLifetimeScope();
}
/// <summary>
///
/// </summary>
private IoC()
{
_lock = new object();
ConfigureDependencies();
}
/// <summary>
///
/// </summary>
private void ConfigureDependencies()
{
//Configure all your depedendencies here!!
//Database connection
var connectionString = ConfigurationManager.ConnectionStrings["DBConnectionStringName"].ConnectionString;
this.Register(c => new SqlConnection(connectionString)).As<IDbConnection>().InstancePerRequest();// InstancePerLifetimeScope();
//Database Connection OrmLite
OrmLiteConfig.DialectProvider = SqlServerDialect.Provider;
//Register Repositories
this.RegisterType<Repository>().As<IRepository>().InstancePerRequest();// InstancePerLifetimeScope();
// Register Services
this.RegisterType<UserService>().As<IUserService>().InstancePerRequest();// InstancePerLifetimeScope();
this.RegisterType<TokenService>().As<ITokenService>().InstancePerRequest();
this.RegisterType<DKMenuService>().As<IDKMenuService>().InstancePerRequest();// InstancePerLifetimeScope();
this.RegisterType<DKGRIDTblService>().As<IDKGRIDTblService>().InstancePerRequest();// InstancePerLifetimeScope();
this.RegisterType<FKService>().As<IFKService>().InstancePerRequest();// InstancePerLifetimeScope();
this.RegisterType<LOVService>().As<ILOVService>().InstancePerRequest();// InstancePerLifetimeScope();
this.RegisterType<JobService>().As<IJobService>().InstancePerRequest();// InstancePerLifetimeScope();
this.RegisterType<MADEService>().As<IMADEService>().InstancePerRequest();// InstancePerLifetimeScope();
}
}
And I decorate my Controllers with this filter like this
[GenericAuthenticationFilter]
public AuthenticateController(ITokenService tokenService)
{
_tokenService = tokenService;
}
My Problem is that the OnAuthorazation method of the GenericAuthenticationFilter is never fired.
If on the IoC Class class I change InstancePerRequest to InstancePerLifetimeScope everything works ok, but I want my dependencies to work per Request
Any Ideas?
I'm not sure if this is part or all of your issue, but... you can only build a ContainerBuilder once. In Startup.Configuration() I see on lines 11-12:
config.DependencyResolver =
new AutofacWebApiDependencyResolver(IoC.Instance.GetComponentsContainer());
And IoC.Instance.GetComponentsContainer() calls Build() to create the container.
But just two lines later I see you're adding more components to the container, and after that I see a second call:
app.UseAutofacMiddleware(IoC.Instance.GetComponentsContainer());
Based on your code, that's going to be the same container that was built before you added the new registrations. The container won't include the API controllers, the model binders, or the filter provider.
I'm actually not sure why you're not having more problems than you're having now.
Try moving the setting of the containers (the calls to IoC.Instance.GetComponentsContainer()) until all the way at the end, after you've finished registering all of your dependencies.
The only configuration that worked was the following
public class Startup
{
public void Configuration(IAppBuilder app)
{
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=316888
// Get your HttpConfiguration. In OWIN, you'll create one
// rather than using GlobalConfiguration.
var config = new HttpConfiguration();
WebApiConfig.Register(config);
// Register your Web Api controllers.
IoC.Instance.RegisterApiControllers(Assembly.GetExecutingAssembly());
IoC.Instance.RegisterWebApiModelBinders(Assembly.GetExecutingAssembly());
IoC.Instance.RegisterWebApiModelBinderProvider();
config.DependencyResolver =
new AutofacWebApiDependencyResolver(IoC.Instance.GetComponentsContainer());
// Register your Web Api controllers.
//IoC.Instance.RegisterApiControllers(Assembly.GetExecutingAssembly());
//IoC.Instance.RegisterWebApiModelBinders(Assembly.GetExecutingAssembly());
//IoC.Instance.RegisterWebApiModelBinderProvider();
// Register the Autofac middleware FIRST, then the Autofac Web API middleware,
// and finally the standard Web API middleware.
app.UseAutofacMiddleware(IoC.Instance.GetComponentsContainer());
app.UseAutofacWebApi(config);
app.UseWebApi(config);
}
}
I am still not sure whether it is right.
Examples of usage. I have a filter like the following
public class ApiAuthenticationFilter : GenericAuthenticationFilter
{
/// <summary>
/// Default Authentication Constructor
/// </summary>
public ApiAuthenticationFilter()
{
}
}
A method in this filter is using a service which is resolved like this
protected override bool OnAuthorizeUser(string username, string password, HttpActionContext actionContext)
{
var provider = actionContext.Request.GetDependencyScope().GetService(typeof(IUserService)) as IUserService;
}
My controllers on the other hand do not have parameterless constructors and the dependencies are resolved automatically
public class DKMenuController : ApiController
{
#region Private variable.
private readonly ITokenService _tokenService;
private readonly IDKMenuService _dkMenuService;
private readonly IUserService _userservice;
private const string Token = "Token";
#endregion
#region Public Constructor
/// <summary>
/// Public constructor to initialize DKMenu service instance
/// </summary>
public DKMenuController(ITokenService tokenService, IUserService userservice, IDKMenuService dkMenuService)
{
_tokenService = tokenService;
_dkMenuService = dkMenuService;
_userservice = userservice;
}
}
I don't know whether it is correct but it works
Is it possible with an Owin Middleware implementation to add claims prior to the execution of a Web API controller?
Created an OwinMiddleware implementation and added an identity:
var id = new ClaimsIdentity();
id.AddClaim(new Claim("Whatever", "is possible"));
context.Authentication.User.AddIdentity(id);
await Next.Invoke(context);
However, even this Invoke method call the identities are not updated (just the internal claims array). And the controller when executed of course never gets the new dummy claim.
Ideas?
There's already a class that can provide claims enrichment ClaimsAuthenticationManager, which you can extend so it handles your domain-specific claims, for example...
public class MyClaimsAuthenticationManager : ClaimsAuthenticationManager
{
public override ClaimsPrincipal Authenticate(string resourceName, ClaimsPrincipal incomingPrincipal)
{
if (!incomingPrincipal.Identity.IsAuthenticated)
{
return base.Authenticate(resourceName, incomingPrincipal);
}
return AddApplicationClaims(incomingPrincipal);
}
private ClaimsPrincipal AddApplicationClaims(ClaimsPrincipal principal)
{
// TODO: Add custom claims here based on current principal.
return principal;
}
}
Next task is to provide appropriate middleware to invoke this. For my projects I've written the following classes...
/// <summary>
/// Middleware component to apply claims transformation to current context
/// </summary>
public class ClaimsTransformationMiddleware
{
private readonly Func<IDictionary<string, object>, Task> next;
private readonly IServiceProvider serviceProvider;
public ClaimsTransformationMiddleware(Func<IDictionary<string, object>, Task> next, IServiceProvider serviceProvider)
{
this.next = next;
this.serviceProvider = serviceProvider;
}
public async Task Invoke(IDictionary<string, object> env)
{
// Use Katana's OWIN abstractions
var context = new OwinContext(env);
if (context.Authentication != null && context.Authentication.User != null)
{
var manager = serviceProvider.GetService<ClaimsAuthenticationManager>();
context.Authentication.User = manager.Authenticate(context.Request.Uri.AbsoluteUri, context.Authentication.User);
}
await next(env);
}
}
And then a wiring extension...
public static class AppBuilderExtensions
{
/// <summary>
/// Add claims transformation using <see cref="ClaimsTransformationMiddleware" /> any depdendency resolution is done via IoC
/// </summary>
/// <param name="app"></param>
/// <param name="serviceProvider"></param>
/// <returns></returns>
public static IAppBuilder UseClaimsTransformation(this IAppBuilder app, IServiceProvider serviceProvider)
{
app.Use<ClaimsTransformationMiddleware>(serviceProvider);
return app;
}
}
I know this is service locator anti-pattern but using IServiceProvider is container neutral and seems to be the accepted way of putting dependencies into Owin middleware.
Last you need to wire this up in your Startup, example below presumes Unity and registering/exposing a IServiceLocator property...
// Owin config
app.UseClaimsTransformation(UnityConfig.ServiceLocator);
You may find useful inheriting from Authorizate Attribute and extending it to meet your requirements:
public class DemoAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext){
if (Authorize(actionContext)){
return;
}
HandleUnauthorizedRequest(actionContext);
}
protected override void HandleUnauthorizedRequest(System.Web.Http.Controllers.HttpActionContext actionContext){
var challengeMessage = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized;
//Adding your code here
var id = new ClaimsIdentity();
id.AddClaim(new Claim("Whatever", "is possible"));
context.Authentication.User.AddIdentity(id);
challengeMessage.Headers.Add("WWW-Authenticate", "Basic");
throw new HttpResponseException(challengeMessage);
}
private bool Authorize(System.Web.Http.Controllers.HttpActionContext actionContext){
try{
var someCode = (from h in actionContext.Request.Headers where h.Key == "demo" select h.Value.First()).FirstOrDefault();
// or check for the claims identity property.
return someCode == "myCode";
}
catch (Exception){
return false;
}
}
}
And in your controller:
[DemoAuthorize]
public class ValuesController : ApiController{
Here is a link on other custom implemenation for WebApi Authorizations:
http://www.piotrwalat.net/basic-http-authentication-in-asp-net-web-api-using-membership-provider/
This is how I ended up adding a new claim in owin middleware, based on the OP's comment about hooking into UseOAuthBearerAuthentication. It uses IdentityServer3.AccessTokenValidation, which calls UseOAuthBearerAuthentication internally and passes the OAuthBearerAuthenticationProvider through to it.
using System.Security.Claims;
using System.Threading.Tasks;
using IdentityServer3.AccessTokenValidation;
using Owin;
using Microsoft.Owin.Security.OAuth;
//...
public void Configuration(IAppBuilder app)
{
app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = "http://127.0.0.1/identityserver",
TokenProvider = new OAuthBearerAuthenticationProvider
{
OnValidateIdentity = AddClaim
}
});
}
private Task AddClaim(OAuthValidateIdentityContext context)
{
context.Ticket.Identity.AddClaim(new Claim("test", "123"));
return Task.CompletedTask;
}