MVC - Mixed Auth - OWIN + Windows Auth - c#

I need to have both windows authentication and owin (forms) authentication but i can't get it to work.
Probably the best option is to have two sites that have different authentication methods.
I found a project that does what i want: MVC5-MixedAuth. But it uses IISExpress and i can't get it to work with Local IIS.
The error that occurs is:
Request filtering is configured on the Web server to deny the request because the query string is too long.
If i remove all my ConfigureAuth() method inside Startup.Auth.cs it doesn't throw the error but i can't login because it is needed to do CookieAuthentication.
Startup.Auth.cs:
public void ConfigureAuth(IAppBuilder app)
{
app.CreatePerOwinContext(dbEmployeePortal.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, UserMaster, int>
(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentityCallback: (manager, user) => user.GenerateUserIdentityAsync(manager),
getUserIdCallback: (id) => (Int32.Parse(id.GetUserId()))
)
}
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
}
Any idea?
UPDATE 1
The error
Request filtering is configured on the Web server to deny the request because the query string is too long.
appears because occurs a login loop when it tries to reach the login page.

Resolved!
I followed the example: MVC5-MixAuth
Credits: Mohammed Younes
UPDATE 1
Problem: I needed to have both Anonymous Authentication and Windows Authentication enabled.
But when you enable them both, you can only get NT AUTHORITY\IUSR.
Resolution: To get the current user (introduced with NTLM prompt), we need to create an handler that will execute when an user enter at login page.
When the user hits the login page, the handler will get the current windows identity cached in the browser and then set as the LogonUserIdentity.
Note: I needed to use windows first login, when the user hits the login page it will try to get the correspondent ASP.NET User.
Handler
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using Microsoft.AspNet.Identity;
namespace MixedAuth
{
/// <summary>
/// Managed handler for windows authentication.
/// </summary>
public class WindowsLoginHandler : HttpTaskAsyncHandler, System.Web.SessionState.IRequiresSessionState
{
public HttpContext Context { get; set; }
public override async Task ProcessRequestAsync(HttpContext context)
{
this.Context = context;
//if user is already authenticated, LogonUserIdentity will be holding the current application pool identity.
//to overcome this:
//1. save userId to session.
//2. log user off.
//3. request challenge.
//4. log user in.
if (context.User.Identity.IsAuthenticated)
{
this.SaveUserIdToSession(context.User.Identity.GetUserId());
await WinLogoffAsync(context);
context.RequestChallenge();
}
else if (!context.Request.LogonUserIdentity.IsAuthenticated)
{
context.RequestChallenge();
}
else
{
// true: user is trying to link windows login to an existing account
if (this.SessionHasUserId())
{
var userId = this.ReadUserIdFromSession();
this.SaveUserIdToContext(userId);
await WinLinkLoginAsync(context);
}
else // normal login.
await WinLoginAsync(context);
}
}
#region helpers
/// <summary>
/// Executes Windows login action against account controller.
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
private async Task WinLoginAsync(HttpContext context)
{
var routeData = this.CreateRouteData(Action.Login);
routeData.Values.Add("returnUrl", context.Request["returnUrl"]);
routeData.Values.Add("userName", context.Request.Form["UserName"]);
await ExecuteController(context, routeData);
}
/// <summary>
/// Execute Link Windows login action against account controller.
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
private async Task WinLinkLoginAsync(HttpContext context)
{
var routeData = this.CreateRouteData(Action.Link);
await ExecuteController(context, routeData);
}
/// <summary>
/// Executes Windows logoff action against controller.
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
private async Task WinLogoffAsync(HttpContext context)
{
var routeData = this.CreateRouteData(Action.Logoff);
await ExecuteController(context, routeData);
}
/// <summary>
/// Executes controller based on route data.
/// </summary>
/// <param name="context"></param>
/// <param name="routeData"></param>
/// <returns></returns>
private async Task ExecuteController(HttpContext context, RouteData routeData)
{
var wrapper = new HttpContextWrapper(context);
MvcHandler handler = new MvcHandler(new RequestContext(wrapper, routeData));
IHttpAsyncHandler asyncHandler = ((IHttpAsyncHandler)handler);
await Task.Factory.FromAsync(asyncHandler.BeginProcessRequest, asyncHandler.EndProcessRequest, context, null);
}
#endregion
}
}
Extensions
using System;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using System.Web.Routing;
namespace MixedAuth
{
public enum Action { Login, Link, Logoff };
public static class MixedAuthExtensions
{
const string userIdKey = "windows.userId";
//http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
const int fakeStatusCode = 418;
const string controllerName = "Account";
const string loginActionName = "WindowsLogin";
const string linkActionName = "LinkWindowsLogin";
const string logoffActionName = "WindowsLogoff";
const string windowsLoginRouteName = "Windows/Login";
public static void RegisterWindowsAuthentication(this MvcApplication app)
{
app.EndRequest += (object sender, EventArgs e) =>
{
HttpContext.Current.ApplyChallenge();
};
}
/// <summary>
/// Registers ignore route for the managed handler.
/// </summary>
/// <param name="routes"></param>
public static void IgnoreWindowsLoginRoute(this RouteCollection routes)
{
routes.IgnoreRoute(windowsLoginRouteName);
}
/// <summary>
/// By pass all middleware and modules, by setting a fake status code.
/// </summary>
/// <param name="context"></param>
public static void RequestChallenge(this HttpContext context)
{
context.Response.StatusCode = fakeStatusCode;
}
/// <summary>
/// Invoke on end response only. Replaces the current response status code with 401.2
/// </summary>
/// <param name="context"></param>
public static void ApplyChallenge(this HttpContext context)
{
if (context.Response.StatusCode == fakeStatusCode)
{
context.Response.StatusCode = 401;
context.Response.SubStatusCode = 2;
//http://msdn.microsoft.com/en-us/library/system.web.httpresponse.tryskipiiscustomerrors(v=vs.110).aspx
//context.Response.TrySkipIisCustomErrors = true;
}
}
/// <summary>
///
/// </summary>
/// <param name="handler"></param>
/// <param name="action"></param>
/// <returns></returns>
public static RouteData CreateRouteData(this WindowsLoginHandler handler, Action action)
{
RouteData routeData = new RouteData();
routeData.RouteHandler = new MvcRouteHandler();
switch (action)
{
case Action.Login:
routeData.Values.Add("controller", controllerName);
routeData.Values.Add("action", loginActionName);
break;
case Action.Link:
routeData.Values.Add("controller", controllerName);
routeData.Values.Add("action", linkActionName);
break;
case Action.Logoff:
routeData.Values.Add("controller", controllerName);
routeData.Values.Add("action", logoffActionName);
break;
default:
throw new NotSupportedException(string.Format("unknonw action value '{0}'.", action));
}
return routeData;
}
/// <summary>
/// Saves userId to the items collection inside <see cref="HttpContext"/>.
/// </summary>
public static void SaveUserIdToContext(this WindowsLoginHandler handler, string userId)
{
if (handler.Context.Items.Contains(userIdKey))
throw new ApplicationException("Id already exists in context.");
handler.Context.Items.Add("windows.userId", userId);
}
/// <summary>
/// Reads userId from item collection inside <see cref="HttpContext"/>.
/// </summary>
/// <remarks>The item will removed before this method returns</remarks>
/// <param name="context"></param>
/// <returns></returns>
public static int ReadUserId(this HttpContextBase context)
{
if (!context.Items.Contains(userIdKey))
throw new ApplicationException("Id not found in context.");
int userId = Convert.ToInt32(context.Items[userIdKey] as string);
context.Items.Remove(userIdKey);
return userId;
}
/// <summary>
/// Returns true if the session contains an entry for userId.
/// </summary>
public static bool SessionHasUserId(this WindowsLoginHandler handler)
{
return handler.Context.Session[userIdKey] != null;
}
/// <summary>
/// Save a session-state value with the specified userId.
/// </summary>
public static void SaveUserIdToSession(this WindowsLoginHandler handler, string userId)
{
if (handler.SessionHasUserId())
throw new ApplicationException("Id already exists in session.");
handler.Context.Session[userIdKey] = userId;
}
/// <summary>
/// Reads userId value from session-state.
/// </summary>
/// <remarks>The session-state value removed before this method returns.</remarks>
/// <param name="session"></param>
/// <returns></returns>
public static string ReadUserIdFromSession(this WindowsLoginHandler handler)
{
string userId = handler.Context.Session[userIdKey] as string;
if (string.IsNullOrEmpty(userIdKey))
throw new ApplicationException("Id not found in session.");
handler.Context.Session.Remove(userIdKey);
return userId;
}
/// <summary>
/// Creates a form for windows login, simulating external login providers.
/// </summary>
/// <param name="htmlHelper"></param>
/// <param name="htmlAttributes"></param>
/// <returns></returns>
public static MvcForm BeginWindowsAuthForm(this HtmlHelper htmlHelper, object htmlAttributes)
{
return htmlHelper.BeginForm("Login", "Windows", FormMethod.Post, htmlAttributes);
}
/// <summary>
/// Creates a form for windows login, simulating external login providers.
/// </summary>
/// <param name="htmlHelper"></param>
/// <param name="htmlAttributes"></param>
/// <returns></returns>
public static MvcForm BeginWindowsAuthForm(this HtmlHelper htmlHelper, object routeValues, object htmlAttributes)
{
return htmlHelper.BeginForm("Login", "Windows", FormMethod.Post, htmlAttributes);
}
}
}
Note
You need to have AccountController.cs as partial.
AccountController.Windows.cs
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using Microsoft.AspNet.Identity;
using MixedAuth;
namespace EmployeePortal.Web.Controllers
{
[Authorize]
public partial class AccountController : BaseController
{
//
// POST: /Account/WindowsLogin
[AllowAnonymous]
[AcceptVerbs(HttpVerbs.Get | HttpVerbs.Post)]
public ActionResult WindowsLogin(string userName, string returnUrl)
{
if (!Request.LogonUserIdentity.IsAuthenticated)
{
return RedirectToAction("Login");
}
var loginInfo = GetWindowsLoginInfo();
// Sign in the user with this external login provider if the user already has a login
var user = UserManager.Find(loginInfo);
if (user != null)
{
SignIn(user, isPersistent: false);
return RedirectToLocal(returnUrl);
}
else
{
return RedirectToAction("Login", new RouteValueDictionary(new { controller = "Account", action = "Login", returnUrl = returnUrl }));
}
}
//
// POST: /Account/WindowsLogOff
[HttpPost]
[ValidateAntiForgeryToken]
public void WindowsLogOff()
{
AuthenticationManager.SignOut();
}
//
// POST: /Account/LinkWindowsLogin
[AllowAnonymous]
[HttpPost]
public async Task<ActionResult> LinkWindowsLogin()
{
int userId = HttpContext.ReadUserId();
//didn't get here through handler
if (userId <= 0)
return RedirectToAction("Login");
HttpContext.Items.Remove("windows.userId");
//not authenticated.
var loginInfo = GetWindowsLoginInfo();
if (loginInfo == null)
return RedirectToAction("Manage");
//add linked login
var result = await UserManager.AddLoginAsync(userId, loginInfo);
//sign the user back in.
var user = await UserManager.FindByIdAsync(userId);
if (user != null)
await SignInAsync(user, false);
if (result.Succeeded)
return RedirectToAction("Manage");
return RedirectToAction("Manage", new { Message = ManageMessageId.Error });
}
#region helpers
private UserLoginInfo GetWindowsLoginInfo()
{
if (!Request.LogonUserIdentity.IsAuthenticated)
return null;
return new UserLoginInfo("Windows", Request.LogonUserIdentity.User.ToString());
}
#endregion
}
public class WindowsLoginConfirmationViewModel
{
[Required]
[Display(Name = "User name")]
public string UserName { get; set; }
}
}
Then, you need to add the handler:
<add name="Windows Login Handler" path="Login" verb="GET,POST" type="MixedAuth.WindowsLoginHandler" preCondition="integratedMode" />
Startup.cs
app.CreatePerOwinContext(dbEmployeePortal.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
PathString path = new PathString("/Account/Login");
if (GlobalExtensions.WindowsAuthActive)
path = new PathString("/Windows/Login");
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
//LoginPath = new PathString("/Account/Login")
LoginPath = path
});
// Use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
Then you need to configure Local IIS to use WindowsAuthentication and AnonymousAuthentication. You can do this in the Authentication Module.
Note If you don't have WindowsAuthentication go to Control Panel then Programs and Features then "Turn Windows features on or off":
select "Internet Information Services" > "World Wide Web" > "Security"
and select Windows Authentication.

