Owin OAuth Providers Twitter and Microsoft - twitter

I am having issues with the below line returning null for twitter and microsoft:
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
this is in the account controller like below:
[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, false);
switch (result)
{
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
//case SignInStatus.RequiresVerification:
// return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = false });
case SignInStatus.Failure:
default:
// If the user does not have an account, then prompt the user to create an account
ViewBag.ReturnUrl = returnUrl;
ViewBag.LoginProvider = loginInfo.Login.LoginProvider;
return View("ExternalLoginConfirmation", new AccountExternalLoginConfirmationViewModel { Email = loginInfo.Email });
}
}
In the startup.auth.cs the current configuration is:
app.UseTwitterAuthentication(
new TwitterAuthenticationOptions()
{
ConsumerKey = ConfigurationManager.AppSettings["TwitterAPIKey"],
ConsumerSecret = ConfigurationManager.AppSettings["TwitterAPISecret"],
Provider = new TwitterAuthenticationProvider()
{
OnAuthenticated = context =>
{
context.Identity.AddClaim(new Claim("urn:tokens:twitter:accesstoken", context.AccessToken));
context.Identity.AddClaim(new Claim("urn:tokens:twitter:accesstokensecret",
context.AccessTokenSecret));
return Task.FromResult(true);
}
}
});
app.UseMicrosoftAccountAuthentication(new MicrosoftAccountAuthenticationOptions()
{
ClientId = ConfigurationManager.AppSettings["MicrosoftAPIKey"],
ClientSecret = ConfigurationManager.AppSettings["MicrosoftAPISecret"],
// Scope = { "wl.basic", "wl.emails" },
Provider = new MicrosoftAccountAuthenticationProvider()
{
OnAuthenticated = context =>
{
context.Identity.AddClaim(new Claim("urn:microsoftaccount:access_token", context.AccessToken, "Microsoft"));
context.Identity.AddClaim(new Claim("urn:microsoft:email", context.Email));
return Task.FromResult(true);
}
}
});
It has been suggested including Scope = { "wl.basic", "wl.emails" } in MicrosoftAccountAuthenticationOptions. This returns a bad request however. Any ideas on the way to resolve this issue with twitter and microsoft login.
My urls I am using for microsoft are
Redirect Url: https://localhost/signin-microsoft
Logout Url: https://localhost/account/logout
Homepage: https://localhost
Twitter
Website: https://127.0.0.1
Call Back url: https://127.0.0.1/signin-twitter
I have tried with live urls on live also and am still getting null on
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();

