I have an ASP.NET MVC 4 application.
Yesterday my users started to complain they cannot login using their Google accounts. After lots of googling I found this: DotNetOpenAuth.GoogleOAuth2. I followed the instructions.
I created Client ID for web applications in Google console.
In AuthConfig.RegisterAuth() I have:
var client = new DotNetOpenAuth.GoogleOAuth2.GoogleOAuth2Client(googleClientID, googleClientSecret);
var extraData = new Dictionary<string, object>();
OAuthWebSecurity.RegisterClient(client, "Google", extraData);
In AccountController, I have something like this:
public ActionResult ExternalLoginCallback(string returnUrl)
{
DotNetOpenAuth.GoogleOAuth2.GoogleOAuth2Client.RewriteRequest();
AuthenticationResult result = OAuthWebSecurity.VerifyAuthentication(Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl }));
if (!result.IsSuccessful)
{
return RedirectToAction("ExternalLoginFailure");
}
if (OAuthWebSecurity.Login(result.Provider, result.ProviderUserId, createPersistentCookie: false))
{
// here I have some logic where is user sent when login was successfull
return RedirectToLocal(returnUrl);
}
if (User.Identity.IsAuthenticated)
{
// If the current user is logged in add the new account
OAuthWebSecurity.CreateOrUpdateAccount(result.Provider, result.ProviderUserId, User.Identity.Name);
return RedirectToLocal(returnUrl);
}
else
{
// User is new, ask for their desired membership name
string loginData = OAuthWebSecurity.SerializeProviderUserId(result.Provider, result.ProviderUserId);
// some logic
return View("ExternalLoginConfirmation", new RegisterExternalLoginModel { UserName = username, ExternalLoginData = loginData, EncryptedEmail = encryptedEmail });
}
}
I have two problems:
Before the change, the result.UserName contained the users email. Now it contains name. But I need email. Except for this, registration works fine.
My biggest problem - existing users cannot log in using their google account. The code goes to "// User is new, ask for their desired membership name" for them. The ProviderUserId I get now is different for the same email address.
Thanks a lot for any advice.
Can you configure this library to pass additional parameters to the Google authorization service? If so, you should pass 'openid.realm=$your_app_openid2_realm' (if your app was configured for OpenID2 before, it most likely asserted a 'realm' value in its requests, you should use the same value).
In this case, you'll receive two identifiers from Google. The new one (which is compatible with profile URLs and overall more Google APIs) and the old one (returned as openid_id).
Related
I have a web app that allows a user to pay for a holiday booking.
The user will log in using a combination of a unique booking ID and a booking password.
Is there a simple way I can authenticate the user once they have successfully logged in. Is it a good idea to use the .net session object for this purpose?
The app wont be load balanced so I don't need to worry about storing session across different machines.
I would like to not have to use .Net membership as I don't want to create any extra tables in the database.
If you are able to retrieve the user, I guess this is possible. Below is the way I would try:
public ActionResult UrlAuth(string bookingId, string bookingPass)
{
var userManager=HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
// Retrieve the user by the give booking values (Custom part in your app
string userId= _bookingRepository.GetUserIdByBooking(bookingId, bookinggPass);
var user = userManager.FindById(userId);
if (user != null)
{
var userIdentity = userManager.CreateIdentity(user, DefaultAuthenticationTypes.ApplicationCookie);
HttpContext.GetOwinContext().Authentication.SignIn(new AuthenticationProperties { IsPersistent = false }, userIdentity );
// authentication succeed do what you want
return Redirect("Wherever");
}
ModelState.AddModelError("", "Invalid booking values");
return View();
}
Here's a good general article with custom authentication for MVC
http://www.ryadel.com/en/http-basic-authentication-asp-net-mvc-using-custom-actionfilter/
http://www.codeproject.com/Articles/1005485/RESTful-Day-sharp-Security-in-Web-APIs-Basic
I have a dedicated web api identity server used by potentially multiple client applications. Now I am trying to get the flow for Facebook authentication work. The process is very similar to the one found in microsoft examples. Here's an image of the flow:
Basically my problem is that once last call is finished successfully, browser is not redirected to client application (localhost:9053).
ExternalLogin in API:
[OverrideAuthentication]
[HostAuthentication(DefaultAuthenticationTypes.ExternalCookie)]
[AllowAnonymous]
[Route("api/externalLogin", Name = "externallogin")]
public async Task<IHttpActionResult> GetExternalLogin(string provider)
{
if (User == null || !User.Identity.IsAuthenticated)
{
return new ChallengeResult(provider, this);
}
ExternalLoginData externalLogin = FromIdentity(provider, User.Identity as ClaimsIdentity);
if (externalLogin == null)
{
return InternalServerError();
}
if (externalLogin.LoginProvider != provider)
{
Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);
return new ChallengeResult(provider, this);
}
IdentityUser user = await UserManager.FindByIdAsync(externalLogin.UserName);
Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);
ClaimsIdentity oAuthIdentity = await _userManager.CreateIdentityAsync(user, OAuthDefaults.AuthenticationType);
ClaimsIdentity cookieIdentity = await _userManager.CreateIdentityAsync(user, CookieAuthenticationDefaults.AuthenticationType);
IDictionary<string, string> data = new Dictionary<string, string>
{
{ "userName", user.UserName }
};
var properties = new AuthenticationProperties(data);
Authentication.SignIn(properties, oAuthIdentity, cookieIdentity);
return Ok();
}
Given a user who is signed into their facebook account and have already accepted permission for this application, it performs the tasks in the image, which ends up coming all the way to the bottom of this method and returning Ok() (200). After this step, I expected owin facebook middleware to intercept and redirect the user to the client user localhost:9053 but that is not happening and it just hangs there.
Any ideas anyone?
When you register your application with Facebook, they ask you to provide a Site URL. (Go to https://developers.facebook.com/, then look under: Settings > Website > Site URL)
The Site URL is also used by Facebook as a server whitelist. In other words, Facebook will only respond to a request with a redirect if the redirect_uri matches the Site URL exactly. The reason they do this is to protect you against an attack called an Open Redirect.
Make both servers share the same hostname by proxying one of them.
I have an MVC5 application that is using Oauth and is logging in correctly however I want to save the returned email to the database and am having trouble doing this.
I have the following code in startup.auth.cs:
The below code adds the claim fine and I can the email claim in the debugger, however if it is the users first login then the user isn't actually created at this point so I can't update the user here.
LinkedInAuthenticationOptions x = new LinkedInAuthenticationOptions();
x.ClientId = "key";
x.ClientSecret = "key";
x.Scope.Add("r_emailaddress");
x.Provider = new LinkedInAuthenticationProvider()
{
OnAuthenticated = async context =>
{
//Get the access token
context.Identity.AddClaim(
new System.Security.Claims.Claim("Email", context.Email));
}
};
app.UseLinkedInAuthentication(x);
In the below code (in the default project accountcontroller.cs) I try to access the email claim that I added above however it is unable to find the claim...
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
ClaimsIdentity identity = UserManager.CreateIdentity(user, DefaultAuthenticationTypes.ApplicationCookie);
}
Does anybody know how I can access the email claim that I added in startup.auth.cs ?
You want to do something similar to this blog post, just store the email claim instead of the Facebook access token.
How to get more data from external providers?
I'm trying to implement OAuth with twitter so my users can log into my site using their Twitter IDs. For this I am using the TweetSharp library. Following the examples they have I wrote the following code which seems to work.
public ActionResult Login(string oauth_token, string oauth_verifier)
{
var service = new TwitterService(consumerKey, consumerSecret);
if (oauth_token == null)
{
var requestToken = service.GetRequestToken(Request.Url.ToString());
var uri = service.GetAuthorizationUri(requestToken);
return new RedirectResult(uri.ToString(), false);
}
else
{
var requestToken = new OAuthRequestToken { Token = oauth_token };
OAuthAccessToken accessToken = service.GetAccessToken(requestToken, oauth_verifier);
service.AuthenticateWith(accessToken.Token, accessToken.TokenSecret);
TwitterUser user = service.VerifyCredentials(new VerifyCredentialsOptions());
TempData["response"] = string.Format("Your username is {0}", user.ScreenName);
return RedirectToAction("Success");
}
}
public ActionResult Success()
{
ViewBag.Response = TempData["response"];
return View();
}
However, there is a problem. Every time the user logs into the system twitter asks them to authorize the application even though they have done it before. Is there a way to prevent this behavior? I have also implemented OAuth with Facebook and Google and I don't need to authorize the application every time I want to log into the system.
Unfortunately not as far as I'm aware. Twitter uses OAuth 1.1 rather than OAuth 2.0 like Facebook and Google, so there is a manual step in the middle in which users are asked to authorise the application even though they have done already. I'm having exactly the same issue and it appears to be something we have to live with.
I was having the same problem with a slightly older app I was trying to resurrect. I noticed that the problem went away when I selected "Sign in with Twitter" in my app settings at http://dev.twitter.com/apps.
I'm trying to approximate Single Sign on. Currently, the most workable solution involves the user imputing details on my site before being sent off to goggle Apps to authenicate.
I'm using dotnetopenauth to send and recieve the requests
This means that it takes no account of google apps login cookies should they be present.
There are several questions covering this already. But none seem to have an answer to this.
This is the current code (note that I'm constraining the possible domains that can be used to just google apps for my domain):
public ActionResult Authenticate(string returnUrl)
{
string input=Request.Form["openid_identifier"]+"#example.com";
openid.DiscoveryServices.Clear();
openid.DiscoveryServices.Insert(0, GoogleAppsDiscovery); // it should be first if we don't clear the other discovery services
var response = openid.GetResponse();
if (response == null)
{
// Stage 2: user submitting Identifier
Identifier id;
if (Identifier.TryParse(input, out id))
{
try
{
return openid.CreateRequest(input).RedirectingResponse.AsActionResult();
}
catch (ProtocolException ex)
{
ViewData["Message"] = ex.Message;
return View("Login");
}
}
else
{
ViewData["Message"] = "Invalid identifier";
return View("Login");
}
}
else
{
// Stage 3: OpenID Provider sending assertion response
switch (response.Status)
{
case AuthenticationStatus.Authenticated:
FormsAuthentication.SetAuthCookie(response.ClaimedIdentifier, false);
var results = userstable.Select(response.FriendlyIdentifierForDisplay);
if (results.Count() > 0)
{
Session["FriendlyIdentifier"] = results.ElementAt(0).UserFName;
return RedirectToAction("Index", "Home", results.ElementAt(0).UserID);
}
else
{
UsersDataModel user = new UsersDataModel();
user.OpenID = response.ClaimedIdentifier.ToString();
user.UserID = Utils.HashToBase64(response.FriendlyIdentifierForDisplay);
user.Type = "Empolyee";
userstable.Insert(user);
//return RedirectToAction("Register");
}
if (!string.IsNullOrEmpty(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
case AuthenticationStatus.Canceled:
ViewData["Message"] = "Canceled at provider";
return View("Login");
case AuthenticationStatus.Failed:
ViewData["Message"] = response.Exception.Message;
return View("Login");
}
}
return new EmptyResult();
}
The only difference between this code and the normal pattern are the following lines:
openid.DiscoveryServices.Clear();
openid.DiscoveryServices.Insert(0, GoogleAppsDiscovery); // it should be first if we don't clear the other discovery services
These lines correctly setup OpenId to process the response from google apps.
As I said, this works only if the user inputs his/her email address first. i can't find a way of automatically redirecting to the Google Apps Login
In normal gmail, if I pass the user off to: https://www.google.com/accounts/o8/id instead of processing the input, google will have the user input login details on their site rather than on mine. It will skip that if there are cookies and just pass the user right back to my site with no fuss.
I would like to have the same behavior for my site for the google apps logins.
Additionally, it would be nice if I could constrain the logins to my google apps domain.
There are a couple of URLs floating around:
https://www.google.com/accounts/o8/site-xrds?hd=example.com
or
https://www.google.com/a/example.com/o8/id
But even with those lines of code I pointed out earlier, dontnetopenauth refuses to see an openid endpoint at either address.
Even the sample in dotnetaopenauth for webforms still requires user input.
Any help would be most appreciated.
You can't have no user input and any Google Apps Domain work for the user, because the user may be logged into 15 Google Apps for Domains accounts, and then no one would know which account the user means to log in with.
But since you say you'd like them to only be able to log in with your own domain, that's easy. Skip asking the user, and just pretend "example.com" was the user input and pass that into DotNetOpenAuth. It doesn't have to be an email address. Only the domain name is used anyway.
That said, that doesn't guarantee no one from other Providers or domains can log into your site. To do that, you need to filter on the IAuthenticationResponse that comes back and make sure its Provider property matches the one value you intend to allow. Otherwise, "unsolicited assertions" can still come in.