Custom Auth using OWIN on Asp Mvc - c#

I am trying to create a login screen using owin on Asp MVC.
This is the code that is in the controller.
I have even tried hard coding the values but I still get a 401 when I try enter the dashboard after login.
[HttpPost]
public ActionResult Login(string username, string password)
{
int userId = 0;
string role = string.Empty;
if (new UserManager().IsValid(username, password, ref userId, ref role))
{
var ident = new ClaimsIdentity(
new[] {
// adding following 2 claim just for supporting default antiforgery provider
new Claim(ClaimTypes.NameIdentifier, userId.ToString()),
new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", "ASP.NET Identity", "http://www.w3.org/2001/XMLSchema#string"),
new Claim(ClaimTypes.Name,username),
// optionally you could add roles if any
new Claim(ClaimTypes.Role, role)
},
DefaultAuthenticationTypes.ApplicationCookie);
HttpContext.GetOwinContext().Authentication.SignIn(
new AuthenticationProperties { IsPersistent = false }, ident);
return RedirectToAction("Dashboard"); // auth succeed
}
// invalid username or password
ModelState.AddModelError("", "invalid username or password");
return View("Index", "_LayoutUnAuthorised");
}
[Authorize(Roles = "Default")]
public ActionResult Dashboard()
{
return View();
}
My startup file is empty am I missing something here
public class Startup
{
public void Configuration(IAppBuilder app)
{
}
}

Yes, you missed the Owin Cookie Configuration on Startup class:
public void Configuration(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AuthenticationType = "ApplicationCookie",
LoginPath = new PathString("/Account/LogOn"),
});
}
Install Nuget Package Microsoft.Owin.Security.Cookies then you are good to go

Related

Why does my log in info is missing after my RedirectToAction call?

In my asp.net MVC 4.6 application, after I logged in the site, I need to call RedirectToAction to call another controller, but the current log in data is lost before reaching another controller. However, the data is not lost if I run it in Anonymous authentication is enabled. But I can not make the Anonymous Authentication enabled.
Here is the log on method in my AccountController class:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult LogOn(string userName, string password, string returnUrl)
{
if (ValidateLogOn(userName, password) == false)
{
return View();
}
FormsAuthenticationService.SignIn(userName, false);
IVtrsUser currentUser = MembershipService.Get(userName);
if (currentUser.IsPasswordExpired)
{
return RedirectToAction("ChangePassword");
}
else if (string.IsNullOrEmpty(returnUrl) == false)
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Calendar");
}
}
From the debug, I can see the two services have set the current user properly:
FormsAuthenticationService.SignIn(userName, false);
IVtrsUser currentUser = MembershipService.Get(userName);
But after the code excuated to the CalendarController as below:
[Authorize(Roles = "Admin")]
public class CalendarController : Controller
{ ...
The user signed in data such as Role = "Admin" is lost, which causes the following code in CalendarController is wrong, and is not execute the Index() method of CalendarController, Can anybody help me why this user login data is lost in Anonymous Authentication is Disabled mode? and the user data is remaining when Anonymous Authentication is enabled mode?
Add more , there is related codes:
public IFormsAuthenticationService FormsAuthenticationService { get; private set; }
and this is IFormsAuthenticationService:
namespace VTRS.WebApp.Controllers.Common
{
public interface IFormsAuthenticationService
{
void SignIn(string userName, bool createPersistentCookie);
void SignOut();
}
}
please let me know what is the standard or best way to keep user log
in data, then I can modify my service or rewrite the code to keep the
user log in data.
In ASP.NET MVC, you want to use ASP.NET OWIN Cookie Middleware instead of FormAuthentication.
OwinAuthenticationService
private readonly HttpContextBase _context;
private const string AuthenticationType = "ApplicationCookie";
public OwinAuthenticationService(HttpContextBase context)
{
_context = context;
}
public void SignIn(User user)
{
IList<Claim> claims = new List<Claim>
{
new Claim(ClaimTypes.Sid, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.UserName),
new Claim(ClaimTypes.GivenName, user.FirstName),
new Claim(ClaimTypes.Surname, user.LastName),
};
ClaimsIdentity identity = new ClaimsIdentity(claims, AuthenticationType);
IOwinContext context = _context.Request.GetOwinContext();
IAuthenticationManager authenticationManager = context.Authentication;
authenticationManager.SignIn(identity);
}
public void SignOut()
{
IOwinContext context = _context.Request.GetOwinContext();
IAuthenticationManager authenticationManager = context.Authentication;
authenticationManager.SignOut(AuthenticationType);
}
Startup.cs
You also need to configure Startup for all those to happen.
[assembly: OwinStartup(typeof(YourApplication.Startup))]
namespace YourApplication
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "ApplicationCookie",
LoginPath = new PathString("/Account/Login")
});
}
}
}
Then you can start using [Authorize] attribute in Controller and Action methods.
[Authorize]
public class UsersController : Controller
{
// ...
}
Here is my sample application at GitHub which uses AD for Authentication. But the core pieces are OwinAuthenticationService.cs and Startup.cs.

