Spring security Google signin, controller doesn't redirect to my index.html - spring-security

My application has a login page which uses spring security. Then I would like to add "Sign in with Google" button likes following:
add google signin button
Then if user click signin with google, it should send a post request, following is my javascript file
<script type="text/javascript">
function onSignIn(googleUser) {
// The ID token you need to pass to your backend:
var id_token = googleUser.getAuthResponse().id_token;
var xhr = new XMLHttpRequest();
xhr.open('POST', 'http://localhost:8080/myapp/tokensigningoogle');
xhr.setRequestHeader('Content-Type',
'application/x-www-form-urlencoded');
xhr.onload = function() {
console.log('Signed in as: ' + xhr.responseText);
};
xhr.send('idtoken=' + id_token);
}
</script>
Then I added a MVC controller to get the GrantedAuthorities since I need to get the roles of the current user. Then I want to redirect to my index.html. It doesn't have errors when I run it in eclipse, however, it can not redirect to my index.html and it still stayes in my login.jsp.
#Controller
#RequestMapping(value = "/tokensigningoogle")
public class SigninWithGoogle {
static final Logger LOGGER = Logger.getLogger(SigninWithGoogle.class);
#RequestMapping(method = RequestMethod.POST, headers = "content-type=application/x-www-form-urlencoded")
public String processRegistration(#RequestParam("idtoken") String idTokenString, ModelMap model)
throws GeneralSecurityException, IOException {
GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(new NetHttpTransport(), new GsonFactory())
.setAudience(Arrays.asList(Config.GOOGLE_CLIENT_ID)).setIssuer("accounts.google.com").build();
GoogleIdToken idToken = verifier.verify(idTokenString);
if (idToken != null) {
Payload payload = idToken.getPayload();
// Print user identifier
String userId = payload.getSubject();
// Get profile information from payload
String email = payload.getEmail();
boolean emailVerified = Boolean.valueOf(payload.getEmailVerified());
List<User> users = DbFunction.listHqlNew("FROM User WHERE email = :email", "email", email);
if (!emailVerified || users.isEmpty()) {
return "/error.html";
} else {
List<String> roles = DbFunction.listSQLNew(
"SELECT role.name FROM user_role_association JOIN role ON role.id = role_id JOIN user on user.id = user_id WHERE user.email = :email",
"email", email);
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
for (String role : roles) {
authorities.add(new SimpleGrantedAuthority(role));
}
UserDetails userDetails = new org.springframework.security.core.userdetails.User(users.get(0).getName(),
"xx", users.get(0).isEnabled(), true, true, true, authorities);
Authentication authentication = new UsernamePasswordAuthenticationToken(users.get(0).getName(), null,
userDetails.getAuthorities());
SecurityContextHolder.clearContext();
SecurityContextHolder.getContext().setAuthentication(authentication);
return "/index.html";
}
} else {
System.out.println("Invalid ID token.");
}
return "/error.html";
}
}

Related

Azure AD Auto signing out after signin

So ive gone through as many posts as I can, and I cant seem to sort this out!
My client wants us to allow logging into their ADFS via an MVC platform that I have built, so I am trying to allow them to sign into their Azure AD to sign into the platform.
When I am redirected to my signin page for Azure AD(MS Login), I type in my credentials and then it looks like it is doing a quick redirect loop and then automatically signs me out, I am going crazy!!!
Below is everything I have setup:
On Azure AD:
Created App service and put ApplicationId and TenantId in my Web.config
<add key="ida:ClientId" value="ApplicationID from AzureAD" />
<add key="ida:Tenant" value="TenantId from AzureAD" />
<add key="ida:AADInstance" value="https://login.microsoftonline.com/{0}" />
<add key="ida:RedirectUri" value="https://sitename.azurewebsites.net/Home/Index" />
<add key="ida:PostLogoutRedirectUri" value="https://sitename.azurewebsites.net" />
On Startup.Auth.cs
public partial class Startup
{
// Calling the keys values from Web.config file
private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
private static string tenant = ConfigurationManager.AppSettings["ida:Tenant"];
private static string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
private static string postLogoutRedirectUri = ConfigurationManager.AppSettings["ida:PostLogoutRedirectUri"];
// Concatenate aadInstance, tenant to form authority value
private string authority = string.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
// ConfigureAuth method
public void ConfigureAuth(IAppBuilder app)
{
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
//app.UseCookieAuthentication(new CookieAuthenticationOptions());
//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.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = (context) =>
{
context.HandleResponse();
context.OwinContext.Response.Redirect("/Home/Index");
return Task.FromResult(0);
}
}
});
} // end - ConfigureAuth method
On my routeConfig : This was done so that my custom landing page can be loaded first, on this page is a button saying "Enter platform", which the client will click on and go to Azure AD signin(MS Login page)
public static class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.LowercaseUrls = true;
routes.MapRoute("Default", "{controller}/{action}/{id}", new
{
controller = "Account",
action = "Login",
id = UrlParameter.Optional
}).RouteHandler = new DashRouteHandler();
}
}
Account Controller
[Authorize]
public void SignIn()
{
clsHomeScreen clsHomeScreen = new clsHomeScreen();
if (!Request.IsAuthenticated)
{
HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/" }, OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
Response.Redirect("/");
}
[AllowAnonymous]
[OutputCache(NoStore = true, Location = OutputCacheLocation.None)]
public ActionResult Login(string returnUrl)
{
// We do not want to use any existing identity information
EnsureLoggedOut();
// Store the originating URL so we can attach it to a form field
var viewModel = new AccountLoginModel { ReturnUrl = returnUrl };
return View(viewModel);
}
HomeController - This is where it SHOULD be redirected to after signing in, but its not:
[Authorize]
public async Task<ActionResult> Index()
{
HomeScreenLists HS = new HomeScreenLists();
IEnumerable<Challenges> ActiveChallenges;
IEnumerable<Challenges> PrivateChallenges;
string loggedInUserId = "";
string loggedInEmail = "";
var userClaims = User.Identity as System.Security.Claims.ClaimsIdentity;
string email = userClaims?.FindFirst(System.IdentityModel.Claims.ClaimTypes.Name)?.Value;
string firstname = userClaims?.FindFirst(System.IdentityModel.Claims.ClaimTypes.GivenName)?.Value;
string lastname = userClaims?.FindFirst(System.IdentityModel.Claims.ClaimTypes.Surname)?.Value;
string userId = "";
//The Email will not contain an #(i.e. an email address) if not using Azure AD to sign in.
if (!email.Contains("#"))
{
loggedInUserId = User.Identity.GetUserId();
goto LoggedInUser_Found;
}
if (TempData["LoggedInEmail"] != null)
{
if (email != TempData["LoggedInEmail"].ToString())
{
userId = clsHomeScreen.GetUserId(TempData["LoggedInEmail"].ToString());
}
else
{
userId = clsHomeScreen.GetUserId(email);
}
}
if (email != null)
{
userId = clsHomeScreen.GetUserId(email);
}
if (userId == null || userId == "")
{
clsUsers clsUsers = new clsUsers();
if (TempData["LoggedInEmail"] != null)
{
loggedInEmail = TempData["LoggedInEmail"].ToString();
var userDetails = clsUsers.GetUsers().Where(x => x.Email == loggedInEmail).FirstOrDefault();
loggedInUserId = userDetails.Id;
}
else
{
if(userId == null)
{
await RegisterAAD();
userId = clsHomeScreen.GetUserId(email);
loggedInUserId = userId;
}
else
{
loggedInUserId = User.Identity.GetUserId();
}
}
}
else
{
loggedInUserId = userId;
}
LoggedInUser_Found:
int iBU = (int)db.Users.FirstOrDefault(x => x.Id == loggedInUserId).fkiBusinessUnitId;
if (iBU == 0)
{
HS.HasBU = false;
TempData["HasBU"] = "No";
TempData["UserId"] = loggedInUserId;
}
else
{
HS.HasBU = true;
TempData["HasBU"] = "Yes";
TempData["UserId"] = loggedInUserId;
}
bool isAdmin = false;
if (User.IsInRole("Administrator"))
{
isAdmin = true;
}
ActiveChallenges = clsChallenges.GetActiveChallenges();
PrivateChallenges = clsChallenges.GetPrivateChallenges(loggedInUserId, isAdmin);
HS.HomeScreenList = clsHomeScreen.GetHomeScreenAdverts();
HS.ActiveChallengesList = ActiveChallenges;
HS.PrivateChallengesList = PrivateChallenges;
HS.UserId = loggedInUserId;
return View(HS);
}
So if I remove the [Authorize] attribute on the Index ActionResult, then it does a continuous redirect loop.
What ive tried:
I have tried using the KentorCookiSaver, which didnt work.
Recreating the app service
Changed the redirectUrl in Azure AD App Registration
Someone even spoke about rewriting the cookies, which I tried, but dont know if I followed the steps correctly, the link is Here
Ive tried so many things that I cant even remember what ive tried. Could anyone possibly help with what I am doing wrong, please.
Thanks a million!
So with some help from someone who knows these things, my problem was solved.
Ultimately what it came down to was:
I needed to add the RedirectUri into my Web.config and into my Startup.Auth
Web.Config
<add key="ida:RedirectUri" value="https://sitename.azurewebsites.net/Home/Index"/>
Startup.Auth
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
RedirectUri = redirectUri,
PostLogoutRedirectUri = postLogoutRedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = (context) =>
{
context.HandleResponse();
context.OwinContext.Response.Redirect("/Home/Index");
return Task.FromResult(0);
}
}
});
My Signin process kept routing back to my Account/Login page when failing, when it should have routed to my Home/Index, as I was using my Account/Login as my landing page and authentication only happening there after, the problem that happened here was because I did an "EnsureLogOut" on the Account/Login, thus why it kept logging me out first before wanting to authenticate. So instead of the Redirect = "/" I changed as follows:
public void SignIn()
`{`
`clsHomeScreen clsHomeScreen = new clsHomeScreen();`
`if (!Request.IsAuthenticated)`
`{`
`HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/Home/Index" }, OpenIdConnectAuthenticationDefaults.AuthenticationType);`
`}`
`Response.Redirect("/Home/Index");`
`}`
Perhaps this is something that might not help others, but maybe it helps them in the right direction.

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.