Try this:
var options = new TwitterAuthenticationOptions
{
SignInAsAuthenticationType = signInAsType,
ConsumerKey = "...",
ConsumerSecret = "...",
Provider = new TwitterAuthenticationProvider()
{
OnAuthenticated = async ctx =>
{
var manager = new OAuth.Manager(
"-your-twitter-access-token-",
"-your-twitter-access-token-secret-",
ctx.AccessToken,
ctx.AccessTokenSecret);
var url = "https://api.twitter.com/1.1/account/verify_credentials.json";
var authzHeader = manager.GenerateAuthzHeader(url, "GET");
var request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "GET";
request.PreAuthenticate = true;
request.AllowWriteStreamBuffering = true;
request.Headers.Add("Authorization", authzHeader);
using (var response = (HttpWebResponse)request.GetResponse())
{
if (response.StatusCode != HttpStatusCode.OK)
throw new Exception("NOK");
var responseStream = response.GetResponseStream();
var reader = new System.IO.StreamReader(responseStream);
var res = reader.ReadToEnd();
Newtonsoft.Json.Linq.JObject data = (Newtonsoft.Json.Linq.JObject)JsonConvert.DeserializeObject(res);
var claims = new List<Claim>();
claims.Add(new Claim(Core.Constants.ClaimTypes.RawData, ctx.Identity.Claims.ToJsonString()));
claims.Add(new Claim(Core.Constants.ClaimTypes.AccessToken, ctx.AccessToken));
claims.Add(new Claim(Core.Constants.ClaimTypes.AccessTokenSecret, ctx.AccessTokenSecret));
claims.Add(new Claim(Core.Constants.ClaimTypes.Subject, ctx.UserId));
claims.Add(new Claim(Core.Constants.ClaimTypes.Name, data["name"].TokenString()));
claims.Add(new Claim(Core.Constants.ClaimTypes.Locale, GenerateLocale(data["lang"].TokenString())));
claims.Add(new Claim(Core.Constants.ClaimTypes.ZoneInfo, GenerateZone(data["location"].TokenString(), data["time_zone"].TokenString())));
claims.Add(new Claim(Core.Constants.ClaimTypes.WebSite, data["url"].TokenString()));
claims.Add(new Claim(Core.Constants.ClaimTypes.ProfileUrl, "https://twitter.com/" + ctx.ScreenName));
claims.Add(new Claim(Core.Constants.ClaimTypes.Picture, data["profile_image_url"].TokenString()));
await PrepClaims(ctx.Identity, claims);
}
}
}

Related

ASP.NET MVC "SignInManager.ExternalSignInAsync(loginInfo, isPersistent: false);" return failure all the time

I've made some changes in my ExternalLogin actions. I added a new field to my user table, for the first name, and I added code as to request the first name from facebook. It could be the reason why I am getting the error. I will not post the app secret for facebook external login here, but you know it is present in my code. When I register a new user with facebook it works and keeps the user logged in, and adds the user to the database, also gets the correct first name. However When I log off and attempt to log in with facebook, it forces me to register instead of logging me in (it redirects to the register with facebook page, saying "you have successfully authenticated with facebook. Please enter a user name for this site below and click the Register button to finish logging in.")
Here is the code with the changes I made:
Inside the Startup.Auth class:
app.UseFacebookAuthentication(new FacebookAuthenticationOptions()
{
AppId = "2737863089761791",
AppSecret = "",
BackchannelHttpHandler = new HttpClientHandler(),
UserInformationEndpoint = "https://graph.facebook.com/v2.8/me?fields=id,name,email,first_name,last_name",
Scope = { "email" },
Provider = new FacebookAuthenticationProvider()
{
OnAuthenticated = async context =>
{
context.Identity.AddClaim(new System.Security.Claims.Claim("FacebookAccessToken", context.AccessToken));
foreach (var claim in context.User)
{
var claimType = string.Format("urn:facebook:{0}", claim.Key);
string claimValue = claim.Value.ToString();
if (!context.Identity.HasClaim(claimType, claimValue))
context.Identity.AddClaim(new System.Security.Claims.Claim(claimType, claimValue, "XmlSchemaString", "Facebook"));
}
}
}
});
Inside my ExternalLoginCallback action in AccountController:
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
var firstName = loginInfo.ExternalIdentity.Claims.First(c => c.Type == "urn:facebook:first_name").Value;
if (loginInfo == null)
{
return RedirectToAction("Login");
}
// Sign in the user with this external login provider if the user already has a login
var result = await SignInManager.ExternalSignInAsync(loginInfo, isPersistent: false);
switch (result)
{
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = false });
case SignInStatus.Failure:
default:
// If the user does not have an account, then prompt the user to create an account
ViewBag.ReturnUrl = returnUrl;
ViewBag.LoginProvider = loginInfo.Login.LoginProvider;
return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = loginInfo.Email, Name = firstName });
}
I figured it redirects me to the register page, because the line
var result = await SignInManager.ExternalSignInAsync(loginInfo, isPersistent: false);
returns failure inside ExternalLoginCallback and I don't know why. It must return success to get to the case:
case SignInStatus.Success
I had an error in my ExternalLoginConfirmation action, so i put some code in a try-catch block. It solved my problem, but I still don't know where the error was coming from:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ExternalLoginConfirmation(ExternalLoginConfirmationViewModel model, string returnUrl)
{
if (User.Identity.IsAuthenticated)
{
return RedirectToAction("Index", "Manage");
}
if (ModelState.IsValid)
{
// Get the information about the user from the external login provider
var info = await AuthenticationManager.GetExternalLoginInfoAsync();
if (info == null)
{
return View("ExternalLoginFailure");
}
var user = new ApplicationUser { UserName = model.Name, Email = model.Email , Name = model.Name};
var result = await UserManager.CreateAsync(user);
if (result.Succeeded)
{
try
{
var useraux = await UserManager.FindByEmailAsync(model.Email);
result = await UserManager.AddLoginAsync(useraux.Id, info.Login);
}
catch (DbEntityValidationException e)
{
foreach (var eve in e.EntityValidationErrors)
{
Console.WriteLine("Entity of type \"{0}\" in state \"{1}\" has the following validation errors:",
eve.Entry.Entity.GetType().Name, eve.Entry.State);
Response.Write("Object: " + eve.Entry.Entity.ToString());
Response.Write(" " +
"");
foreach (var ve in eve.ValidationErrors)
{
Console.WriteLine("- Property: \"{0}\", Error: \"{1}\"",
ve.PropertyName, ve.ErrorMessage);
Response.Write(ve.ErrorMessage + "" +
"");
}
}
//throw;
}
if (result.Succeeded)
{
//--
//await StoreFacebookAuthToken(user);
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
return RedirectToLocal(returnUrl);
}
}
AddErrors(result);
}
ViewBag.ReturnUrl = returnUrl;
return View(model);
}