Unable to access Controller action after sign in and adding Authorize attribute to the Action

I've set up a simple login page to login my user when he clicks the login button. The user gets assigned roles upon the login. To test if it works out I've done the following code for login:
[HttpPost]
[ActionName("Login")]
public ActionResult Login(LoginViewModel model)
{
if (ModelState.IsValid)
{
string userName = model.Username;
string[] userRoles = new string[5];
userRoles[0] = "Administrator";
ClaimsIdentity identity = new ClaimsIdentity(DefaultAuthenticationTypes.ApplicationCookie);
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, userName));
// userRoles.ToList().ForEach((role) => identity.AddClaim(new Claim(ClaimTypes.Role, role)));
identity.AddClaim(new Claim(ClaimTypes.Role, userRoles[0]));
identity.AddClaim(new Claim(ClaimTypes.Name, userName));
AuthenticationManager.SignIn(identity);
return RedirectToAction("Success");
}
else
{
return View("Login",model);
}
}
And I've added a Authorize attribute to my MVC action, just to see if the user will really be able to access it after the login... Here's how I've done it:
[Authorize(Roles="Administrator")]
public ActionResult Register()
{
var model = new UserRegistrationViewModel();
var countries = Connection.ctx.Countries.OrderBy(x => x.CountryName).ToList();
model.Countries = new SelectList(countries, "CountryId", "CountryName");
return View(model);
}
But for some reason when I try to access like following:
mywebsite.com/user/register
It shows me:
HTTP Error 401.0 - Unauthorized
You do not have permission to view this directory or page.
What could it be ?
Edit:
Here is the snapshot of claims and identities after the user logs in:
And 2nd one:
Could you ensure that you have Cookie middleware? For example,
Startup.cs
[assembly: OwinStartup(typeof(YourApplicationName.Startup))]
namespace YourApplicationName
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login")
});
}
}
}
Using Cookie Middleware without ASP.NET Core Identity
I'd say you are getting the 401 because your user does not have the "Administrator" role. Check your user (identity) on the subsequent request(s), I am not sure that the Roles persist in the cookie - you may need to find a way to persist the roles yourself.

mvc custom oauth server does not redirect to login page on unauthorized