I didn't see this in your answer, so for anyone looking on how to capture Windows Auth in your Owin pipeline, you also can add the following to your ConfigureAuth method:
public void ConfigureAuth(IAppBuilder app)
{
HttpListener listener = (HttpListener)app.Properties["System.Net.HttpListener"];
listener.AuthenticationSchemes = AuthenticationSchemes.IntegratedWindowsAuthentication;
}

Related

Accessing Cookies from static class in ASP.net Core

I'm Trying to access Cookies from Static Class in Asp.net Core , So far I couldn't find any useful topics on Google. any Suggestions?
I already Did that in asp.net:
public static string GetString(string Key)
{
var format = System.Web.HttpContext.Current.Request.Cookies["Language"];
if (format == null)
format = new HttpCookie("Language") { Value = "Fa" };
ResourceManager rm = new ResourceManager("Ippv.Virtual.Application.Properties." + format.Value,
Assembly.GetExecutingAssembly());
return rm.GetString(Key);
}
How can I write equivalent of this code in asp.net core?
In ASP.NET, we can access cookies using httpcontext.current but in ASP.NET Core, there is no htttpcontext.currently. In ASP.NET Core, everything is decoupled and modular.Httpcontext is accessible from Request object and the IHttpContextAccessor interface which is under "Microsoft.AspNetCore.Http" namespace and this is available anywhere in the application. Refer to the following code:
public class TestController : Controller
{
private readonly IHttpContextAccessor _httpContextAccessor;
public TestController(IHttpContextAccessor httpContextAccessor)
{
this._httpContextAccessor = httpContextAccessor;
}
public IActionResult Index()
{
//read cookie from IHttpContextAccessor
string cookieValueFromContext = _httpContextAccessor.HttpContext.Request.Cookies["key"];
//read cookie from Request object
string cookieValueFromReq = Request.Cookies["Key"];
//set the key value in Cookie
Set("kay", "Hello from cookie", 10);
//Delete the cookie object
Remove("Key");
return View();
}
/// <summary>
/// Get the cookie
/// </summary>
/// <param name="key">Key </param>
/// <returns>string value</returns>
public string Get(string key)
{
return Request.Cookies[key];
}
/// <summary>
/// set the cookie
/// </summary>
/// <param name="key">key (unique indentifier)</param>
/// <param name="value">value to store in cookie object</param>
/// <param name="expireTime">expiration time</param>
public void Set(string key, string value, int? expireTime)
{
CookieOptions option = new CookieOptions();
if (expireTime.HasValue)
option.Expires = DateTime.Now.AddMinutes(expireTime.Value);
else
option.Expires = DateTime.Now.AddMilliseconds(10);
Response.Cookies.Append(key, value, option);
}
/// <summary>
/// Delete the key
/// </summary>
/// <param name="key">Key</param>
public void Remove(string key)
{
Response.Cookies.Delete(key);
}

OpenIddict migration from v2 to v3 rc1 - Login success, Authorize Failed

I have tried to migration my project from v2 to v3 and I think i have updated everything, but for some reason i can login fine and i get an access_token returned, but this access_token fails to authorize a request. Not sure why, but if someone can see anything obvious that would be great. My application is a web Api with a angular2 web application for the client.
In v2 i was using JWT, but i understand that this is the default in v3 so i may have some code that needs to removed for this to work.
To restrict a controllers methods or a single method, i am using [Authorize] attribute.
Extension method to be called in Startup.cs for adding Authentication, done to keep Startup.cs file from getting to big and keeps things in one place
/// <summary>
/// Add authentication
/// </summary>
/// <param name="services"></param>
/// <param name="appSettings"></param>
public static void AddAuthentication(this IServiceCollection services, AppSettings appSettings)
{
// appSettings.ApiUrl = "http://localhost:5000"
// appSettings.WebsiteUrl = "http://localhost:4200"
// the default value for AllowuserNameCharacters is "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._#+"
// here we have just added some additional characters
services.AddIdentity<User, Role>(options => { options.User.AllowedUserNameCharacters += "'&"; })
.AddDefaultTokenProviders();
// 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;
});
// return unauthorized message instead of url
services.ConfigureApplicationCookie(options =>
{
options.Events.OnRedirectToLogin = context =>
{
context.Response.StatusCode = 401;
return System.Threading.Tasks.Task.CompletedTask;
};
});
// configure all tokens generated from aspnet to expire in 3 days (create password, forget password etc)
services.Configure<DataProtectionTokenProviderOptions>(options => options.TokenLifespan = TimeSpan.FromDays(3));
// Authentication
var authenticationBuilder = services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(cfg =>
{
cfg.Authority = appSettings.ApiUrl;
cfg.Audience = appSettings.ApiUrl;
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
});
// OpenIddict
services.AddOpenIddict()
.AddCore(options =>
{
options.UseEntityFramework(e => e.UseDbContext<ApplicationDbContext>());
})
.AddServer(options =>
{
// For token lifetimes look at the below link
// https://github.com/openiddict/openiddict-core/wiki/Configuration-and-options
// Enable the authorization, logout, token endpoints.
options.SetAuthorizationEndpointUris("/connect/authorize")
.SetLogoutEndpointUris("/connect/logout")
.SetTokenEndpointUris("/connect/token");
// Note: the Mvc.Client sample only uses the code flow and the password flow, but you
// can enable the other flows if you need to support implicit or client credentials.
options.AllowPasswordFlow()
.AllowRefreshTokenFlow();
// Mark the "email", "profile" and "roles" scopes as supported scopes.
options.RegisterScopes(Scopes.Email,
Scopes.Profile,
Scopes.OpenId,
Scopes.OfflineAccess,
Scopes.Roles);
// code to allow requests without client_id
options.AcceptAnonymousClients();
options.AddDevelopmentEncryptionCertificate()
.AddDevelopmentSigningCertificate();
// Register the ASP.NET Core MVC binder used by OpenIddict.
// Note: if you don't call this method, you won't be able to
// bind OpenIdConnectRequest or OpenIdConnectResponse parameters.
options.UseAspNetCore()
.EnableAuthorizationEndpointPassthrough()
.EnableLogoutEndpointPassthrough()
.EnableTokenEndpointPassthrough()
.DisableTransportSecurityRequirement(); // Never use https because we use load balancer
})
.AddValidation(options =>
{
// Import the configuration from the local OpenIddict server instance.
options.UseLocalServer();
// Register the ASP.NET Core host.
options.UseAspNetCore();
});
}
Startup.cs
/// <summary>
/// Configure services
/// </summary>
/// <param name="services"></param>
public void ConfigureServices(IServiceCollection services)
{
// application settings
var appSettings = new AppSettings();
Configuration.Bind(appSettings);
// mvc
services.AddMvc(appSettings);
// identity / saml etc
services.AddAuthentication(appSettings);
}
/// <summary>
/// Configure application
/// </summary>
/// <param name="applicationBuilder"></param>
/// <param name="webHostEnvironment"></param>
/// <param name="loggerFactory"></param>
public void Configure(IApplicationBuilder applicationBuilder, IWebHostEnvironment webHostEnvironment, ILoggerFactory loggerFactory)
{
// get current appSettings and output to logger
var appSettings = applicationBuilder.ApplicationServices.GetRequiredService<AppSettings>();
var logger = loggerFactory.CreateLogger(appSettings.ApiUrl);
logger.LogInformation(string.Format("{0}\n{1}", appSettings.Environment, appSettings.Data.DefaultConnection.ConnectionString));
// environment
webHostEnvironment.EnvironmentName = appSettings.Environment;
// CORS
applicationBuilder.UseCors("Default");
// static files in root
applicationBuilder.UseStaticFiles();
// routing
applicationBuilder.UseRouting();
// comment this out and you get an error saying
// InvalidOperationException: No authentication handler is configured to handle the scheme: Microsoft.AspNet.Identity.External
applicationBuilder.UseAuthentication();
// for authorization headers
applicationBuilder.UseAuthorization();
// response caching
applicationBuilder.UseResponseCaching();
// routes
applicationBuilder.UseEndpoints(options =>
{
// default goes to Home, and angular will deal with client side routing
options.MapControllerRoute(
name: "default",
pattern: "{*url}",
defaults: new { controller = "home", action = "index" });
});
}
I have updated my AuthorizationController.cs as well
/// <summary>
/// Handles authorization requests
/// </summary>
[ApiExplorerSettings(IgnoreApi = true)]
public class AuthorizationController : Controller
{
private IOptions<IdentityOptions> _identityOptions;
private OpenIddictApplicationManager<OpenIddictEntityFrameworkApplication> _applicationManager;
private SignInManager<MyUser> _signInManager;
private UserManager<MyUser> _userManager;
private AppSettings _appSettings;
private IEncryptionService _encryptionService;
/// <summary>
/// Create a new authorization controller
/// </summary>
/// <param name="identityOptions"></param>
/// <param name="applicationManager"></param>
/// <param name="signInManager"></param>
/// <param name="userManager"></param>
/// <param name="appSettings"></param>
/// <param name="encryptionService"></param>
public AuthorizationController(
IOptions<IdentityOptions> identityOptions,
OpenIddictApplicationManager<OpenIddictEntityFrameworkApplication> applicationManager,
SignInManager<MyUser> signInManager, UserManager<MyUser> userManager,
AppSettings appSettings, IEncryptionService encryptionService)
{
_identityOptions = identityOptions;
_applicationManager = applicationManager;
_signInManager = signInManager;
_userManager = userManager;
_appSettings = appSettings;
_encryptionService = encryptionService;
}
// Note: to support interactive flows like the code flow,
// you must provide your own authorization endpoint action:
/// <summary>
/// Authorize an openId request
/// </summary>
/// <param name="connectRequest"></param>
/// <returns></returns>
[Authorize, HttpGet, Route("~/connect/authorize")]
public async Task<IActionResult> Authorize()
{
var request = HttpContext.GetOpenIddictServerRequest() ??
throw new InvalidOperationException("The OpenID Connect request cannot be retrieved.");
// Retrieve the application details from the database.
var application = await _applicationManager.FindByClientIdAsync(request.ClientId, new System.Threading.CancellationToken());
if (application == null)
{
return Forbid(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: new AuthenticationProperties(
new Dictionary<string, string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidClient,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "Details concerning the calling client application cannot be found in the database"
}
)
);
}
// Flow the request_id to allow OpenIddict to restore
// the original authorization request from the cache.
return View(new AuthorizeViewModel
{
ApplicationName = application.DisplayName,
RequestId = request.RequestId,
Scope = request.Scope
});
}
/// <summary>
/// Accept an openId request
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
[Authorize, HttpPost("~/connect/authorize/accept"), ValidateAntiForgeryToken]
public async Task<IActionResult> Accept()
{
var request = HttpContext.GetOpenIddictServerRequest() ??
throw new InvalidOperationException("The OpenID Connect request cannot be retrieved.");
// Retrieve the profile of the logged in user
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return Forbid(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: new AuthenticationProperties(
new Dictionary<string, string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.ServerError,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "An internal error has occurred"
}
)
);
}
// create a new principal
var principal = await CreatePrincipalAsync(request, user);
// returning a SignInResult will ask OpenIddict to issue the appropriate access/identity tokens.
return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
/// <summary>
/// Deny an openId request
/// </summary>
/// <returns></returns>
[Authorize, HttpPost("~/connect/authorize/deny"), ValidateAntiForgeryToken]
public IActionResult Deny()
{
// Notify OpenIddict that the authorization grant has been denied by the resource owner
// to redirect the user agent to the client application using the appropriate response_mode.
return Forbid(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
/// <summary>
/// Logout
/// </summary>
/// <returns></returns>
[HttpPost("~/connect/logout")]
public async Task<IActionResult> Logout()
{
// Ask ASP.NET Core Identity to delete the local and external cookies created
// when the user agent is redirected from the external identity provider
// after a successful authentication flow (e.g Google or Facebook).
await _signInManager.SignOutAsync();
// Returning a SignOutResult will ask OpenIddict to redirect the user agent
// to the post_logout_redirect_uri specified by the client application.
return SignOut(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
// Note: to support non-interactive flows like password,
// you must provide your own token endpoint action:
/// <summary>
/// Exchange request for valid openId token
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
[HttpPost("~/connect/token")]
[Produces("application/json")]
public async Task<IActionResult> Exchange()
{
var request = HttpContext.GetOpenIddictServerRequest() ??
throw new InvalidOperationException("The OpenID Connect request cannot be retrieved.");
if (request.IsPasswordGrantType())
{
var user = await _userManager.FindByNameAsync(request.Username);
if (user == null)
{
return Forbid(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: new AuthenticationProperties(
new Dictionary<string, string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The username/password couple is invalid"
}
)
);
}
// Ensure the password is valid.
if (!await _userManager.CheckPasswordAsync(user, request.Password))
{
if (_userManager.SupportsUserLockout)
{
await _userManager.AccessFailedAsync(user);
}
return Forbid(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: new AuthenticationProperties(
new Dictionary<string, string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The username/password couple is invalid"
}
)
);
}
if (_userManager.SupportsUserLockout)
{
await _userManager.ResetAccessFailedCountAsync(user);
}
// create a new principal
var principal = await CreatePrincipalAsync(request, user);
return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
else if (request.IsRefreshTokenGrantType())
{
// Retrieve the claims principal stored in the authorization code/refresh token.
var info = await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
// Retrieve the user profile corresponding to the refresh token
var user = await _userManager.GetUserAsync(info.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 refresh 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"
}
)
);
}
// create a new principal
var principal = await CreatePrincipalAsync(request, user);
return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
throw new NotImplementedException("The specified grant type is not implemented");
}
/// <summary>
/// Creates a principal based on the openId request
/// </summary>
/// <param name="request"></param>
/// <param name="user"></param>
/// <param name="properties"></param>
/// <returns></returns>
private async Task<ClaimsPrincipal> CreatePrincipalAsync(OpenIddictRequest request, MyUser user, AuthenticationProperties properties = null)
{
// Create a new ClaimsPrincipal containing the claims that
// will be used to create an id_token, a token or a code.
var principal = await _signInManager.CreateUserPrincipalAsync(user);
if (!request.IsRefreshTokenGrantType())
{
// Set the list of scopes granted to the client application.
// Note: the offline_access scope must be granted
// to allow OpenIddict to return a refresh token.
principal.SetScopes(new[]
{
Scopes.OpenId,
Scopes.Email,
Scopes.Profile,
Scopes.OfflineAccess,
Scopes.Roles
}.Intersect(request.GetScopes()));
}
// Set resource
principal.SetResources(new string[] { _appSettings.ApiUrl });
// 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.
foreach (var claim in principal.Claims)
{
claim.SetDestinations(GetDestinations(claim, principal));
}
return principal;
}
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;
}
}
}
Also here is my applicationDbContext
/// <summary>
/// Application context to hold our openIddict entities
/// </summary>
public class ApplicationDbContext : DbContext, IApplicationDbContext
{
/// <summary>
/// Create context with connection string
/// </summary>
/// <param name="connectionString"></param>
public ApplicationDbContext(string connectionString) : base(connectionString)
{
}
/// <summary>
/// When creating models
/// </summary>
/// <param name="modelBuilder"></param>
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.UseOpenIddict();
}
/// <summary>
/// Authorizations
/// </summary>
public virtual DbSet<OpenIddictEntityFrameworkAuthorization> Authorization { get; set; }
/// <summary>
/// Applications
/// </summary>
public virtual DbSet<OpenIddictEntityFrameworkApplication> Application { get; set; }
/// <summary>
/// Tokens
/// </summary>
public virtual DbSet<OpenIddictEntityFrameworkToken> Token { get; set; }
/// <summary>
/// Scopes
/// </summary>
public virtual DbSet<OpenIddictEntityFrameworkScope> Scope { get; set; }
}
As i said, i am getting a token returned, like below
{
"access_token": "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZDQkMtSFM1MTIiLCJraWQiOiI5NDVDNjFERjcyRUU1OUNDNUNFQzQ0NzJCRDE0MDg4NDEyNTAyRjM3IiwidHlwIjoiYXQrand0In0.TxmA-Lv60Lwvr5eXdu2fgG95Bx4AUoPvp5XniqSYbBgmpDrVXCUpSjnEv7tWQ5QLPjks6TAy8hGAGfgQPFB1xDM05mZyu2WDPBsoEQ2VtxuabaPhj-uK2KjnWEccjS-n-YAY7OlSDFXrmiDsFCOf3jaPGZ43gUCPyTVl_WVE_KBGVXa19RDZFf9Ger-TsHY3evYbsUchOKMco-CD2IGt6Xg6DttxFcKF3SUPwMXhn6AGdkNesGVIoVSeDd_CnM3MyHtJIbGbkRkmwpMWsbwaWGI38itUJ0XeUqn45B_GsZfaZllytuMHbuqt1GgL0gdOaCfBUxaqy7AaYsY9UKlaEw.LS4FnIOCRESdeo1c5K2_5Q.Ic8fssDScEu7lAomL8d_7JZMVho5hLphg269UIETZui7DUB2gund7YCtGq7imdnDtN-wsEoZaLHJ4UQSJrpuIMCN2pW69J9kKcx-UT1e2Ma-JNj2G5CtxwRf_bszRRsgWC1ia85LU5TPsUFvhd_wDxyUax2GKowb0FVl8EFRoFdZF40h06LaofxDzD2BFdwSYaHaDm3icVNZ0CRpYCoZq-MK_c3Fl98l57zjZl1CNscs1w0trApMDvQ7UeFcez3zelN24H6TCaXqTRP3lixMiv9Rtm3Kqkv67HEUAFD-vXIMyWQTo28oJMMAQz9zQeTp88JsfI4Pv7euUECEkwK7Fe5rbxhD4oicNNa_wmRo2vsjrpm4C7mmRKH2u3cX2CCTwahOsVHxu4kO3zliWmniW-krEAAsaW6BjoWqJiGN3ydoiHLIv3muM78jt85KzKxMIFsalpdf2F9Z8neQBglhzxZQFp9JiEecczxao6Y03WuFNX98kZh8Zr4D21CM_m634u3mf5-Gz-frVlSgnMrE9vgCG2eETI3fOnObf9pQ1XWpK7I9SE0AZvc2PciaKO4H7mKDqXuXFrzdiz6Tx9G9MMkXXbuKsuslZJ6q9wlE3xOl9mzd6shN9GvfSTQNrFoV-PS62xhnfCFKQDfYLzWF1fhQbq_RECp6IVJN960jaMAGIZ0yzIbRInIkbhdFEIbl__h9AYo6FfcZgYv4tfw-iB_unezgHCIPpbBPC0eRMiZh1wqpfwpiJ8zjvcXJXXCfSlu13KlBWD9tHVEHh6aHE258hGdFzApA-MTCkjHwMRjVgmv_-Ed-xwk0hjBpRhiZv-0kNwKI72YJVgZMX9CMpFBg2CO5z5hHSlc0pO9pc5ovuCZ_dYYRiqqcIDUJ5Wl6dDt1JCPI084C4yssC7MC4e94OtvfRtrI096qMI34qrLWi-jn7UOMnWMUunNepQLwQ07DK-ubXsS-m0xvSxPYxtE6XM6QebmvCcXj__vELGscDu8mWmvP1Y0L9SoSpWlErHvlCPpkfVeMnkdP74cKTYgpXSQcGduqcdfU86leI9oUYgnrcPwvMgUq3jwgHGWn_0d4Bo8CsChWatYWmSNKN88h3WfASSl6SyqeVbSSYvIp-0HBGxGOwODgO-YsOlOgeKmm8oSIhnELVNMVEY1uiUZpJ9DGfrUUXKMw7aIz6LK_zH_HxlMiBam0fxgAQwRyYlO0AFmuxFO62KEHFEjdDSEO9pcUP03_RNqDfAX-IAV_EoFT7CwVpOZMUvFLo78S4xkq2ss3CFbkA3J4ioud88T5SUfslnsZmY1dJYtW4HhlGF7SKVMN6GSwckz1YhyaxqlQpMbMRFA0uCbkM6u41K0-_toRQejKDX5juqFwqK8.m0bmPYOuAYYdc6WfjDWP2UneysP1G3FqwlmOzWOrTnM",
"token_type": "Bearer",
"expires_in": 3599,
"id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjcwMzJBRUJBQjc4NkNCM0ExRkI3Nzk1NjdDQTRGRjU1ODg3OTFCNDEiLCJ4NXQiOiJjREt1dXJlR3l6b2Z0M2xXZktUX1ZZaDVHMEUiLCJ0eXAiOiJKV1QifQ.eyJzdWIiOiIxIiwibmFtZSI6InBvd2VydXNlckBtZWFzdXJlMmltcHJvdmUuY29tIiwib2lfYXVfaWQiOiIxMDJjZTE0NC02NzkyLTRlYzAtOTlhNy0xZDBlMzdmNzkyZTYiLCJhdF9oYXNoIjoiV3BibExxNFdNR2dwdHdGVVVSMktRdyIsIm9pX3Rrbl9pZCI6IjQyMzBlYmJkLWUyNzItNDJlZS1iOTZiLTQ1ZGJmMTJlZWJmMiIsImV4cCI6MTYwNjQ3NzgzOSwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwLyIsImlhdCI6MTYwNjQ3NjYzOX0.WZkB63ZfZuJfigNVabPegu-B8TvMeS1DmgRQJS151XGR08Pw-fcCldb35oM7ZW9oQenj7059BAZMI1EveHWNVWOEFpabebi7TccGRoR1YKqWSNWTBDwyQgGMyehVmze_TPgsSjAJA0y1f_xtF3-ImfVx5Tzlcjg4XAmAhV3MRd-fEobdGk5540uto5hZJ7ieHrV_7U_FF4NgVT5nSw92bkFNjUokmNgMBpDWelZUEXmsb3MFGDMnQkP0oTGXeIcy0nuuIKpr1Liza_cvv1JfICQnSUKw_u3zdqSbsbXtzGg9GfunEhXf1zSF5dxfbNpr2E4bsnIxGJ-mOYxBsOQDJA",
"refresh_token": "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZDQkMtSFM1MTIiLCJraWQiOiI5NDVDNjFERjcyRUU1OUNDNUNFQzQ0NzJCRDE0MDg4NDEyNTAyRjM3IiwidHlwIjoib2lfcmVmdCtqd3QifQ.E5xUDcqMK15spCgqoM8YrOCn_32wBX_X_XsG_f1nKOR-TcDI2AKi07vX62uMmbO1bgclGSGNynEUTuPt7KATf9UHXwpytXAK3_BnLxQLz7NbYIlfrja9t5z3gCRIGOZ-5gHbHcU6RdJrDpC73_V7CXYIfkVhfWeeS30_3GtLa-BUu4Nlr9YL7K22KdR0LJQsK6SXLMKobsj6PLrrywHnClaw6dG_O_SDezC5M_eIo_ErdHFqUBwWYhUHyntijJ9ekH4A2SiY0LGaQD6QyvmUNPC7E-LtLVO78saJYAgGW-CkQbwTBF7b_vOoEr_mPrFnpytwLnIvxSQCFVjclH-vzA.RGNxh09nh1RWb6XU6jNZSg.VRwdO_5UrB7tqaMHXwt6qZN8VucSlNXnBNoBxcvisDvjWhBOlhrJxEidGQPBeiIpbrYrWQXyPfSgFrmZAlqYcF-3cXTl0W-xR4oW1ZqRf95eMs-YTA9-bR6n8P2pV7WeYky7wkwa-i3Vs9rzasYjK18LTU6v71xMNVN2K0Z2HRgTXxPg3U4HUgkVQpZp4qetyhozdOhGBV37igAHSZHFUSYg-dlstz3DtEX7IXvi7GQbM_fLKVlDLWYZOtX1yH366C9TIjZbxGP3h-qPqfKKDNP-IhD_x2IpymmiKllfhfLKb62JQzy_ci-ICXGvs0z_ZLZirmzVSEotqrcKmwe1rlqRXbymP8llIXe3AzjLonN3I_35bLeqZz1KQE93pkLOpJGAJuhJ4dc8a7wJ5kxTAs9_CCvb6UBvhRxInUyZ9PQq026RaYJvN9i--x8JxAAtH58h9Y5zgm_M4kFbEoGgZshyGZ8QSQrp4JpPrGeW9ElvkyEmrGvEpm6zmy_Y6tEF3lXFfrGJL6FPGD5_q8m9lGQ162OCmZMGg2Sy46Hne0SPv04TpM43F-m82BTzXxGRbpvOoNye7Pl9dKbdOBno1WEmaesL-R-W_8qogNszW5c-gbhQTUjcQ1J5yuVL4r29qN9xhVDPKqxPYPzInCr30m8SH41NI6WusFVJbNoqVs43wymGfQwuDfJacVCIfuT1Lx2VMdVl3nvCAGOnEwiUDufjqMaA-Nh-Row4QIwTHMGwfzxbFkQjRfwq_NjVU0LZlVDX2BgSETw2ak3KpgAGoeDlVVwmXuQpbvZ3wRHVh4a3k3_JVGf1dOiHbXVcWx984-EARKmW6gLjIhe2t5tzIJ9NQqBrdBrCvMmQXWfBiP00-YH31yDfYRF9tNZoojnkcrkOewHFUsEcFuayBzP5ySR6RgGVVE_zkEXTnBXxeiynzfJn0D3XthpRPosLJTJz91tdmvo6CxPWuaaZoUlXq_EXqFrGCb09f11Hs_mKu7T7pHMbJioTAvj1Jy8jzduMU3Rth-w0A8Md2gdeKmoCXW51lvpuyFsP4R8AdthhXGUbAk6rNJXdkaUkRyygYU8ZQWI2WCyroDnBRpEIaBtjYT1Uf4zXyTUZ3jicKe5Rjr08tmgAQd_3pNu1Wb2dBQgvcoqnwXcnLvoMTp0yk7Osae94Pqh_Oyz45kz_oS_7fR6WvZm-avBPYLmW92eQWiMG6glxPPe_Vnq9ghwjYjS4KEvyFaDqusDWmxBEDwEzLgBTF3p0R2saedTbwpG4Epcey_T18KEuklAWbCwz-fvV3ip-_wGUNe6cuAOeyuXcUHm18Le0knZt6xNS_a8cxMz9RAEpBs3a-tOWQLrVELXxnNY03NL2szkNQuDUzY9JRtTxqngxcPpHVk058dRG1rwFZqiI4-6_yL03X_fbXsIR0ItBig9surYyB_crwyH3C6OZTnGgwxLKUU9qY0LNTzR0gatrrT1l8NrorLAvODzQqhrqqClHgZkVoQXANvz3mNZWIpxjCkOgDkH-YOaaR3egnwLVMk_clo3-gC76UQ-5T-NnZ7MJTv2twFBhUKHABEJsT9a3nR7ra2CbHFzJFNvRRCPHAGOVAY-y-Ek0xZn8mvd3Fvw4wlej4EdQvlnFUcaIH4Cl0MZD9G9t2W3A96KLRZctVfGK6W1yM846DWHwSfeTj3ZPN_6bYDqbWHoXrQxe5BGi9aN8PXfL1Bu9W5jhQpgrvTRvnjhYqwN24ta9r6BbrMzBL3cszf56dG3Ko6aA8gDwUmDSjyNVhk76sdoW.1xLMuQf7NjeYjAXDL5BUxaoFSR0EECEzPh7XxkE8Hj4"
}
Here is the token being passed successfully, but Unauthorized is returned, as you can see the next call fails as well.
Any help much appreciated
I know this is old, but just post my solution in case anyone has the same issue.
Just specificy the schema for the Authorize attribute:
[Authorize(AuthenticationSchemes = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme)]