asp.net core 2.0 cookie authentication with session variable

I'm trying to implement asp.net core cookie-based authentication. So I have added the code below to my startup.cs
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.AccessDeniedPath = new PathString("/login");
options.LoginPath = new PathString("/login");
options.SlidingExpiration = true;
});
and I'm sign-in using the code below
[HttpPost]
[ValidateAntiForgeryToken]
[Route("login")]
public async Task<IActionResult> Login(AuthViewModel authView)
{
if (ModelState.IsValid)
{
var (status, message, SigninUser) = await authentication.Authenticate(new User()
{
email = authView.Email,
pwd = authView.Password
});
if (status)
{
List<Claim> claims = new List<Claim>
{
new Claim(ClaimTypes.Name, "App Member"),
new Claim(ClaimTypes.Email, SigninUser.email)
};
ClaimsIdentity identity = new ClaimsIdentity(claims, "cookie");
ClaimsPrincipal principal = new ClaimsPrincipal(identity);
await HttpContext.SignInAsync(
scheme: CookieAuthenticationDefaults.AuthenticationScheme,
principal: principal,
properties: new AuthenticationProperties
{
IsPersistent = authView.RememberMe,
ExpiresUtc = DateTime.UtcNow.AddYears(1)
});
HttpContext.Session.Set<User>("session_user", SigninUser);
if (Url.IsLocalUrl(authView.returnUrl))
return Redirect(authView.returnUrl);
else
return RedirectToAction("Index");
}
else
{
authView.Status = false;
authView.Message = message;
}
}
else
{
string message = string.Join(" | ", ModelState.Values.SelectMany(e => e.Errors).Select(v => v.ErrorMessage));
authView.Status = false;
authView.Message = message;
}
return View(authView);
}
This works fine. But when I keep the browser IDLE for like 30 minutes the "session_user" session variable get expired and the user still gets authenticated. How can I resolve this?
Also using cookie-based authentication can have a performance penalty?
Thanks

Social Signout not working in conjunction with OpenIdConnect