I setup my own oauth authorization Server (NO Login via Facebook, Google and co). Unfortunately my server does not redirect to the login page after the Authorize-Endpoint is hit. Why doesn't the middleware redirect to my login page?
I followed some tutorials:
OWIN OAuth 2.0 Authorization Server
OAuth custom provider c# (Which is by the way the only code example i found for client login into custom oauth server. Thanks to #MatthiasRamp. All other stuff I read is about how to login with the social media Clients which is very frustrating)
MVC 5 application - implement OAuth Authorization code flow
This is what I made shortend on my oauth server side:
Startup.Auth.cs use active Cookie Authentication with Loginpath
Startup.Auth.cs use passive Cookie Authentication
Startup.Auth.cs use authorization Server with AuthorizeEndpointPath
SecurityController/Authorize calls Authentication.challenge() to change the status to 401
One point in the post from #Satish P he described:
Redirect the client to a login page
For this purpose I set the property CookieAuthenticationOptions.LoginPath which tells me
The LoginPath property informs the middleware that it should change an outgoing 401 Unauthorized status code into a 302 redirection onto the given login path.
This is my Startup.Auth.cs including LoginPath:
public void ConfigureAuth(IAppBuilder app)
{
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Application",
AuthenticationMode = AuthenticationMode.Passive,
LoginPath = new PathString("/Security/Login"),
LogoutPath = new PathString("/Security/Logout")
});
// Enable the External Sign In Cookie.
app.SetDefaultSignInAsAuthenticationType("External");
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "External",
AuthenticationMode = AuthenticationMode.Passive,
CookieName = CookieAuthenticationDefaults.CookiePrefix + "External",
ExpireTimeSpan = TimeSpan.FromMinutes(5),
});
// The UseOAuthAuthorizationServer extension method is to setup the authorization server. The setup options are: [...]
app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions
{
AuthorizeEndpointPath = new PathString("/Security/Authorize"),
TokenEndpointPath = new PathString("/Token"),
ApplicationCanDisplayErrors = true,
#if DEBUG
AllowInsecureHttp = true,
#endif
// Authorization server provider which controls the lifecycle of Authorization Server
Provider = new ApplicationOAuthProvider(PublicClientId)
});
}
I created a class which derives from OAuthAuthorizationServerProvider.
public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider
{
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
// [Check username and pw here]
var oAuthIdentity = new ClaimsIdentity(new GenericIdentity(context.UserName, OAuthDefaults.AuthenticationType), context.Scope.Select(x => new Claim("urn:oauth:scope", x)));
var cookiesIdentity = new ClaimsIdentity(new GenericIdentity(context.UserName, CookieAuthenticationDefaults.AuthenticationType), context.Scope.Select(x => new Claim("urn:oauth:scope", x)));
AuthenticationProperties properties = CreateProperties(context.UserName);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
}
public override Task TokenEndpoint(OAuthTokenEndpointContext context)
{
foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
{
context.AdditionalResponseParameters.Add(property.Key, property.Value);
}
return Task.FromResult<object>(null);
}
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
var grantType = context.Parameters.SingleOrDefault(p => p.Key == "grant_type").Value;
if (grantType != null)
{
if (grantType[0] == "authorization_code")
{
string clientId;
string clientSecret;
if (context.TryGetBasicCredentials(out clientId, out clientSecret) || context.TryGetFormCredentials(out clientId, out clientSecret))
{
if (clientId == Clients.ClientApp.Id && clientSecret == Clients.ClientApp.Secret)
{
context.Validated();
}
}
}
}
return Task.FromResult<object>(null);
}
public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
{
if (context.ClientId == Clients.ClientApp.Id)
{
context.Validated(Clients.ClientApp.RedirectUrl);
}
return Task.FromResult<object>(null);
}
public static AuthenticationProperties CreateProperties(string userName)
{
IDictionary<string, string> data = new Dictionary<string, string>
{
{ "userName", userName }
};
return new AuthenticationProperties(data);
}
}
And this is the where the redirect should happen because challenge will change the response to unauthorized (401). Instead of the redirect to the login page it wants to return the authorize view.
public ActionResult Authorize()
{
if (Response.StatusCode != 200)
{
return View("AuthorizeError");
}
var authType = DefaultAuthenticationTypes.ApplicationCookie;
var authentication = HttpContext.GetOwinContext().Authentication;
var ticket = authentication.AuthenticateAsync(authType).Result;
var identity = ticket != null ? ticket.Identity : null;
if (identity == null)
{
authentication.Challenge(authType);
}
else
{
// login stuff
}
return View();
}
I know it's too late and you might not need this anymore but just in case if anyone else also faces this issue then you need to return HttpUnauthorizedResult(); after challenge and it will rediret you to login page.
e.g
if (identity == null)
{
authentication.Challenge(authType);
return new HttpUnauthorizedResult();
}
//login stuff