.Net Core 2.2 Cookie Authentication Problem

I am pretty new to .net core development and I've got a problem with my .net core web application regards the user authentication by cookie authentication scheme. The problem is, that even tough the cookie is created and I'm logged in properly, I am not able to automatically re-login if the user calls the login page again.
Here is my code for better understanding:
Services configuration
services.AddAuthentication()
// Add operator authentication
.AddCookie("OPERATOR_AUTH", o =>
{
o.LoginPath = "/login";
o.LogoutPath = "/logout";
o.AccessDeniedPath = "/denied";
// Block js cookie access
o.Cookie.HttpOnly = true;
// Limit cookies to https connections in production environments
o.Cookie.SecurePolicy = CookieSecurePolicy.Always;
// Only allow the cookie on the service pages
o.Cookie.SameSite = SameSiteMode.Strict;
});
services.AddTransient<ILoginService, LoginService>();
App configuration
app.UseCookiePolicy();
app.UseAuthentication();
app.Use(next => ctx =>
{
var cookieOptions = new CookieOptions
{
HttpOnly = true
};
var tokens = antiforgery.GetAndStoreTokens(ctx);
ctx.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken,cookieOptions);
return next(ctx);
});
Login Controller
/// <summary>
/// The operator login service
/// </summary>
private readonly ILoginService _loginService;
/// <summary>
/// The logger instance received by dependency
/// injection
/// </summary>
private readonly ILogger<LoginController> _logger;
/// <summary>
/// Renders the login view
/// </summary>
/// <returns></returns>
[Route("login")]
public async Task<IActionResult> Login()
{
return View(new OperatorLoginModel());
}
/// <summary>
/// Renders the login denied view
/// </summary>
/// <returns></returns>
[Route("denied")]
public IActionResult Denied()
{
return View();
}
/// <summary>
/// Performs a login
/// </summary>
/// <param name="model">The login model</param>
/// <returns>The result view</returns>
[Route("login")]
[ValidateAntiForgeryToken]
[HttpPost]
public async Task<IActionResult> Login(OperatorLoginModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var op = await _loginService.IsAuthorizedOperator(model.OperatorCode, model.OperatorPassword);
if (op != null)
{
return await SignInOperator(op);
}
return RedirectToAction("Denied", "Login");
}
/// <summary>
/// Performs a logout
/// </summary>
/// <returns>The result view redirection</returns>
[Route("logout")]
[ValidateAntiForgeryToken]
[HttpPost]
public async Task<IActionResult> Logout()
{
await HttpContext.SignOutAsync("OPERATOR_AUTH");
return RedirectToAction("Index", "Home");
}
/// <summary>
/// Performs the actual sign in by assigning the operator
/// identity to the http context
/// </summary>
/// <param name="op">The operator to sign in</param>
/// <returns>The result view redirection</returns>
private async Task<IActionResult> SignInOperator(Operator op)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier,op.Code),
new Claim(ClaimTypes.Name, op.Description)
};
var authProperties = new AuthenticationProperties()
{
AllowRefresh = true,
IsPersistent = true,
ExpiresUtc = DateTimeOffset.Now.AddMinutes(30).UtcDateTime,
IssuedUtc = DateTimeOffset.Now.UtcDateTime
};
var identity = new ClaimsIdentity(claims, "OPERATOR_AUTH");
var principal = new ClaimsPrincipal(identity);
await HttpContext.SignInAsync("OPERATOR_AUTH", principal, authProperties);
return RedirectToAction("UserInformation", "Home");
}
/// <summary>
/// Constructor of the login controller
/// </summary>
/// <param name="logger">Logger instance</param>
/// <param name="loginService">Login service</param>
public LoginController(ILogger<LoginController> logger, ILoginService loginService)
{
_logger = logger;
_loginService = loginService;
}
What happens is the following:
The operator can correctly navigate to the login view
The operator is able to login by entering his or her credentials
The operator is navigated to the user information view
The cookie is set in the browser:
Now if I log off, the cookie is deleted correctly.
If I don't log of, but refresh the page or renavigate to Home/Login I am not able to receive the User information in the HttpContext:
Also no automatic authentication is done.
Am I missing something? Anything wrong here?
The problem could be solved by adding the authorization context attribute, aswell as the anonymous context attribute to the controller method as mentioned by kirk larkin.
[Route("login")]
[AllowAnonymous]
[Authorize(AuthenticationSchemes = "OPERATOR_AUTH")]
public IActionResult Login()
{
if (HttpContext.User.Identity.IsAuthenticated)
{
return RedirectToAction("UserInformation", "Home");
}
return View(new OperatorLoginModel());
}