I created an MVC Web Application with OpenIdConnect authentication (for Azure Authentication) and Authentication providers for Google, Facebook and Microsoft Account.
The Configuration in StartupAuth looks like this:
public void ConfigureAuth(IAppBuilder app)
{
if (Config.TaskboardUserSource == Config.DirectoryService.AzureAD)
{
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
ExpireTimeSpan = new TimeSpan(6, 0, 0),
SlidingExpiration = true,
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Home/Index"),
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = Config.ClientId,
Authority = string.Format("{0}common", Config.AadInstance),
UseTokenLifetime = false,
TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuer = false,
},
Notifications = new OpenIdConnectAuthenticationNotifications()
{
SecurityTokenValidated = (context) =>
{
return Task.FromResult(0);
},
AuthorizationCodeReceived = (context) =>
{
var code = context.Code;
ClientCredential credential = new ClientCredential(Config.ClientId, Config.AppKey);
string tenantID = context.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
string signedInUserID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
AuthenticationContext authContext = new AuthenticationContext(string.Format("{0}{1}", Config.AadInstance, tenantID), new ADALTokenCache(signedInUserID));
AuthenticationResult result = authContext.AcquireTokenByAuthorizationCodeAsync(
code,
new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)),
credential,
Config.GraphResourceID).Result;
return Task.FromResult(0);
},
RedirectToIdentityProvider = (context) =>
{
// This ensures that the address used for sign in and sign out is picked up dynamically from the request
// this allows you to deploy your app (to Azure Web Sites, for example)without having to change settings
// Remember that the base URL of the address used here must be provisioned in Azure AD beforehand.
string appBaseUrl = context.Request.Scheme + "://" + context.Request.Host + context.Request.PathBase;
context.ProtocolMessage.RedirectUri = appBaseUrl + "/";
context.ProtocolMessage.PostLogoutRedirectUri = appBaseUrl;
return Task.FromResult(0);
},
AuthenticationFailed = (context) =>
{
context.OwinContext.Response.Redirect("/Home/Index");
context.HandleResponse(); // Suppress the exception
return Task.FromResult(0);
}
}
});
var facebookAuthenticationOptions = new FacebookAuthenticationOptions()
{
AppId = Config.FBAppId,
AppSecret = Config.FBAppSecret,
UserInformationEndpoint = Config.FBUserInformationEndpoint
};
facebookAuthenticationOptions.Scope.Add("email");
app.UseFacebookAuthentication(facebookAuthenticationOptions);
app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions()
{
ClientId = Config.GoogleClientId,
ClientSecret = Config.GoogleClientSecret
});
var microsoftOptions = new MicrosoftAccountAuthenticationOptions()
{
ClientId = Config.MSAppId,
ClientSecret = Config.MSAppSecret,
};
microsoftOptions.Scope.Add("wl.basic");
microsoftOptions.Scope.Add("wl.emails");
app.UseMicrosoftAccountAuthentication(microsoftOptions);
}
}
All authentication options work fine.
When I want to signout, the only signout working is OpenIdConnect Signout.
For all other authentication providers, the cookie is still available and just by clicking the "Logon" Button the secured pages are shown without asking for a password.
My Signout looks like this:
public void SignOut()
{
string callbackUrl = Url.Action("SignOutCallback", "Account", routeValues: null, protocol: Request.Url.Scheme);
HttpContext.GetOwinContext().Authentication.SignOut(
new AuthenticationProperties { RedirectUri = callbackUrl },
HttpContext.GetOwinContext()
.Authentication.GetAuthenticationTypes()
.Select(o => o.AuthenticationType).ToArray());
HttpContext.GetOwinContext().Authentication.SignOut(
new AuthenticationProperties { RedirectUri = callbackUrl },
CookieAuthenticationDefaults.AuthenticationType);
}
How can I make sure the user is signed out and gets redirected to the start page?
After I inserted a switch case statement in my signout code to do the signout for every logonprovider it finally works. Here is my code:
public async Task<ActionResult> SignOut()
{
var currentUser = await UserService.CurrentUser();
if (currentUser != null)
{
var redirectUrl = Request.GetBaseUrl();
var loginProviders = new string[] {
"Google",
"TwoFactorRememberBrowser",
"TwoFactorCookie",
"ExternalCookie",
"ApplicationCookie"
};
switch (currentUser.LoginProvider)
{
case LogonProvider.FacebookProviderKey:
{
loginProviders = new string[] {
"Facebook",
"TwoFactorRememberBrowser",
"TwoFactorCookie",
"ExternalCookie",
"ApplicationCookie" };
break;
}
case LogonProvider.GoogleProviderKey:
{
loginProviders = new string[] {
"Google",
"TwoFactorRememberBrowser",
"TwoFactorCookie",
"ExternalCookie",
"ApplicationCookie" };
//return new RedirectResult($"https://www.google.com/accounts/Logout");
break;
}
case LogonProvider.MicrosoftProviderKey:
{
loginProviders = new string[] {
"Microsoft",
"TwoFactorRememberBrowser",
"TwoFactorCookie",
"ExternalCookie",
"ApplicationCookie" };
break;
}
default:
{
loginProviders = new string[] {
"Office365",
"TwoFactorRememberBrowser",
"TwoFactorCookie",
"ExternalCookie",
"ApplicationCookie" };
break;
}
}
HttpContext.GetOwinContext().Authentication.SignOut(new AuthenticationProperties { RedirectUri = redirectUrl }, loginProviders);
}
return RedirectToAction("Index", "Home");
}

