How do I configure an MVC-application to be OAuth-login only - oauth

I'm trying to configure an MVC 5 application to use Steam login only i.e. no user registration, etc. I'm completely new to ASP.NET MVC 5 and Owin, so I'm struggling a bit to understand what my basic flow should be.
I'm using the Owin.Security.Providers.Steam installed via NuGet.
Below I have listed my Startup.Auth.cs and AccountController. You will notice I have implemented custom UserManager and SignInManager along with a custom user store. This part works fine, but when I'm done logging the user in, User.Identity.IsAuthenticated continues to be false. To fix this I've tried to add cookie authentication to my Startup.Auth.cs, but when I do so I get the following exception in my ExternalLoginCallback:
Line 79: var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
[InvalidOperationException: Sequence contains more than one element]
System.Linq.Enumerable.SingleOrDefault(IEnumerable`1 source) +4098162
Microsoft.Owin.Security.<AuthenticateAsync>d__8.MoveNext() +358
This is my Startup.Auth.cs:
app.CreatePerOwinContext<SteamUserManager>(SteamUserManager.Create);
app.CreatePerOwinContext<SteamSignInManager>(SteamSignInManager.Create);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ExternalCookie,
LoginPath = new PathString("/Account/Login")
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseSteamAuthentication("XXXXXXXXXXXXXXXXXXXXX"); // My Steam key
AccountController.cs:
[AllowAnonymous]
public ActionResult Login(string returnUrl)
{
return ExternalLogin("Steam", returnUrl);
}
[HttpPost]
[AllowAnonymous]
public ActionResult ExternalLogin(string provider, string returnUrl)
{
return new ChallengeResult(provider, Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }));
}
[AllowAnonymous]
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
if (loginInfo == null)
{
return RedirectToAction("Login");
}
var result = await SignInManager.ExternalSignInAsync(loginInfo, isPersistent: false);
switch (result)
{
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.Failure:
default:
// If the user does not have an account, then prompt the user to create an account
return await CreateFirstTimeUser(loginInfo, returnUrl);
}
}
}

The answer was pretty straight-forward. AuthenticationType on the CookieAuthenticationOptions should be ApplicationCookie and not ExternalCookie.
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login")
});

Related

This Page isn't working Localhost redirected too many times MVC

Program.cs File with Cookie authentication:
builder.Services.AddAuthentication("CookieAuthentication").AddCookie("CookieAuthentication", options =>
{
options.LoginPath = "/<Login/LoginView";
options.AccessDeniedPath = "/Login/AccessDenied";
});
// Configure the HTTP request pipeline.
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Login}/{action=Signup}/{id?}");
app.Run();
Login Controller:
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult LoginView(string username, string password)
{
if (!ModelState.IsValid)
{
//Error code here
}
if (!UserExists(username, password))//Check if user exists in Database
{
//Error code here
}
TempData["Username"] = username;
return RedirectToAction("Index", "Home");
//I used breakpoint here and this code runs but doesn't work properly.
}
I have also used the [Authorize] attribute on Home Controller to prevent users from accessing it without login.Login/LoginView is the Login rage Path.
This Page isn't working Localhost redirected too many times MVC
For your current scenario,be sure add the [AllowAnonymous] on your Index action in the HomeController. And your LoginPath is /Home/Index, it is no need to be authorized.
[Authorize]
public class HomeController : Controller
{
[AllowAnonymous]
public async Task<IActionResult> Index()
{
//do your stuff...
return View();
}
//....
}
Update:
Cookie authentication to login
Program.cs:
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options =>
{
options.LoginPath = "/Login/LoginView";
options.AccessDeniedPath = "/Login/AccessDenied";
});
var app = builder. Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthentication(); //be sure add authentication and authorization middleware.....
app.UseAuthorization();
//...
How to sigh in the user:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> LoginView(string username, string password)
{
if (!ModelState.IsValid)
{
//Error code here
}
if (!UserExists(username, password))//Check if user exists in Database
{
//Error code here
}
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier,username) //add the claims you want...
};
//authProperties you can choose any option you want, below is a sample...
var authProperties = new AuthenticationProperties
{
//IssuedUtc = DateTimeOffset.UtcNow,
//ExpiresUtc = DateTimeOffset.UtcNow.AddHours(1),
//IsPersistent = false
};
var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity), authProperties);
TempData["Username"] = username;
return RedirectToAction("Index", "Home");
}

How to get Owin identity in Signalr hub

I have an application written in ASP.NET MVC using SignalR with Owin external authentication (steam).
Problem is that I can't obtain any identity information inside SignalR hub. Identity.Name returns empty string, Identity.Claims is empty.
Startup.cs
public class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Home/Index"),
AuthenticationMode = AuthenticationMode.Active
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
app.UseSteamAuthentication("API KEY");
}
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
app.MapSignalR();
}
}
In SteamCallBack
authenticateResult?.Identity.Claims
is not empty, it returns correct Identity.Name provided by Steam.
public async Task<ActionResult> SteamCallback()
{
....
var authenticateResult =
await HttpContext.GetOwinContext().Authentication.AuthenticateAsync("ExternalCookie");
var firstOrDefault = authenticateResult?.Identity.Claims.FirstOrDefault(claim => claim.Issuer == "Steam" && claim.Type.Contains("nameidentifier"));
...
}
Inside Hub all of the following are null/empty
var z = Context.User.Identity.Name;
var b = Context.User.Identity.AuthenticationType;
var x = ((ClaimsIdentity)Context.User.Identity).Claims.ToList();
I solve my problem. I forgot to sign in user using IAuthenticationManager.SignIn method.
Example use of that method:
var identity = new ClaimsIdentity(claims, DefaultAuthenticationTypes.ApplicationCookie);
HttpContext.GetOwinContext().Authentication.SignIn(new AuthenticationProperties()
{
AllowRefresh = true,
IsPersistent = true,
ExpiresUtc = DateTime.UtcNow.AddDays(7)
}, identity);

authenticate both mvc controller and api controller from one login entry

I use Web API2 and MVC5 in the same project with Asp.net Identity 2 for authentication and authorization, for Web APIs I use AngularJs as front end framework,
Now I need to make one login entry for both controllers, MVC controllers and Apicontrollers
this code for my configuration function
public void Configure(IAppBuilder app)
{
// Configure the db context, user manager and signin manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
app.CreatePerOwinContext(TemplateEntities.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,
ExpireTimeSpan = TimeSpan.FromMinutes(5),
LoginPath = new PathString("/Home/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, DefaultAuthenticationTypes.ApplicationCookie))
}
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process.
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
// Enables the application to remember the second login verification factor such as phone or email.
// Once you check this option, your second step of verification during the login process will be remembered on the device where you logged in from.
// This is similar to the RememberMe option when you log in.
app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
// Configure the application for OAuth based flow
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true
};
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
// Uncomment the following lines to enable logging in with third party login providers
//app.UseMicrosoftAccountAuthentication(
// clientId: "",
// clientSecret: "");
//app.UseTwitterAuthentication(
// consumerKey: "",
// consumerSecret: "");
//app.UseFacebookAuthentication(
// appId: "",
// appSecret: "");
//app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions()
//{
// ClientId = "",
// ClientSecret = ""
//});
}
}
and this my provider code
public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider
{
private readonly string _publicClientId;
public ApplicationOAuthProvider(string publicClientId)
{
if (publicClientId == null)
{
throw new ArgumentNullException("publicClientId");
}
_publicClientId = publicClientId;
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
var signInManager = context.OwinContext.Get<ApplicationSignInManager>();
ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);
//userManager.lo
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager,
OAuthDefaults.AuthenticationType);
ClaimsIdentity cookiesIdentity = await user.GenerateUserIdentityAsync(userManager,
CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = CreateProperties(user.UserName);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
var result = await signInManager.PasswordSignInAsync(context.UserName, context.Password, true, shouldLockout: false);
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)
{
// Resource owner password credentials does not provide a client ID.
if (context.ClientId == null)
{
context.Validated();
}
return Task.FromResult<object>(null);
}
public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
{
if (context.ClientId == _publicClientId)
{
Uri expectedRootUri = new Uri(context.Request.Uri, "/");
if (expectedRootUri.AbsoluteUri == context.RedirectUri)
{
context.Validated();
}
}
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);
}
}
I used this line to authorize MVC Controllers
var result = await signInManager.PasswordSignInAsync(context.UserName, context.Password, true, shouldLockout: false);
and this to set token cookie for APIs
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
in my GrantResourceOwnerCredentials function in previous provider code
Now the problem is APIs run perfectly with authenticated user
but when decorating MVC Controller action with [authorize] attribute it doesn't run in spite of this line var result = await signInManager.PasswordSignInAsync(context.UserName, context.Password, true, shouldLockout: false); runs successfully

Authenticate MVC Application with UseCookieAuthentication and an exisiting Web API OAuth Application

I have created my own web api OAuth authentication server with my customized Microsoft.Owin implementation:
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AuthorizeEndpointPath = new PathString("/Auth/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true
};
app.UseOAuthBearerTokens(OAuthOptions);
Now I want to use that OAuth authentication in an external mvc application but I've been really confused.
In fact my main concern is authorizing controllers and actions based on my existing OAuth server.
After 2 days research finally I implemented that based on a custom OAuthBearerAuthenticationProvider which is worked based on a cookie created during sign in process. The functionality works correctly But I think (actually I know) something is wrong.
this is my Custom OAuthBearerAuthenticationProvider :
public class ApplicationOAuthBearerAuthenticationProvider : OAuthBearerAuthenticationProvider
{
public override Task RequestToken(OAuthRequestTokenContext context)
{
if (context == null)
throw new ArgumentNullException("context");
var tokenCookie = context.OwinContext.Request.Cookies["BearerToken"];
if (!string.IsNullOrEmpty(tokenCookie))
{
context.Token = tokenCookie;
}
return Task.FromResult<object>(null);
}
}
and this is my MVC auth startup configure method:
public void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Auth/SignIn")
});
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
Provider = new ApplicationOAuthBearerAuthenticationProvider(),
});
}
If someone has related experience please advise me.
UPDATE: I think finally I found a solution.
In my solution during sign in process I create a ticket and protect that inside OAuthBearerOptions which is defined inside Startup.Auth class.
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> SignIn(LoginPageModel pageModel, string returnUrl)
{
if (!ModelState.IsValid)
{
return RedirectToAction("SignIn", new { returnUrl = returnUrl });
}
try
{
var result = await AuthService.Instance.AuthenticateAsync(pageModel.LoginModel);
CreateIdentity(result);
return RedirectToLocal(returnUrl);
}
catch (Exception ex)
{
return RedirectToAction("SignIn", new { returnUrl = returnUrl });
}
}
private void CreateIdentity(TokenResponseModel result)
{
IDictionary< String, String> data = new Dictionary< String, String>
{
{ "userName", result.Username }
};
var claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.Name, result.Username));
claims.Add(new Claim(ClaimTypes.Email, result.Username));
if (!String.IsNullOrEmpty(result.ExternalIdentity))
{
claims.Add(new Claim(CustomClaimTypes.ExternalIdentity, result.ExternalIdentity));
}
if (result.Roles != null && result.Roles.Length != 0)
{
foreach (var role in result.Roles)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}
}
ClaimsIdentity oAuthIdentity = new ClaimsIdentity(claims, OAuthDefaults.AuthenticationType);
ClaimsIdentity cookiesIdentity = new ClaimsIdentity(claims, DefaultAuthenticationTypes.ApplicationCookie);
AuthenticationProperties properties = new AuthenticationProperties(data);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
Startup.OAuthBearerOptions.AccessTokenFormat.Protect(ticket);
AuthenticationManager.SignIn(cookiesIdentity);
}
And the Auth Configure is changed to this:
public void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Auth/SignIn"),
});
OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
app.UseOAuthBearerAuthentication(OAuthBearerOptions);
}

ASP.net MVC Authentication Cookie is dismissed after closing browser [duplicate]

I am using asp.net identity 2.0 to manage user logins.
I am following the sample for Identity 2.0 and cannot get the cookie to persist after the whole browser is closed. This is happening on all browsers.
Code:
Account Controller
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
var result = await SignInHelper.PasswordSignIn(model.Email, model.Password, isPersistent: true, shouldLockout: true);
switch (result)
{
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
}
SignInHelper
public async Task<SignInStatus> PasswordSignIn(string userName, string password, bool isPersistent, bool shouldLockout)
{
var user = await UserManager.FindByNameAsync(userName);
if (user == null)
{
return SignInStatus.Failure;
}
if (await UserManager.IsLockedOutAsync(user.ID))
{
return SignInStatus.LockedOut;
}
if (await UserManager.CheckPasswordAsync(user, password))
{
// password verified, proceed to login
return await SignIn(user, isPersistent);
}
if (shouldLockout)
{
await UserManager.AccessFailedAsync(user.ID);
if (await UserManager.IsLockedOutAsync(user.ID))
{
return SignInStatus.LockedOut;
}
}
return SignInStatus.Failure;
}
-
private async Task<SignInStatus> SignIn(User user, bool isPersistent)
{
await SignInAsync(user, isPersistent);
return SignInStatus.Success;
}
-
public async Task SignInAsync(User user, bool isPersistent)
{
var userIdentity = await user.GenerateUserIdentityAsync(UserManager);
AuthenticationManager.SignIn(
new AuthenticationProperties
{
IsPersistent = isPersistent
},
userIdentity
);
}
Startup.Auth
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
CookieName = "ApplicationCookie",
LoginPath = new PathString("/Account/Login"),
ExpireTimeSpan = System.TimeSpan.FromMinutes(180), // 3 hours
SlidingExpiration = true,
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = ApplicationCookieIdentityValidator.OnValidateIdentity(
validateInterval: TimeSpan.FromMinutes(0),
regenerateIdentityCallback: (manager, user) => user.GenerateUserIdentityAsync(manager),
getUserIdCallback: (user) => (user.GetGuidUserId()))
}
});
Sorry for the wall of code, but I can't see what I am doing wrong, that the cookie wouldn't be persisted for the 3 hours, when the browser was closed without manually logging off?
The issue is with a bug in the OnValidateIdentity which when regenerating the cookie currently always sets IsPersistent to false (even if the original cookie was persistent). So because you set validateInterval to 0 (always validate every request), you effectively never will get a persistent cookie.

Resources