UserManager.FindAsync returns null after password reset

Following the official documentation (https://github.com/rustd/AspnetIdentitySample) and NuGet package, I'm having issues with logging in after a password reset for my MVC5 application. It seems as though Entity Framework doesn't refresh its context in the process, it's only after I restart my application that I can login with the correct credentials.
As far as I can work out, I've done everything that the code samples have done as well. Only I have much more code and settings (e.g. Unity).
This is the problem area:
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
try
{
if (ModelState.IsValid)
{
ApplicationUser user = await UserManager.FindAsync(model.UserName, model.Password);
if (user != null)
{
await this.SignInAsync(user, false);
return RedirectToLocal(returnUrl);
}
else
{
model.State = ViewModelState.Error;
model.Messages = new List<string>() { "No access buddy!" };
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
catch (Exception ex)
{
throw;
}
}
private async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
ClaimsIdentity identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}
This part works perfectly when I log on for the first time. However, after I have reset my password, logging in with the new credentials isn't possible (it still takes the old version).
Here is my configuration:
public class ApplicationUserManager : UserManager<ApplicationUser>
{
#region Constructor
public ApplicationUserManager(IUserStore<ApplicationUser> store)
: base(store)
{
this.UserTokenProvider = new TotpSecurityStampBasedTokenProvider<ApplicationUser, string>();
}
#endregion Constructor
#region Methods
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
{
ApplicationUserManager manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<SecurityDbContext>()));
manager.UserValidator = new UserValidator<ApplicationUser>(manager)
{
AllowOnlyAlphanumericUserNames = false,
RequireUniqueEmail = true
};
// Configure validation logic for passwords
manager.PasswordValidator = new PasswordValidator
{
RequiredLength = 6,
RequireNonLetterOrDigit = true,
RequireDigit = true,
RequireLowercase = true,
RequireUppercase = true,
};
// Configure user lockout defaults
manager.UserLockoutEnabledByDefault = true;
manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
manager.MaxFailedAccessAttemptsBeforeLockout = 5;
// Register two factor authentication providers. This application uses Phone and Emails as a step of receiving a code for verifying the user
// You can write your own provider and plug it in here.
manager.RegisterTwoFactorProvider("Phone Code", new PhoneNumberTokenProvider<ApplicationUser>
{
MessageFormat = "Your security code is {0}"
});
manager.RegisterTwoFactorProvider("Email Code", new EmailTokenProvider<ApplicationUser>
{
Subject = "Security Code",
BodyFormat = "Your security code is {0}"
});
manager.EmailService = new EmailService();
manager.SmsService = new SmsService();
var dataProtectionProvider = options.DataProtectionProvider;
if (dataProtectionProvider != null)
{
manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));
}
return manager;
}
#endregion Methods
}
This is what I've configured during Startup:
// Configure the db context, user manager and signin manager to use a single instance per request
app.CreatePerOwinContext(SecurityDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
// Configure the sign in cookie
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions { });
Ultimately, after a few screens, here is where the user ultimately ends up to create a new password:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ResetPassword(ResetPasswordViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
ApplicationUser user = await UserManager.FindByEmailAsync(model.Email);
if (user == null)
{
// Don't reveal that the user does not exist
return RedirectToAction("ResetPasswordConfirmation", "Account");
}
IdentityResult result = await UserManager.ResetPasswordAsync(user.Id, model.Code, model.Password);
if (result.Succeeded)
{
return RedirectToAction("ResetPasswordConfirmation", "Account");
}
else
{
AddErrors(result);
return View();
}
}
No errors here either, it stores the new hashed value and security stamp in the database. I'm thinking of some caching, cookies or dbContext that isn't refreshed at the time the password is reset.
Does anyone have any ideas?
Ok so I have finally found the reason for this odd behavior. I had the following DbConfiguration:
public class Configuration : DbConfiguration
{
public Configuration()
{
CacheTransactionHandler transactionHandler = new CacheTransactionHandler(new InMemoryCache());
this.AddInterceptor(transactionHandler);
Loaded += (sender, args) =>
{
args.ReplaceService<DbProviderServices>((s, _) => new CachingProviderServices(s, transactionHandler));
};
}
}
Commenting out the callback did the trick, which sounds logical as I replaced the standard DbProviderServices with second-level caching (as provided by https://efcache.codeplex.com/)
Update:
It's not necessary to entirely remove the second-level caching. Instead, by adding a caching provider, I can choose which tables to cache (and for how long). Here is the updated code:
public class Configuration : DbConfiguration
{
public Configuration()
{
CacheTransactionHandler transactionHandler = new CacheTransactionHandler(new InMemoryCache());
this.AddInterceptor(transactionHandler);
MyCachingPolicy cachingPolicy = new MyCachingPolicy();
Loaded += (sender, args) =>
{
args.ReplaceService<DbProviderServices>((s, _) => new CachingProviderServices(s, transactionHandler, cachingPolicy));
};
}
}
internal class MyCachingPolicy : CachingPolicy
{
#region Constructor
internal MyCachingPolicy()
{
this.NonCachableTables = new List<string>()
{
"AspNetUsers",
"Resource",
"Task",
"Appointment"
};
}
#endregion Constructor
#region Properties
private List<string> NonCachableTables { get; set; }
#endregion Properties
#region Methods
#endregion Methods
protected override bool CanBeCached(ReadOnlyCollection<EntitySetBase> affectedEntitySets, string sql, IEnumerable<KeyValuePair<string, object>> parameters)
{
return !affectedEntitySets.Select(e => e.Table ?? e.Name).Any(tableName => this.NonCachableTables.Contains(tableName));
}
protected override void GetCacheableRows(ReadOnlyCollection<EntitySetBase> affectedEntitySets, out int minCacheableRows, out int maxCacheableRows)
{
base.GetCacheableRows(affectedEntitySets, out minCacheableRows, out maxCacheableRows);
}
protected override void GetExpirationTimeout(ReadOnlyCollection<EntitySetBase> affectedEntitySets, out TimeSpan slidingExpiration, out DateTimeOffset absoluteExpiration)
{
base.GetExpirationTimeout(affectedEntitySets, out slidingExpiration, out absoluteExpiration);
}
}

Authorize is not authenticating action asp.net mvc 5 with Identity

I have Global Filter as
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new AuthorizeAttribute());
}
My Signin Code is
[AllowAnonymous,HttpPost]
public async Task<ActionResult> Login(UsersViewModel usermodel)
{
if (ModelState.IsValid)
{
Mapper.CreateMap<UsersViewModel, Users>();
Users model = Mapper.Map<Users>(usermodel);
Users result = await UserManager.FindAsync(model.UserName, usermodel.Password);
if (result != null)
{
await SignInAsync(result, true);
return RedirectToAction("Success", "Home");
}
ModelState.AddModelError("Error", "Incorrect username and/or password");
}
return RedirectToAction("Index", "Home");
}
private async Task SignInAsync(Users user, bool isPersistent)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
ClaimsIdentity identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ExternalCookie);
AuthenticationManager.SignIn(new AuthenticationProperties()
{
IsPersistent = isPersistent
}, identity);
}
Now when I am signing in then its redirecting to /Home/Success. Its alright I saw the cookies its also creating the authentication cookie. However when I am trying to access /Account/AddRole which isnt marked as allowannonymous. In spite I have already authenticated . Its still redirecting me to /Account/Login . Which shouldn't happen . However my Authentication Startup Class for Owin is :
public void ConfigureAuth(IAppBuilder app)
{
app.CreatePerOwinContext(() => new RemsContext());
app.CreatePerOwinContext<RemsContext>(() => new RemsContext());
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
CookieName = "Authcookie",
CookieSecure = CookieSecureOption.Always,
AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Active
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
}
Why its not authentication working inspite it sets the authentication cookie for Asp.net ? From Identity Framework . I m currently using Identityframework beta 2.0.1 . can someone guide why authorize in global isnt working as expected in spite I have already logged in and the authentication cookie has been set ?

Categories