WebApi Facebook authentication with email address

I want to sign my users in using their email address from Facebook. I have configured my Facebook authentication:
var facebookAuthenticationOptions = new FacebookAuthenticationOptions
{
AppId = facebookId,
AppSecret = facebookSecret,
Provider = new FacebookProvider()
};
app.UseFacebookAuthentication(facebookAuthenticationOptions);
I have overridden the Facebook provider to also return the email address:
public class FacebookProvider : FacebookAuthenticationProvider
{
public override Task Authenticated(FacebookAuthenticatedContext context)
{
var accessTokenClaim = new Claim("ExternalAccessToken", context.AccessToken, "urn:facebook:access_token");
context.Identity.AddClaim(accessTokenClaim);
var extraClaims = GetAdditionalFacebookClaims(accessTokenClaim);
context.Identity.AddClaim(new Claim(ClaimTypes.Email, extraClaims.First(k => k.Key == "email").Value.ToString()));
context.Identity.AddClaim(new Claim("Provider", context.Identity.AuthenticationType));
context.Identity.AddClaim(new Claim(ClaimTypes.Name, context.Identity.FindFirstValue(ClaimTypes.Name)));
var userDetail = context.User;
var link = userDetail.Value<string>("link") ?? string.Empty;
context.Identity.AddClaim(new Claim("link", link));
context.Identity.AddClaim(new Claim("FacebookId", userDetail.Value<string>("id")));
return Task.FromResult(0);
}
private static JsonObject GetAdditionalFacebookClaims(Claim accessToken)
{
var fb = new FacebookClient(accessToken.Value);
return fb.Get("me", new { fields = new[] { "email" } }) as JsonObject;
}
Everything works fine in MVC - in the LoginOwinCallback function, I am able to retrieve the user's email address as returned from Facebook. I am trying to achieve the same thing in WebApi using token authentication instead of external cookies. However, although I can see my provider adding the email claim to the response, when I call the AuthenticateAsync method in the following routine, the Email claim is not included.
private async Task<ExternalLoginInfo> GetExternalLoginInfoAsync()
{
var result = await Authentication.AuthenticateAsync(DefaultAuthenticationTypes.ExternalBearer);
if (result == null || result.Identity == null) return null;
var idClaim = result.Identity.FindFirst(ClaimTypes.NameIdentifier);
if (idClaim != null)
{
return new ExternalLoginInfo()
{
DefaultUserName = result.Identity.Name == null ? "" : result.Identity.Name.Replace(" ", ""),
ExternalIdentity = result.Identity,
Login = new UserLoginInfo(idClaim.Issuer, idClaim.Value)
};
}
return null;
}
Any ideas what I am doing wrong?
For anyone else facing the same problem, the Email claim is available - just a bit hidden.
Firstly, to correctly retrieve the user's email address from Facebook, the authentication set-up in the ConfigureAuth method of Startup.Auth.cs should be:
app.UseFacebookAuthentication(new FacebookAuthenticationOptions
{
AppId = facebookId,
AppSecret = facebookSecret,
Scope= {"email"},
UserInformationEndpoint = "https://graph.facebook.com/v2.4/me?fields=email"
});
In the LoginOwinCallback method of the MVC AccountController class, the email address is found in the Email property of the ExternalLoginInfo object returned by var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();.
To retrieve the email address in the WebAPI AccountController class, you will need to cast the User.Identity object to the ClaimsIdentity type.
[OverrideAuthentication]
[HostAuthentication(DefaultAuthenticationTypes.ExternalCookie)]
[AllowAnonymous]
[Route("ExternalLogin", Name = "ExternalLogin")]
public async Task<IHttpActionResult> GetExternalLogin(string provider, string error = null)
{
if (error != null)
{
return Redirect(Url.Content("~/") + "#error=" + Uri.EscapeDataString(error));
}
if (!User.Identity.IsAuthenticated)
{
return new ChallengeResult(provider, this);
}
// get all of the claims
var claimsIdentity = User.Identity as ClaimsIdentity; // this cast exposes all of the claims returned by Facebook
// get the external login details
var externalLogin = ExternalLoginData.FromIdentity(claimsIdentity);
if (externalLogin == null)
{
return InternalServerError();
}
if (externalLogin.LoginProvider != provider)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
return new ChallengeResult(provider, this);
}
var user = await UserManager.FindAsync(new UserLoginInfo(externalLogin.LoginProvider,
externalLogin.ProviderKey));
var hasRegistered = user != null;
if (hasRegistered)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
var oAuthIdentity = await user.GenerateUserIdentityAsync(UserManager,
OAuthDefaults.AuthenticationType);
var cookieIdentity = await user.GenerateUserIdentityAsync(UserManager,
CookieAuthenticationDefaults.AuthenticationType);
var properties = ApplicationOAuthProvider.CreateProperties(user.UserName);
AuthenticationManager.SignIn(properties, oAuthIdentity, cookieIdentity);
}
else
{
var claims = claimsIdentity?.Claims ?? externalLogin.GetClaims();
var identity = new ClaimsIdentity(claims, OAuthDefaults.AuthenticationType);
AuthenticationManager.SignIn(identity);
}
return Ok();
}
This means you can then use the FindFirst method on the Identity to find the email claim returned by Facebook.

