I'm trying make my user login with Azure AD credentials (using OWIN WsFederation plugin) or using a local user account with microsoft asp.net identity in a MVC 5.1 Web App.
Login with local users work fine, login using a federated account works only once, and I need to restart my app to make it work again.
I suppose the problem is with the response from Microsoft login page not processed correctly
Infact, using two differente browsers (chrome+ie) in private mode and Fiddler, I can see that my cookie is set on first request but not on a subsequent request made from a different browser
First request
Second request
This is my ConfigureAuth
public void ConfigureAuth(IAppBuilder app)
{
AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier;
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.SetDefaultSignInAsAuthenticationType("ExternalCookie");
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Active,
});
// these two lines of code are needed if you are using any of the external authentication middleware
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "ExternalCookie",
AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Passive,
});
app.UseWsFederationAuthentication(new Microsoft.Owin.Security.WsFederation.WsFederationAuthenticationOptions()
{
MetadataAddress = "https://login.windows.net/XXXXXXX.onmicrosoft.com/federationmetadata/2007-06/federationmetadata.xml",
Wtrealm = "https://MYREALM",
AuthenticationType = WsFederationAuthenticationDefaults.AuthenticationType,
});
}
This is part of the account controller
//
// POST: /Account/ExternalLogin
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult ExternalLogin(string provider, string returnUrl)
{
// Request a redirect to the external login provider
return new ChallengeResult(provider, Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }));
}
// GET: /Account/ExternalLoginCallback
[AllowAnonymous]
public ActionResult ExternalLoginCallback(string returnUrl)
{
var ctx = Request.GetOwinContext();
var result = ctx.Authentication.AuthenticateAsync("ExternalCookie").Result;
if (result != null) //null on request other than the first (!!!)
{
ctx.Authentication.SignOut("ExternalCookie");
var claims = result.Identity.Claims.ToList();
claims.Add(new Claim(ClaimTypes.AuthenticationMethod, "External Account"));
var email = claims.Where(x => x.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name").SingleOrDefault().Value;
var ci = new ClaimsIdentity(claims, DefaultAuthenticationTypes.ApplicationCookie);
ctx.Authentication.SignIn(ci);
}
return RedirectToLocal(returnUrl);
}
In the ConfgureAuth set AuthenticationMode to Passive. It worked in my workflow which seems very similar to yours.
app.UseWsFederationAuthentication(new Microsoft.Owin.Security.WsFederation.WsFederationAuthenticationOptions()
{
MetadataAddress = "https://login.windows.net/XXXXXXX.onmicrosoft.com/federationmetadata/2007-06/federationmetadata.xml",
Wtrealm = "https://MYREALM",
AuthenticationType = WsFederationAuthenticationDefaults.AuthenticationType,
AuthenticationMode = AuthenticationMode.Passive
});
http://msdn.microsoft.com/en-us/library/microsoft.owin.security.authenticationmode%28v=vs.113%29.aspx
Related
We are in the process of learning Identity Server with the eventual aim of migrating our existing authentication service to it. For company, logistical and compatibility reasons, we are sticking with IS 3. We're not quite ready to move over to Core.
There are two parts to my question:
1) I have modified the sample app, available here, that uses a custom login page so that the browser prompts the user for their X509Certificate2 (as a partial login). The user enters a password and the certificate is used to call another endpoint which returns user-specific data. At that point, we wish to create custom user claims based on the returned data and then issue the cookie.
This all works fine up until the client receives the cookie. I cannot seem to extract the custom claims added to AuthenticatedLogin's Claims object on the client application. The client is configured to access all scopes.
It seems like I'm missing something very basic. Am I doing something wrong here? Bear in mind, these are just meaningless claims for test purposes.
2) Would this be an acceptable approach to issue claims? We would then likely use the returned cookie in order to call a separate authorisation service, as our roles are quite complex.
I have implemented the custom user service, with PreAuthenticateAsync redirecting to the custom login page:
public override Task PreAuthenticateAsync(PreAuthenticationContext context)
{
var id = ctx.Request.Query.Get("signin");
context.AuthenticateResult = new AuthenticateResult("~/custom/login?id=" + id, (IEnumerable<Claim>)null);
return Task.FromResult(0);
}
The controller method which creates the claims and calls IssueLoginCookie :
[RequireHttps]
[Route("core/custom/login")]
[HttpPost]
public ActionResult Index(string id, string password)
{
var userData = GetUser(password);
var owinEnvironment = Request.GetOwinContext().Environment;
var authenticatedLogin = new AuthenticatedLogin
{
IdentityProvider = Constants.BuiltInIdentityProvider,
Name = userData.UserName,
Subject = userData.EmailAddress,
Claims = GetClaims(userData),
PersistentLogin = false
};
owinEnvironment.IssueLoginCookie(authenticatedLogin);
var msg = owinEnvironment.GetSignInMessage(id);
var returnUrl = msg.ReturnUrl;
owinEnvironment.RemovePartialLoginCookie();
return Redirect(returnUrl);
}
// add our CUSTOM claims
private List<Claim> GetClaims(CustomUser authenticatedUser)
{
List<Claim> claims = new List<Claim>();
claims.Add(new Claim("claim1", authenticatedUser.CustomClaim1));
claims.Add(new Claim("claim2", authenticatedUser.CustomClaim2));
claims.Add(new Claim("claim3", authenticatedUser.CustomClaim3));
claims.Add(new Claim("Claim4", authenticatedUser.CustomClaim4));
return claims;
}
The client controller method with Authorize decorator:
[Authorize]
public ActionResult About()
{
// "CustomClaim1", "CustomClaim2" etc are not there :(
return View((User as ClaimsPrincipal).Claims);
}
The registered in-memory scope:
var scope1 = new Scope
{
Enabled = true,
Name = "user",
Type = ScopeType.Identity,
Claims = new List<ScopeClaim>
{
new ScopeClaim("CustomClaim1", true),
new ScopeClaim("CustomClaim2", true),
new ScopeClaim("CustomClaim3", true),
new ScopeClaim("CustomClaim4", true),
},
IncludeAllClaimsForUser = true
};
And finally the client's Configuration:
public void Configuration(IAppBuilder app)
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
AntiForgeryConfig.UniqueClaimTypeIdentifier = Constants.ClaimTypes.Subject;
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap = new Dictionary<string, string>();
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
Authority = "https://localhost/idprov/core",
ClientId = "mvc",
RedirectUri = "https://localhost/dummyclient/About",
ResponseType = "id_token",
ClientSecret = "secret",
Scope = "openid partyuser",
SignInAsAuthenticationType = "Cookies",
});
}
Hi Try adding scope in your client like
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
Authority = "https://localhost/idprov/core",
ClientId = "mvc",
RedirectUri = "https://localhost/dummyclient/About",
ResponseType = "id_token",
ClientSecret = "secret",
Scope = "openid partyuser CustomClaim1 CustomClaim2",
SignInAsAuthenticationType = "Cookies",
});
Situation:
I have a Web API 2 project which acts as an Authorization server (/token endpoint) and a resource server. I am using the template that comes out of box with ASP.Net Web API minus any MVC reference.
The Start.Auth is configured as below:
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context and user manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.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
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Configure the application for OAuth based flow
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AuthorizeEndpointPath = new PathString("/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
// In production mode set AllowInsecureHttp = false
AllowInsecureHttp = true
};
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
var facebookAuthenticationOptions = new FacebookAuthenticationOptions()
{
AppId = ConfigurationManager.AppSettings["Test_Facebook_AppId"],
AppSecret = ConfigurationManager.AppSettings["Test_Facebook_AppSecret"],
//SendAppSecretProof = true,
Provider = new FacebookAuthenticationProvider
{
OnAuthenticated = (context) =>
{
context.Identity.AddClaim(new System.Security.Claims.Claim("FacebookAccessToken", context.AccessToken));
return Task.FromResult(0);
}
}
};
facebookAuthenticationOptions.Scope.Add("email user_about_me user_location");
app.UseFacebookAuthentication(facebookAuthenticationOptions);
}
The MVC 5 Client (different Project) uses the Web API app for authorization and data. Below is the code to retrieve the Bearer token in case of Username/Password store:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
model.ExternalProviders = await GetExternalLogins(returnUrl);
return View(model);
}
var client = Client.GetClient();
var response = await client.PostAsync("Token",
new StringContent(string.Format("grant_type=password&username={0}&password={1}", model.Email, model.Password), Encoding.UTF8));
if (response.IsSuccessStatusCode)
{
return RedirectToLocal(returnUrl);
}
return View();
}
Problem
I could retrieve the Bearer token and then add it to the Authorization Header for subsequent calls. I think that would be ok in case of an Angular App or a SPA. But I think there should be something in MVC that handles it for me, like automatically store it in a cookie and send the cookie on subsequent requests. I have searched around quite a lot and there are posts which hint towards this (Registering Web API 2 external logins from multiple API clients with OWIN Identity) but I haven't been able to figure out what to do after I get a token.
Do I need to add something in the MVC app Startup.Auth?
Ideally, I need the functionality which the AccountController in ASP.Net Template (MVC + Web API) gives out of box (Logins, Register, External logins, forget password etc etc...) but with the MVC and Web API in different projects.
Is there a template or a git repo which has this boiler plate code?
Thanks in advance!
Update
Incorporating #FrancisDucharme suggestions, below is the code for GrantResourceOwnerCredentials().
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
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);
//Add a response cookie...
context.Response.Cookies.Append("Token", context.Options.AccessTokenFormat.Protect(ticket));
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
}
But I can't still seem to get that Cookie or figure out what to do next.
Restating Questions:
What would be the correct way to authenticate, authorize and call Web API methods (Auth and Resource server) from an MVC client?
Is there boilerplate code or template for AccountController which does the basic plumbing (Login, register - internal/external, forgot password etc.)?
You could have your Startup class return a response cookie that the client will then return on all subsequent requests, here's an example. I would do it in GrantResourceOwnerCredentials.
public class AuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
//your authentication logic here, if it fails, do this...
//context.SetError("invalid_grant", "The user name or password is incorrect.");
//return;
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim("sub", context.UserName));
identity.AddClaim(new Claim("role", "user"));
AuthenticationTicket ticket = new AuthenticationTicket(identity);
//Add a response cookie...
context.Response.Cookies.Append("Token", context.Options.AccessTokenFormat.Protect(ticket));
context.Validated(ticket);
}
The Startup class:
public partial class Startup
{
public static OAuthBearerAuthenticationOptions OAuthBearerOptions { get; private set; }
public Startup()
{
OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
}
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
ConfigureOAuth(app);
//I use CORS in my projects....
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
WebApiConfig.Register(config);
}
public void ConfigureOAuth(IAppBuilder app)
{
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true, //I have this here for testing purpose, production should always only accept HTTPS encrypted traffic.
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30),
Provider = new AuthorizationServerProvider()
};
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(OAuthBearerOptions);
}
}
That assumes the client has cookies enabled, of course.
Then, modify your MVC headers to add the Authorization header to all requests as such.
In the ActionFilterAttribute, fetch your cookie value (Token) and add the header.
Instead of storing in session, I have added it to the the DefaultRequestHeaders as shown below so I don't need to add this in every call I make to Web API.
public async Task AuthenticateUser(string username, string password)
{
var data = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("grant_type", "password"),
new KeyValuePair<string, string>("username", username),
new KeyValuePair<string, string>("password", password)
});
using (HttpResponseMessage response = await APIClient.PostAsync("/Token", data))
{
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadAsAsync<AuthenticatedUser>();
APIClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.Access_Token);
}
else
{
throw new Exception(response.ReasonPhrase);
}
}
}
I have a mvc 5 app that uses forms authentication but the real Authentication of user happens using bearer token in web api . I'm adding token details in the cookie so the website is always authenticated. MVC and Web api are in same project. Web api hosted using Owin.
here is my code snippet.
startup.cs
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
//Configure authorization
ConfigureOAuth(app);
//register WebAPI
app.UseWebApi(ConfigureWebApiRoutes());
}
}
startup.auth.cs
// Enable the application to use a cookie to store information for the signed in user
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Login/Login"),
CookieHttpOnly = true,
//AuthenticationMode = AuthenticationMode.Passive,
CookieName = "YetAnotherTodo.WebApi.Auth",
//#if DEBUG
// CookieSecure = CookieSecureOption.Never
//#endif
});
// Use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
// using OAuth authentication server as authentication middle ware and Token Generation
app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new SimpleAuthorizationServerProvider(),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
#if DEBUG
AllowInsecureHttp = true
#endif
});
app.UseOAuthBearerAuthentication(OAuthBearerOptions);
code in MVC Login Controller
[AllowAnonymous]
[HttpPost]
public async Task<ActionResult> Login(LoginViewModel model, string redirectUrl = null)
{
if (!ModelState.IsValid) return View(model);
try
{
if (string.IsNullOrWhiteSpace(redirectUrl))
{
redirectUrl = "~/Home";
}
var result = await WebApiService.Instance.AuthenticateAsync<LogInResult>(model.UserName, model.Password);
//Let's keep the user authenticated in the MVC webapp.
//By using the AccessToken, we can use User.Identity.Name in the MVC controllers to make API calls.
FormsAuthentication.SetAuthCookie(result.AccessToken, model.RememberMe);
//Create an AuthenticationTicket to generate a cookie used to authenticate against Web API.
//But before we can do that, we need a ClaimsIdentity that can be authenticated in Web API.
var claims = new[]
{
new Claim(ClaimTypes.Name, model.UserName),
//Name is the default name claim type, and UserName is the one known also in Web API.
new Claim(ClaimTypes.NameIdentifier, model.UserName)
//If you want to use User.Identity.GetUserId in Web API, you need a NameIdentifier claim.
};
//Generate a new ClaimsIdentity, using the DefaultAuthenticationTypes.ApplicationCookie authenticationType.
//This also matches what we've set up in Web API.
var claimsIdentity = new ClaimsIdentity(claims,DefaultAuthenticationTypes.ApplicationCookie);
var authProperties = new AuthenticationProperties
{
ExpiresUtc = result.Expires,
IsPersistent = model.RememberMe,
IssuedUtc = result.Issued,
RedirectUri = redirectUrl
};
var authTicket = new AuthenticationTicket(claimsIdentity, authProperties);
//And now it's time to generate the cookie data. This is using the same code that is being used by the CookieAuthenticationMiddleware class in OWIN.
byte[] userData = DataSerializers.Ticket.Serialize(authTicket);
//Protect this user data and add the extra properties. These need to be the same as in Web API!
//byte[] protectedData = MachineKey.Protect(userData, new[] { "Microsoft.Owin.Security.Cookies.CookieAuthenticationMiddleware", DefaultAuthenticationTypes.ApplicationCookie, "v1" });
//base64-encode this data.
string protectedText = TextEncodings.Base64Url.Encode(userData);
//And now, we have the cookie.
Response.SetCookie(new HttpCookie("YetAnotherTodo.WebApi.Auth")
{
HttpOnly = true,
Expires = result.Expires.UtcDateTime,
Value = protectedText
});
Code in my provider that generates token
AuthenticationTicket ticket;
var cookiesIdentity = GenerateCookiesIdentity(context, user, out ticket);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
I was able to login and able to get ticket from token server but on subsequent request or redirect to home page after logging in , I'm still getting 401 error.
This is kinda combinations of these two blogs/tutorials : Blog 1 and Blog 2
I'm cobbling together snippets of code from blogs and different places to try to get this to work. Normally, I'd refer to the reference documentation, but I can't find it here or anywhere else. It's just videos and demos for specific use cases that include user management or facebook or twitter.
I have a proprietary authentication service that I'm using. User accounts are not managed inside my application. So I need to be able to sign in a user that's completely constructed at run time.
Here's what I'm trying now in my MVC app.
using System.Security.Claims;
public class HomeController : Controller {
public ActionResult Scratch() {
var claims = new Claim[] {
new Claim(ClaimTypes.Name, "somename"),
new Claim(ClaimTypes.NameIdentifier, "someidentifier"),
new Claim("foo", "bar"),
};
var identity = new ClaimsIdentity(claims);
var authenticationManager = HttpContext.GetOwinContext().Authentication;
authenticationManager.SignIn(identity);
return Content(
$"authentication manager type: {authenticationManager.GetType()} \n"
+ $"authenticated: {HttpContext.User.Identity.IsAuthenticated} \n"
+ $"user name: {HttpContext.User.Identity.Name} \n",
"text/plain");
}
}
The output is
authentication manager type: Microsoft.Owin.Security.AuthenticationManager
authenticated: False
user name:
Questions:
Why does the output show that the user has not been authenticated? What more do I have to do to get this user authenticated?
Where is the documentation for this framework?
Update
Startup.cs
public partial class Startup {
public void Configuration(IAppBuilder app) {
ConfigureAuth(app);
ConfigureAnalyticContext(app);
}
}
Startup.Auth.cs:
(there is actually much more, but all the rest has been commented out, in search of finding a minimal configuration that works)
public partial class Startup {
public void ConfigureAuth(IAppBuilder app) {
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
}
}
AnalyticContext.Auth.cs
(this is my Entity Framework context, I doubt it's related to this problem)
public partial class Startup {
public void ConfigureAnalyticContext(IAppBuilder app) {
app.CreatePerOwinContext(() => CentoAnalyticsContext.Create());
}
}
Well, it seems that you are not using ASP.NET Identity. ASP.NET Identity is new membership system of asp.net, which automatically creates database tables for storing users, encrypting password, etc.
What you are trying to do is to use the new authentication system provided by OWIN, which replaces the old FormsAuthentication style.
To make it work, you have to create the cookie authentication. Like this:
public static class AuthConfig
{
public const string DefaultAuthType = "DefaultAppCookie";
public const string LoginPath = "/System/SignIn";
public static void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthType,
LoginPath = new PathString(LoginPath)
});
AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier; //or whatever
}
}
In the login action:
var claims = new Claim[] {
new Claim(ClaimTypes.Name, "somename"),
new Claim(ClaimTypes.NameIdentifier, "someidentifier"),
new Claim("foo", "bar"),
};
ClaimsIdentity identity = new ClaimsIdentity(claims, AuthConfig.DefaultAuthType);
IAuthenticationManager authManager = Request.GetOwinContext().Authentication;
authManager.SignIn(new AuthenticationProperties() { IsPersistent = true }, identity);
I think that should be enough to make it work in your app. A few days ago I answered a similar question MVC Authentication - Easiest Way, take a look, it might be helpful.
I recently have added Active Directory authentication, constructed ClaimsPrincipal myself and signed-in the same way you do.
And you are indeed missing .UseCookieAuthentication in your ConfigureAuth(IAppBuilder app)
public void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "MyAuthenticationName", // <-- this must match the AuthenticatioType name when you do sign-out
LoginPath = new PathString("/MyLoginPath"),
CookieName = "MyCookieName",
CookieHttpOnly = true,
});
}
And you don't need UseExternalSignInCookie.
Request.IsAuthenticated will be false with in the same request flow.
I think you still need to update the current security principal if you need to check IsAuthenticated for the request as authenticationManager.SignIn only validates the user against data store and sets the OWIN cookie which when sent back in subsequent request sets the security principal , usually a redirect takes care of this as in most cases there will be redirection in home page or something. If you still need to check with in the same request you can do something like below depending on your requirement
var claims = new Claim[] {
new Claim(ClaimTypes.Name, "somename"),
new Claim(ClaimTypes.NameIdentifier, "someidentifier"),
new Claim("foo", "bar"),
};
var identity = new ClaimsIdentity(claims,DefaultAuthenticationTypes.ApplicationCookie,
ClaimTypes.Name, ClaimTypes.Role);
var principal = new ClaimsPrincipal(identity);
System.Threading.Thread.CurrentPrincipal = principal;
if (System.Web.HttpContext.Current != null)
System.Web.HttpContext.Current.User = principal;
Hope this helps.
I hope someone can help me out with this problem - it's driving me mad! :)
I'm trying to use external login through QQ Connect (OAuth 2.0) using tinysnake's QQ Connect provider: https://github.com/tinysnake/microsoft-owin-security-qq
Everything seems to be going great - I can sign in via my QQ account and I get posted back to my ExternalLoginCallBack-method with the appropriate claims etc.
I use these values to sign the user in through the IAuthenticationManager - all goes well. However - when I redirect the user to another page and checks if he's logged in - then I get a false value from the IsAuthenticated value... and I can't read any of the claims I set earlier.
It might be a simple fix - but I just can't see it right now :)
Some code:
AuthConfig:
public static void ConfigureAuthentication(IAppBuilder app)
{
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Normal cookie sign in
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
AuthenticationMode = AuthenticationMode.Active
});
// QQ CONNECT
app.UseQQConnectAuthentication(
appId: "XXXXXX",
appSecret: "XXXXXXXXXXXXXXXXX");
}
AccountController:
//
// POST: /Account/ExternalLogin
[System.Web.Mvc.HttpPost]
[System.Web.Mvc.AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult ExternalLogin(string provider, string returnUrl)
{
// Request a redirect to the external login provider
return new ChallengeResult(provider, Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }));
}
//
// GET: /Account/ExternalLoginCallback
[System.Web.Mvc.AllowAnonymous]
[HostAuthentication(DefaultAuthenticationTypes.ExternalCookie)]
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
var ctx = Request.GetOwinContext();
var result = ctx.Authentication.AuthenticateAsync(DefaultAuthenticationTypes.ExternalCookie).Result;
var claims = result.Identity.Claims.ToList();
var name = claims.First(i => i.Type == "urn:qqconnect:name");
claims.Add(new Claim(ClaimTypes.AuthenticationMethod, "QQ"));
claims.Add(new Claim(ClaimTypes.Name, name.Value));
var ci = new ClaimsIdentity(claims, DefaultAuthenticationTypes.ExternalCookie);
ctx.Authentication.SignIn(ci);
// DO OTHER STUFF HERE
return Redirect("~/");
}
All seems to be going well so far...
HomeController:
public ActionResult Index()
{
var model = new HomeViewModel();
var ctx = Request.GetOwinContext();
if (ctx.Authentication.User.Identity.IsAuthenticated) // <-- THIS RETURNS FALSE
{
var claimsIdentity = User.Identity as ClaimsIdentity;
model.Name = claimsIdentity.FindFirst(ClaimTypes.Name).Value;
model.IsAuthenticated = true;
}
return View(model);
}
When I check the ctx.Authentication.User.Identity.IsAuthenticated, I get a false value... and I can't retrieve any of the claims either.
Am I missing something?
Any help would be greatly appreciated :)
UPDATE
I got my code working by doing this in my AccountController:
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
var ctx = Request.GetOwinContext();
var result = ctx.Authentication.AuthenticateAsync(DefaultAuthenticationTypes.ExternalCookie).Result;
if (result.Identity.IsAuthenticated)
{
// Signed in successfully
var claims = result.Identity.Claims.ToList();
var name = claims.First(i => i.Type == "urn:qqconnect:name");
//claims.Add(new Claim(ClaimTypes.AuthenticationMethod, "QQ"));
claims.Add(new Claim(ClaimTypes.Name, name.Value));
var id = new ClaimsIdentity(claims, DefaultAuthenticationTypes.ApplicationCookie);
var authenticationManager = ctx.Authentication;
authenticationManager.SignIn(id);
}
return Redirect("~/");
}
But the way I see it - here I'm using the ApplicationCookie and NOT the ExternalCookie for signing in... or am I missing something entirely?
This solution works for me - but I'd like to know if this is the right way to be doing this?
From my understanding, what you are experiencing is expected. Extremely oversimplifying:
The app gets the external information and uses it to create an external cookie
the external cookie is sent to your app with the assumption that it is just a temporary cookie that will be used to look up any additional local information about the user and then converted to a local [application] cookie
See UseCookieAuthentication vs. UseExternalSignInCookie for a somewhat more thorough breakdown.