Connecting with Javascript to Web API protected by IdentityServer3

I have a Asp.NET MVC / WebAPI project with an embedded IdentityServer3.
I want both MVC and WebAPI to be protected by the IdentityServer. So I have used Authorize attribute on both MVC controllers and API controllers.
When surfing to my test page (which is protected) I get redirected to the IdentityServer login page. I enter my username and password and get authenticated and redirected back.
On the page I have a button that triggers a GET from javascript, with my access token in the authorization header, to my protected API. But here it fails with a 401 Unauthorized.
I get the access token to javascript by rendering it to the page with Razor.
I have 1 client in the IdentityServer set to hybrid flow. MVC uses cookies, while the API uses bearer tokens.
On my API HttpConfiguration I have set SuppressDefaultHostAuthentication. If I remove that line everything works, but then it uses cookies for the API which I don't want.
I use only HTTP and RequireSsl=false for now to avoid potential certificate problems.
I have tried for days to get this to work but I'm getting nowhere.
I don't even know how to debug this to get to cause of the 401.
By now I recognize just about every page that google suggests when I search for help.
Any ideas what it could be or how to debug this?
Here is my Startup.cs
public class Startup
{
public void Configuration(IAppBuilder app)
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Trace()
.CreateLogger();
// MVC client
string authBaseAddress = "http://localhost:50319/identity";
string tokenEndpoint = authBaseAddress + "/connect/token";
string userInfoEndpoint = authBaseAddress + "/connect/userinfo";
string redirectUri = "http://localhost:50319/";
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = "hybrid_clients",
Authority = authBaseAddress,
RedirectUri = redirectUri,
ResponseType = "code id_token token",
Scope = "openid profile roles sampleApi offline_access",
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role"
},
SignInAsAuthenticationType = "Cookies",
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = async n =>
{
// use the code to get the access and refresh token
var tokenClient = new TokenClient(
tokenEndpoint,
"hybrid_clients",
"secret");
var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(n.Code, n.RedirectUri);
if (tokenResponse.IsError)
{
throw new Exception(tokenResponse.Error);
}
// use the access token to retrieve claims from userinfo
var userInfoClient = new UserInfoClient(
new Uri(userInfoEndpoint),
tokenResponse.AccessToken);
var userInfoResponse = await userInfoClient.GetAsync();
// create new identity
var id = new ClaimsIdentity(n.AuthenticationTicket.Identity.AuthenticationType);
id.AddClaims(userInfoResponse.GetClaimsIdentity().Claims);
id.AddClaim(new Claim("access_token", tokenResponse.AccessToken));
id.AddClaim(new Claim("expires_at", DateTime.Now.AddSeconds(tokenResponse.ExpiresIn).ToLocalTime().ToString()));
id.AddClaim(new Claim("refresh_token", tokenResponse.RefreshToken));
id.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
id.AddClaim(new Claim("sid", n.AuthenticationTicket.Identity.FindFirst("sid").Value));
n.AuthenticationTicket = new AuthenticationTicket(
new ClaimsIdentity(id.Claims, n.AuthenticationTicket.Identity.AuthenticationType, "name", "role"),
n.AuthenticationTicket.Properties);
},
// Attach the id_token for the logout roundtrip to IdentityServer
RedirectToIdentityProvider = n =>
{
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest)
{
var idTokenHint = n.OwinContext.Authentication.User.FindFirst("id_token");
if (idTokenHint != null)
{
n.ProtocolMessage.IdTokenHint = idTokenHint.Value;
}
}
return Task.FromResult(0);
}
}
});
AntiForgeryConfig.UniqueClaimTypeIdentifier = Constants.ClaimTypes.Subject;
JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>();
// web api
app.Map("/api", a =>
{
var config = new HttpConfiguration();
a.UseCors(CorsOptions.AllowAll);
a.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
//AuthenticationMode = AuthenticationMode.Active,
Authority = authBaseAddress,
RequiredScopes = new[] { "sampleApi" },
DelayLoadMetadata = true
});
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
config.MapHttpAttributeRoutes();
a.UseWebApi(config);
});
// Identity server
app.Map("/identity", idsrvApp =>
{
idsrvApp.UseCors(CorsOptions.AllowAll);
idsrvApp.UseIdentityServer(new IdentityServerOptions
{
SiteName = "Embedded IdentityServer",
SigningCertificate = LoadCertificate(),
Factory = new IdentityServerServiceFactory()
.UseInMemoryUsers(Users.Get())
.UseInMemoryClients(Clients.Get())
.UseInMemoryScopes(Scopes.Get()),
AuthenticationOptions = new IdentityServer3.Core.Configuration.AuthenticationOptions()
{
EnablePostSignOutAutoRedirect = true // Automatically redirects back to the client on signout
},
RequireSsl = false,
});
});
}
X509Certificate2 LoadCertificate()
{
return new X509Certificate2(
string.Format(#"{0}\bin\idsrv3test.pfx", AppDomain.CurrentDomain.BaseDirectory), "idsrv3test");
}
}
My client
new Client
{
Enabled = true,
ClientName = "Hybrid Clients",
ClientId = "hybrid_clients",
Flow = Flows.Hybrid,
//AllowAccessTokensViaBrowser = false,
RedirectUris = new List<string>
{
"http://localhost:50319/"
},
PostLogoutRedirectUris = new List<string>
{
"http://localhost:50319/"
},
AllowedScopes = new List<string>
{
"openid",
"profile",
"email",
"roles",
"address",
"all_claims",
"sampleApi",
"offline_access"
},
ClientSecrets = new List<Secret>
{
new Secret("secret".Sha256())
},
AccessTokenType = AccessTokenType.Reference,
LogoutSessionRequired = true
},
My scopes
public static class Scopes
{
public static IEnumerable<Scope> Get()
{
var scopes = new List<Scope>
{
StandardScopes.OpenId,
StandardScopes.Profile,
StandardScopes.Email,
StandardScopes.Address,
StandardScopes.OfflineAccess,
StandardScopes.RolesAlwaysInclude,
StandardScopes.AllClaims,
new Scope
{
Enabled = true,
Name = "roles",
Type = ScopeType.Identity,
Claims = new List<ScopeClaim>
{
new ScopeClaim("role")
}
},
new Scope
{
Enabled = true,
DisplayName = "Sample API",
Name = "sampleApi",
Description = "Access to a sample API",
Type = ScopeType.Resource
}
};
return scopes;
}
}
My API
[Authorize]
public class SecuredApiController : ApiController
{
public IHttpActionResult Get()
{
var user = User as ClaimsPrincipal;
var claims = from c in user.Claims
select new
{
type = c.Type,
value = c.Value
};
return Json(claims);
}
}
Part of my Razor view
<button data-bind="click:callApi">Call API</button>
<span data-bind="text:apiResult"></span>
<script>
$(function() {
ko.myViewModel = new ClientAppViewModel('#ViewData["access_token"]');
ko.applyBindings(ko.myViewModel);
});
</script>
My JavaScript (KnockoutJS) that calls SecuredApi
function ClientAppViewModel(accessToken) {
var self = this;
self.accessToken = accessToken;
self.apiResult = ko.observable('empty');
self.callApi = function () {
console.log('CallApi');
var xhr = new XMLHttpRequest();
xhr.open("GET", "http://localhost:50319/api/SecuredApi");
xhr.onload = function () {
self.apiResult(JSON.stringify(JSON.parse(xhr.response), null, 2));
};
xhr.setRequestHeader("Authorization", "Bearer " + self.accessToken);
xhr.send();
}
}
The Katana logs for the API are what you want. With these you will see why the API is returning a 401.
You can access these using the Microsoft.Owin log source (see Katana Documentation)

ASP.NET Identity 2.1 - Password Reset Invalid Tokens

ASP.NET Identity is returning an 'Invalid token.' response when resetting a password for users.
I've tried the following:
URL Encode the code before sending email
URL Encode & Decode the code before and after
Copying the code to make sure it matches what was sent
Ensured my user email is confirmed (I heard that could be a problem)
Created a custom UserManager/Store etc.
This is my email code:
var user = await UserManager.FindByNameAsync(model.Email);
var code = await UserManager.GeneratePasswordResetTokenAsync(user.Id);
var callbackUrl = Url.Action("ResetPassword", "Account", new { code }, "http");
var body = string.Format("Click here to reset your password: {0}", callbackUrl);
await UserManager.SendEmailAsync(user.Id, "Reset Password", body);
return View("~/Views/Account/Login.cshtml", model);
The generated URL:
http://localhost/Account/ResetPassword?code=XTMg3fBDDR77LRptnRpg7r7oDxz%2FcvGscq5Pm3HMe8RJgX0KVx6YbOeqflvVUINipVcXcDDq1phuj0GCmieCuawdgfQzhoG0FUH4BoLi1TxY2kMljGp1deN60krGYaJMV6rbkrDivKa43UEarBHawQ%3D%3D
Finally my reset code:
if (!ModelState.IsValid)
{
return View(model);
}
var user = await UserManager.FindByNameAsync(model.Email);
if (user == null)
{
// Don't reveal that the user does not exist
return RedirectToAction("ResetPasswordConfirmation", "Account");
}
var result = await UserManager.ResetPasswordAsync(user.Id, model.Code, model.Password);
if (result.Succeeded)
{
return RedirectToAction("ResetPasswordConfirmation", "Account");
}
ModelState.AddModelError("","Invalid Password Please Try Again");
return View();
Inside the result is 1 error, Invalid token.
My create UserManager method:
public static CustomerUserManager Create(IdentityFactoryOptions<CustomerUserManager> options, IOwinContext context)
{
var manager = new CustomerUserManager(new CustomerUserStore(context.Get<CustomerDbContext>()));
// Configure validation logic for usernames
manager.UserValidator = new UserValidator<Customer>(manager)
{
AllowOnlyAlphanumericUserNames = false,
RequireUniqueEmail = true
};
// Configure validation logic for passwords
manager.PasswordValidator = new PasswordValidator
{
RequiredLength = 6,
RequireNonLetterOrDigit = true,
RequireDigit = true,
RequireLowercase = true,
RequireUppercase = true,
};
manager.EmailService = new EmailService();
var dataProtectionProvider = options.DataProtectionProvider;
if (dataProtectionProvider != null)
{
manager.UserTokenProvider = new DataProtectorTokenProvider<Customer, string>(dataProtectionProvider.Create("ASP.NET Identity"));
}
return manager;
}
My Startup.Auth config:
app.CreatePerOwinContext(CustomerDbContext.Create);
app.CreatePerOwinContext<CustomerUserManager>(CustomerUserManager.Create);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity =
SecurityStampValidator.OnValidateIdentity<CustomerUserManager, Customer, string>
(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentityCallback: (manager, user) => user.GenerateUserIdentityAsync(manager),
getUserIdCallback: (id) => (id.GetUserId())
)
}
});
List of tried solutions:
ASP.NET Identity 2 - UserManager.ConfirmEmail Fails In Production
aspnet identity invalid token on confirmation email
http://www.asp.net/mvc/overview/security/create-an-aspnet-mvc-5-web-app-with-email-confirmation-and-password-reset#reset
Asp.NET - Identity 2 - Invalid Token Error
aspnet identity invalid token on confirmation email
https://aspnetidentity.codeplex.com/discussions/544368
Thanks for any help with this problem.
You can try this code.
I shared this link: aspnet identity invalid token on confirmation email
var encodedCode= code.Base64ForUrlEncode();
var decodedCode= encodedCode.Base64ForUrlDecode();
public static class UrlEncoding
{
public static string Base64ForUrlEncode(this string str)
{
byte[] encbuff = Encoding.UTF8.GetBytes(str);
return HttpServerUtility.UrlTokenEncode(encbuff);
}
public static string Base64ForUrlDecode(this string str)
{
byte[] decbuff = HttpServerUtility.UrlTokenDecode(str);
return Encoding.UTF8.GetString(decbuff);
}
}