Mix OAuth ("Individual Accounts") and Azure Active Directory ("Organizational Accounts") authentication in MVC5 [duplicate]

I am kind of successful by doing this in the Startup.Auth.cs file
// Configure the db context and user manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.Properties["Microsoft.Owin.Security.Constants.DefaultSignInAsAuthenticationType"] = "ExternalCookie";
// Configure the sign in cookie
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
PostLogoutRedirectUri = postLogoutRedirectUri
});
The challenge I have is, when a user is signed out,and tries to hit a non-login page like say http://mywebsite/users/management rather than http://mywebsite/account/login the application redirects to the Azure AD sign-in page automatically, which is not right. Because there could be users who do not have account on Azure AD at all. Even if we give a proper userid and password in the AD sign in page and click sign-in, it keeps redirecting between different urls within http://login.windows.net and never goes to our website at all.
Here is the logout code -
AuthenticationManager.SignOut(new string[] { DefaultAuthenticationTypes.ExternalCookie, DefaultAuthenticationTypes.ApplicationCookie, OpenIdConnectAuthenticationDefaults.AuthenticationType });
return RedirectToAction("Login", "Account");
I am not sure what I'm doing wrong here.
Edit 1
My ExternalLoginCallback method
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
if (loginInfo == null)
{
return RedirectToAction("Login");
}
var claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.Sid, "Office365"));
// Sign in the user with this external login provider if the user already has a login
var user = await UserManager.FindByEmailAsync(loginInfo.ExternalIdentity.Name);
if (user != null && user.IsActive == true && user.EmailConfirmed == true)
{
var result = await UserManager.AddLoginAsync(user.Id, loginInfo.Login);
if (result.Succeeded)
{
if (claims != null)
{
var userIdentity = await user.GenerateUserIdentityAsync(UserManager);
userIdentity.AddClaims(claims);
}
}
await SignInAsync(user, isPersistent: true);
Session[AppConstants.General.UserID] = user.Id;
string fullName = string.Format("{0} {1}",user.FirstName,user.LastName);
Session[AppConstants.General.UserFullName] = fullName;
return RedirectToLocal(returnUrl);
}
else
{
// If the user does not have an account, tell that to the user.
ViewBag.ReturnUrl = returnUrl;
ViewBag.LoginProvider = loginInfo.Login.LoginProvider;
return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = loginInfo.Email });
}
}
Try this
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = ClientId,
Authority = Authority,
Notifications = new OpenIdConnectAuthenticationNotifications()
{
RedirectToIdentityProvider = (context) =>
{
if (context.Request.Path.Value == "/Account/ExternalLogin" || (context.Request.Path.Value == "/Account/LogOff" && context.Request.User.Identity.IsExternalUser()))
{
// This ensures that the address used for sign in and sign out is picked up dynamically from the request
// this allows you to deploy your app (to Azure Web Sites, for example)without having to change settings
// Remember that the base URL of the address used here must be provisioned in Azure AD beforehand.
string appBaseUrl = context.Request.Scheme + "://" + context.Request.Host + context.Request.PathBase;
context.ProtocolMessage.RedirectUri = appBaseUrl + "/";
context.ProtocolMessage.PostLogoutRedirectUri = appBaseUrl;
}
else
{
//This is to avoid being redirected to the microsoft login page when deep linking and not logged in
context.State = Microsoft.Owin.Security.Notifications.NotificationResultState.Skipped;
context.HandleResponse();
}
return Task.FromResult(0);
},
}
});
EDIT:
Forgot this extension method
public static class IdentityExtensions
{
public static bool IsExternalUser(this IIdentity identity)
{
ClaimsIdentity ci = identity as ClaimsIdentity;
if (ci != null && ci.IsAuthenticated == true)
{
var value = ci.FindFirstValue(ClaimTypes.Sid);
if (value != null && value == "Office365")
{
return true;
}
}
return false;
}
}
EDIT 2:
You have to have some custom logic in the ExternalLoginCallback (AccountController) e.g. add the Sid claim. In this case there is also logic to check if the user allows external login.
// GET: /Account/ExternalLoginCallback
[AllowAnonymous]
public async Task<ActionResult> ExternalLoginCallback(string returnUrl, string urlHash)
{
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
if (loginInfo == null)
{
return RedirectToAction("Login");
}
var claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.Sid, "Office365"));
// Sign in the user with this external login provider if the user already has a login
var user = await UserManager.FindAsync(loginInfo.Login);
if (user == null)
{
user = await UserManager.FindByNameAsync(loginInfo.DefaultUserName);
if (user != null)
{
if(user.AllowExternalLogin == false)
{
ModelState.AddModelError("", String.Format("User {0} not allowed to authenticate with Office 365.", loginInfo.DefaultUserName));
return View("Login");
}
var result = await UserManager.AddLoginAsync(user.Id, loginInfo.Login);
if (result.Succeeded)
{
if (claims != null)
{
var userIdentity = await user.GenerateUserIdentityAsync(UserManager);
userIdentity.AddClaims(claims);
}
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
}
return RedirectToLocal(returnUrl);
}
else
{
ModelState.AddModelError("", String.Format("User {0} not found.", loginInfo.DefaultUserName));
return View("Login");
}
}
else
{
if (user.AllowExternalLogin == false)
{
ModelState.AddModelError("", String.Format("User {0} not allowed to authenticate with Office 365.", loginInfo.DefaultUserName));
return View("Login");
}
if (claims != null)
{
var userIdentity = await user.GenerateUserIdentityAsync(UserManager);
userIdentity.AddClaims(claims);
}
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
return RedirectToLocal(returnUrl);
}
}

Resources