C# WebApi OWIN: Token code returns token, but all normal requests return http 400 "error": "unsupported_grant_type"

I have an odata/webapi app that's been updated to use OWIN auth. The code to issue tokens (/token url) works fine and returns a token, but any "normal" call to any controllers returns HTTP 400
{"error": "unsupported_grant_type"}
with no additional information.
Startup.cs:
using Microsoft.Owin;
using Microsoft.Owin.Security.OAuth;
using Owin;
using System;
using System.Web.Http;
[assembly: OwinStartup(typeof(VP4C_API.Startup))]
namespace VP4C_API
{
public class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
var OAuthOptions = new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new SimpleAuthorizationServerProvider()
};
app.UseOAuthBearerTokens(OAuthOptions);
app.UseOAuthBearerAuthentication(oAuthBearerAuthenticationOptions);
Statics.Config = new HttpConfiguration();
WebApiConfig.Register(Statics.Config);
app.UseWebApi(Statics.Config);
}
/// <summary>
/// Called at when the AppPool starts the website
/// </summary>
/// <param name="app"></param>
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
}
}
}
AuthorizationProvider which returns the token:
using System;
using System.Threading.Tasks;
using Microsoft.Owin.Security.OAuth;
using Microsoft.Owin.Security;
using System.Linq;
using System.Security.Claims;
using System.Security.Principal;
namespace VP4C_API
{
internal class SimpleAuthorizationServerProvider : IOAuthAuthorizationServerProvider
{
Task IOAuthAuthorizationServerProvider.AuthorizationEndpointResponse(OAuthAuthorizationEndpointResponseContext context)
{
throw new NotImplementedException();
}
Task IOAuthAuthorizationServerProvider.AuthorizeEndpoint(OAuthAuthorizeEndpointContext context)
{
throw new NotImplementedException();
}
Task IOAuthAuthorizationServerProvider.GrantAuthorizationCode(OAuthGrantAuthorizationCodeContext context)
{
throw new NotImplementedException();
}
/// <summary>
/// Take the ClientID and validate its one we want to talk to
/// Called after <see cref="IOAuthAuthorizationServerProvider.ValidateTokenRequest"/> />
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
Task IOAuthAuthorizationServerProvider.GrantClientCredentials(OAuthGrantClientCredentialsContext context)
{
if (context.ClientId == "App1Test")
{
var identity = new ClaimsIdentity(new GenericIdentity(
context.ClientId, OAuthDefaults.AuthenticationType),
context.Scope.Select(x => new Claim("urn:oauth:scope", x))
);
context.Validated(identity);
}
else
{
context.Rejected();
context.SetError("Unknown client id", "You must list the client id in the known clients table");
}
return Task.FromResult(0);
}
Task IOAuthAuthorizationServerProvider.GrantCustomExtension(OAuthGrantCustomExtensionContext context)
{
throw new NotImplementedException();
}
Task IOAuthAuthorizationServerProvider.GrantRefreshToken(OAuthGrantRefreshTokenContext context)
{
throw new NotImplementedException();
}
/// <summary>
/// Called after <see cref="IOAuthAuthorizationServerProvider.ValidateTokenRequest"/>
/// Check the username/password here
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
Task IOAuthAuthorizationServerProvider.GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
if (context.UserName == "test" && context.Password == "test")
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
System.Security.Principal.GenericIdentity gi = new System.Security.Principal.GenericIdentity(context.UserName, OAuthDefaults.AuthenticationType);
System.Security.Claims.ClaimsIdentity ci = new System.Security.Claims.ClaimsIdentity(gi, context.Scope.Select(x => new Claim("urn:oauth:scope", x)));
var ticket = new AuthenticationTicket(ci, null);
context.Validated(ticket);
}
else
{
context.SetError("GrantResourceOwner Error", "Username or Password is incorrect.");
context.Rejected();
}
return Task.FromResult(0);
}
/// <summary>
/// Called first to filter on the endpoint attributes (e.g. client application IP address)
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
Task IOAuthAuthorizationServerProvider.MatchEndpoint(OAuthMatchEndpointContext context)
{
context.MatchesTokenEndpoint();
return Task.FromResult(0);
}
/// <summary>
/// Called after <see cref="IOAuthAuthorizationServerProvider.GrantResourceOwnerCredentials"/>
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
Task IOAuthAuthorizationServerProvider.TokenEndpoint(OAuthTokenEndpointContext context)
{
AuthenticationProperties ap = new AuthenticationProperties();
ap.AllowRefresh = true;
ap.IssuedUtc = DateTime.UtcNow;
ap.IsPersistent = true;
context.Issue(context.Identity, ap);
return Task.FromResult(0);
}
/// <summary>
/// Called after <see cref="IOAuthAuthorizationServerProvider.TokenEndpoint"/>
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
Task IOAuthAuthorizationServerProvider.TokenEndpointResponse(OAuthTokenEndpointResponseContext context)
{
return Task.FromResult(0);
}
Task IOAuthAuthorizationServerProvider.ValidateAuthorizeRequest(OAuthValidateAuthorizeRequestContext context)
{
throw new NotImplementedException();
}
/// <summary>
/// Called after <see cref="IOAuthAuthorizationServerProvider.MatchEndpoint"/>
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
Task IOAuthAuthorizationServerProvider.ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
string clientID;
string clientPassword;
context.TryGetBasicCredentials(out clientID, out clientPassword);
context.Validated(clientID);
return Task.FromResult(0);
}
Task IOAuthAuthorizationServerProvider.ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
{
throw new NotImplementedException();
}
/// <summary>
/// Called after <see cref="IOAuthAuthorizationServerProvider.ValidateClientAuthentication"/>
///
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
Task IOAuthAuthorizationServerProvider.ValidateTokenRequest(OAuthValidateTokenRequestContext context)
{
context.Validated();
return Task.FromResult(0);
}
}
}
And a sample controller snippet for http://host/api/Verify/1
[HttpGet, AllowAnonymous, Route("{companyID}")]
public IHttpActionResult Verify(int companyID)
{
string cs = "not set";
int check = -1;
try
{
ErrorLogging.Logging.Information(null, null, Request.RequestUri, $"Verify Start company {companyID}");
Any suggestions on where to look?

Asp.net Identity + Unit of Work: Getting duplicated entries

I am using the newest version of ASP.NET Identity, using the sample project as a base template.
My changes are simple: Adding a FIRM Properyt to the application user.
Thus, the application User looks like this:
public class ApplicationUser : IdentityUser
{
/// <summary>
/// Gets or sets the firm.
/// </summary>
/// <value>
/// The firm.
/// </value>
public virtual Firm Firm { get; set; }
/// <summary>
/// Generates the user identity asynchronous.
/// </summary>
/// <param name="manager">The manager.</param>
/// <returns>A claims identity</returns>
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
//// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
//// Add custom user claims here
return userIdentity;
}
}
Also, I do have a Unit Of Work implented, which is responsible for Firm and other entities.
/// <summary>
/// This contains a Unit of Work pattern Implementation. Provides access to all Elements used in this Application.
/// </summary>
public class UnitOfWork : IDisposable, IUnitOfWork
{
/// <summary>
/// The database context
/// </summary>
private PriorityDBContext dbContext = new PriorityDBContext();
public UnitOfWork(ApplicationDbContext dbContext)
{
this.dbContext = (PriorityDBContext)ApplicationDbContext;
}
private GenericRepository<Firm> firmRepository;
public IGenericRepository<Firm> FirmRepository
{
get
{
if (this.firmRepository == null)
{
this.firmRepository = new GenericRepository<Firm>(this.dbContext);
}
return this.firmRepository;
}
}
}
Now to my problem: I have an Account Controller, which can create new Users (among other things). When a new User is created, the Firm is silently doubled. E.g. I had a Firm called "Example1" in the database before. Now I create a new User and suddenly the Database contains "Example1" two times.
As far I can tell, the root cause is that Asp.net Identity uses one DbContext, my UnitOfWork another. I am unsure why, I tried to set up Ninject in a way that the same DbContext is used in all parts. But it simply have enormous problems getting the same db Context into the UserManager and the unit of work :(
[Authorize]
public class AccountController : Controller
{
/// <summary>
/// The Unit of Work
/// </summary>
private IUnitOfWork uoW;
/// <summary>
/// The _user manager
/// </summary>
private ApplicationUserManager userManager;
public AccountController(IUnitOfWork uow)
{
this.uoW = uow;
}
/// <summary>
/// Gets the user manager.
/// </summary>
/// <value>
/// The user manager.
/// </value>
public ApplicationUserManager UserManager
{
get
{
if (this.userManager == null)
{
this.userManager = HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
}
return this.userManager;
}
private set
{
this.userManager = value;
}
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
bool success = true;
if (ModelState.IsValid)
{
var firm = this.uoW.FirmRepository.GetRaw().Where(f => f.FirmName == newUser.Firm).First();
var user = new ApplicationUser { FirstName = newUser.FirstName, LastName = newUser.LastName, Email = newUser.Email, UserName = newUser.Email, PreferredLanguage = newUser.Language, Firm = firm };
var password = Guid.NewGuid().ToString();
var result = await this.UserManager.CreateAsync(user, password);
if (result.Succeeded)
{
this.logger.Info("Created user for {0} successfully", newUser.Email);
}
}
}
}
}
}
Ninject, registering the Bindings:
private static void RegisterServices(IKernel kernel)
{
/// loading the ApplicationDbContext here because the UserManager uses it from the OwinContext and if the
/// Unit of Work defines it's own DBContext, bad things happen, e.g. duplicated objects when working with users AND our own pocos here.
kernel.Bind<IUnitOfWork>().To<UnitOfWork>().WithConstructorArgument("dbContext", ninjectContext => HttpContext.Current.GetOwinContext().Get<ApplicationDbContext>());
kernel.Bind<IGenericRepository<Firm>>().To<GenericRepository<Firm>>();
//kernel.Bind<ApplicationUserManager>().To(HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>());
}
The ApplicationUserManager:
public class ApplicationUserManager : UserManager<ApplicationUser>
{
public ApplicationUserManager(IUserStore<ApplicationUser> store)
: base(store)
{
}
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
{
var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>()));
//// Konfigurieren der Überprüfungslogik für Benutzernamen.
manager.UserValidator = new UserValidator<ApplicationUser>(manager)
{
AllowOnlyAlphanumericUserNames = false,
RequireUniqueEmail = true
};
// snipped
return manager;
}
}

Categories