How to get Facebook first and last name values using ASP.NET MVC 5 and OWIN?

I know that a 'Name' field is provided, but I would prefer to access the first and last names explicitly. Can someone help with this? I'm still wrapping my head around ASP.Net MVC.
In your Startup.Auth.cs ConfigureAuth(IAppBuilder app) method, set the following for Facebook:
var x = new FacebookAuthenticationOptions();
x.Scope.Add("email");
x.AppId = "*";
x.AppSecret = "**";
x.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"));
}
}
};
x.SignInAsAuthenticationType = DefaultAuthenticationTypes.ExternalCookie;
app.UseFacebookAuthentication(x);
/*
app.UseFacebookAuthentication(
appId: "*",
appSecret: "*");
* */
Then use this to access the user's login info:
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
And then the following to get the first name:
var firstNameClaim = loginInfo.ExternalIdentity.Claims.First(c => c.Type == "urn:facebook:first_name");
Facebook changed its permission api. You can get more information about it here: https://developers.facebook.com/docs/facebook-login/permissions
Name need public_profile permission
var facebookAuthenticationOptions = new FacebookAuthenticationOptions()
{
AppId = "appId",
AppSecret = "key"
};
facebookAuthenticationOptions.Scope.Add("email");
facebookAuthenticationOptions.Scope.Add("public_profile");
app.UseFacebookAuthentication(facebookAuthenticationOptions);
And you can get it using:
var loginInfo = await authenticationManager.GetExternalLoginInfoAsync();
loginInfo.ExternalIdentity.Claims.First(c => c.Type == "urn:facebook:name")
authenticationManager is an instance, you can get using:
HttpContext.GetOwinContext().Authentication;
Unfortunately this method doesn't work anymore since Facebook changed their default return values with API update 2.4
It looks like the only way to get the first_name etc. now is to use the Facebook Graph API (like this posts suggests).
I also found this post on the Katana project site that addresses this issue and already submitted a pull request but it has not been merged jet.
Hopefully this safes somebody a little bit of time ;)
As of 2017, this is the code that is working for me(Thanks to David Poxon's code above). Make sure you have upgraded to version 3.1.0 of Microsoft.Owin.Security.Facebook.
In the Startup.Auth.cs (or Startup.cs in some cases), place this code:
app.UseFacebookAuthentication(new FacebookAuthenticationOptions()
{
AppId = "***",
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"));
}
}
}
});
Then in your controller's external login callback method, add this code:
var firstName = loginInfo.ExternalIdentity.Claims.First(c => c.Type == "urn:facebook:first_name").Value;
Likewise for getting last name, use the above line and replace the urn:facebook:first_name with urn:facebook:last_name
private Uri RedirectUri
{
get
{
var uriBuilder = new UriBuilder(Request.Url);
uriBuilder.Query = null;
uriBuilder.Fragment = null;
uriBuilder.Path = Url.Action("FacebookCallback");
return uriBuilder.Uri;
}
}
[AllowAnonymous]
public ActionResult Facebook()
{
var fb = new FacebookClient();
var loginUrl = fb.GetLoginUrl(new
{
client_id = "296002327404***",
client_secret = "4614cd636ed2029436f75c77961a8***",
redirect_uri = RedirectUri.AbsoluteUri,
response_type = "code",
scope = "email" // Add other permissions as needed
});
return Redirect(loginUrl.AbsoluteUri);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult LogOff()
{
FormsAuthentication.SignOut();
return View("Login");
}
public ActionResult FacebookCallback(string code)
{
var fb = new FacebookClient();
dynamic result = fb.Post("oauth/access_token", new
{
client_id = "296002327404***",
client_secret = "4614cd636ed2029436f75c77961a8***",
redirect_uri = RedirectUri.AbsoluteUri,
code = code
});
var accessToken = result.access_token;
// Store the access token in the session for farther use
Session["AccessToken"] = accessToken;
// update the facebook client with the access token so
// we can make requests on behalf of the user
fb.AccessToken = accessToken;
// Get the user's information
dynamic me = fb.Get("me?fields=first_name,middle_name,last_name,id,email");
string email = me.email;
string firstname = me.first_name;
string middlename = me.middle_name;
string lastname = me.last_name;
db.Insert_customer(firstname,email,null,null,null,null,null,null,null,null,null,null,1,1,System.DateTime.Now,1,System.DateTime.Now);
// Set the auth cookie
FormsAuthentication.SetAuthCookie(email, false);
return RedirectToAction("Index", "Home");
}
}
}
As of Jan 2019, I wanted to confirm how to do this and provide a few extra bits (there is a lot of conflicting info out there depending on what year the answer was written!). David and Waqas have the best answers (IMO). I'm using MVC5, AspNetIdentity 2 and IdentityServer 3.
First, your identity provider configuration for Facebook:
app.UseFacebookAuthentication(new FacebookAuthenticationOptions
{
AuthenticationType = "facebook",
Caption = "Login with Facebook",
SignInAsAuthenticationType = signInAsType,
AppId = ConfigurationManager.AppSettings["FacebookAppId"],
AppSecret = ConfigurationManager.AppSettings["FacebookAppSecret"],
Provider = new FacebookAuthenticationProvider()
{
OnAuthenticated = ctx =>
{
foreach (var claim in ctx.User)
{
var claimType = $"urn:facebook:{claim.Key}";
var claimValue = claim.Value.ToString();
if (!ctx.Identity.HasClaim(claim.Key, claimValue))
{
ctx.Identity.AddClaim(new Claim(claim.Key, claimValue));
}
}
return Task.FromResult(0);
}
}
});
Unlike some of the other answers, this combines the extra requested fields with what you get by default, and takes the urn:facebook: off the front of the claim so it matches the default claim naming scheme.
You don't need to add any additional Scopes or Fields (at least, not for first and last name). Version 4.1 of Microsoft.Owin.Security.Facebook already does this for you. The source code for the FacebookAuthenticationOptions is here. Relevant bits:
public FacebookAuthenticationOptions()
: base(Constants.DefaultAuthenticationType)
{
Caption = Constants.DefaultAuthenticationType;
CallbackPath = new PathString("/signin-facebook");
AuthenticationMode = AuthenticationMode.Passive;
Scope = new List<string>();
BackchannelTimeout = TimeSpan.FromSeconds(60);
SendAppSecretProof = true;
_fields = new HashSet<string>();
CookieManager = new CookieManager();
AuthorizationEndpoint = Constants.AuthorizationEndpoint;
TokenEndpoint = Constants.TokenEndpoint;
UserInformationEndpoint = Constants.UserInformationEndpoint;
Scope.Add("public_profile");
Scope.Add("email");
Fields.Add("name");
Fields.Add("email");
Fields.Add("first_name");
Fields.Add("last_name");
}
If you are using IdentityServer 3 (like I am), then you will need to grab these claims on authentication in your custom UserService like so:
public async override Task AuthenticateExternalAsync(ExternalAuthenticationContext ctx)
{
// first, lets see if we have enough data from this external provider
// at a minimum, FirstName, LastName, and Email are required
string email = null;
string firstName = null;
string lastName = null;
var idp = ctx.ExternalIdentity.Provider;
email = GetClaimValue(ctx, "email");
if (idp == "google")
{
firstName = GetClaimValue(ctx, "given_name");
lastName = GetClaimValue(ctx, "family_name");
}
else if (idp == "facebook")
{
firstName = GetClaimValue(ctx, "first_name");
lastName = GetClaimValue(ctx, "last_name");
}
var missingClaims = "";
if (email == null)
{
missingClaims = "email";
}
if (firstName == null)
{
if (missingClaims.Length > 0) { missingClaims += ", "; }
missingClaims += "first name";
}
if (lastName == null)
{
if (missingClaims.Length > 0) { missingClaims += ", "; }
missingClaims += "last name";
}
if (missingClaims.Length > 0)
{
var errorMessage = $"The external login provider didn't provide the minimum required user profile data. Missing: {missingClaims} " +
"Verify that these fields are specified in your external login provider user profile and that you have allowed external apps (i.e. this one) access to them. " +
"Alternatively, you can try a different external login provider, or create a local acount right here.";
ctx.AuthenticateResult = new AuthenticateResult(errorMessage);
return;
}
var login = new Microsoft.AspNet.Identity.UserLoginInfo(ctx.ExternalIdentity.Provider, ctx.ExternalIdentity.ProviderId);
var user = await _userManager.FindAsync(login);
if (user == null)
{
// this user either does not exist or has not logged in with this identity provider
// let's see if they already exist (by checking to see if there is a user account with this email address)
user = await _userManager.FindByEmailAsync(email);
if (user == null)
{
// there is no existing user with this email, therefore, a new user will be created
user = new MotoTallyUser()
{
Id = Guid.NewGuid(),
UserName = email,
Email = email,
EmailConfirmed = true,
FirstName = firstName,
LastName = lastName
};
await _userManager.CreateAsync(user);
await _userManager.AddLoginAsync(user.Id, login);
}
else
{
// this user DOES exist (matched email provided by external login provider)
// however, they have not logged in with this identity provider
// therefore, update the user info with that reported by the external identity provider, and add the external login
user.UserName = email;
user.Email = email;
user.EmailConfirmed = true;
user.FirstName = firstName;
user.LastName = lastName;
await _userManager.UpdateAsync(user);
await _userManager.AddLoginAsync(user.Id, login);
}
}
else
{
// this user DOES exist (they already have an external login on record)
// therefore, update the user info with that reported by the external identity provider (no need to add external login, its already there)
user.UserName = email;
user.Email = email;
user.EmailConfirmed = true;
user.FirstName = firstName;
user.LastName = lastName;
await _userManager.UpdateAsync(user);
}
ctx.AuthenticateResult = new AuthenticateResult(user.Id.ToString(), user.Email, null, ctx.ExternalIdentity.Provider);
return;
}
private string GetClaimValue(ExternalAuthenticationContext ctx, string claimType)
{
if (ctx.ExternalIdentity.Claims.FirstOrDefault(x => x.Type == claimType) != null)
{
return ctx.ExternalIdentity.Claims.FirstOrDefault(x => x.Type == claimType).Value;
}
return null;
}
Hope this helps someone!
For VB.NET developers this is the code In your Startup.Auth.vb
Dim fb = New FacebookAuthenticationOptions()
fb.Scope.Add("email")
fb.AppId = "*"
fb.AppSecret = "*"
fb.Provider = New FacebookAuthenticationProvider() With
{
.OnAuthenticated = Async Function(context)
context.Identity.AddClaim(New System.Security.Claims.Claim("FacebookAccessToken", context.AccessToken))
For Each claim In context.User
Dim claimType = String.Format("urn:facebook:{0}", claim.Key)
Dim claimValue As String = claim.Value.ToString()
If Not context.Identity.HasClaim(claimType, claimValue) Then context.Identity.AddClaim(New System.Security.Claims.Claim(claimType, claimValue, "XmlSchemaString", "Facebook"))
Next
End Function
}
fb.SignInAsAuthenticationType = DefaultAuthenticationTypes.ExternalCookie
app.UseFacebookAuthentication(fb)
Facebook has changed the way their Graph API returns value in upgrade 2.4. Now you need to explicitly specify all the fields that you want to get back.
See this note from: facebook for developers Upgrade Info:
Graph API changes in version 2.4
In the past, responses from Graph API calls returned a set of default fields. In order to reduce payload size and improve latency on mobile
networks we have reduced the number of default fields returned for
most Graph API calls. In v2.4 you will need to declaratively list the
response fields for your calls.
To get Email, FirstName and LastName from facebook:
First, you need to install Facebook SDK for .NET nuget package
Then, in your startup.Auth.cs, change the configuration of Facebook Authentication as follow:
app.UseFacebookAuthentication(new FacebookAuthenticationOptions
{
// put your AppId and AppSecret here. I am reading them from AppSettings
AppId = ConfigurationManager.AppSettings["FacebookAppId"],
AppSecret = ConfigurationManager.AppSettings["FacebookAppSecret"],
Scope = { "email" },
Provider = new FacebookAuthenticationProvider
{
OnAuthenticated = context =>
{
context.Identity.AddClaim(new System.Security.Claims.Claim("FacebookAccessToken", context.AccessToken));
return Task.FromResult(true);
}
}
});
// this is no longer needed
//app.UseFacebookAuthentication(
// appId: ConfigurationManager.AppSettings["FacebookAppId"],
// appSecret: ConfigurationManager.AppSettings["FacebookAppSecret"]);
Finally, in your AccountController, add the following code to
ExternalLoginCallback method:
if (string.Equals(loginInfo.Login.LoginProvider, "facebook", StringComparison.CurrentCultureIgnoreCase))
{
var identity = AuthenticationManager.GetExternalIdentity(DefaultAuthenticationTypes.ExternalCookie);
var access_token = identity.FindFirstValue("FacebookAccessToken");
var fb = new FacebookClient(access_token);
// you need to specify all the fields that you want to get back
dynamic myInfo = fb.Get("/me?fields=email,first_name,last_name");
string email = myInfo.email;
string firstName = myInfo.first_name;
string lastName = myInfo.last_name;
}
See facebook API Guid for more parameters that you can get back.
Add firstname and last name in facebook option Scope
var facebookOptions = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationOptions()
{
AppId = "your app id",
AppSecret = "your app secret",
};
facebookOptions.Scope.Add("email");
facebookOptions.Scope.Add("first_name");
facebookOptions.Scope.Add("last_name");
return facebookOptions;

Resources