DotNetOpenAuth Authentication against Google Apps OpenID using MVC - asp.net-mvc

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.

Related

problems with DotNetOpenAuth OAuth2 Client for Google

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).

RedirectToAction after successful post to show a message

When people register on my ASP.NET MVC website I send an activation link via email so they can activate their account and login. I want to show a message when the registration is successful and the email is sent. To do that I redirect to another page.
I do not want to show this page when people go directly to this URL because it is not a normal page. I use TempData to check if they are coming from the registration page.
public ActionResult Register()
{
AccountRegisterView accountView = InitializeAccountRegisterViewWithIssue(false, "");
return View(accountView);
}
[HttpPost]
public ActionResult Register(AccountRegisterView accountView)
{
if (!ModelState.IsValid)
{
return View(accountView);
}
// Register user and send activation link via email...
TempData["success"] = true;
return RedirectToAction("RegisterEmail");
}
public ActionResult RegisterEmail()
{
if (TempData["success"] != null)
{
return View();
}
return RedirectToAction("Login");
}
I would like to know if this is considered best practice. Or should I do this differently?
I think this is a perfectly good use of TempData[]. You need a variable for a one time request to decide if you should show the page or not.
I suppose you could use a session variable, but you would need to remember to clear the session. You could use some type of registration key, but then you would need to track those as well. Long story short, nope, you are good.

When using Twitter OAuth with TweetSharp I keep getting asked to authorize my app every time a user wants to log in

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.

FOSOAuthServerBundle - generate manually an access token

I have a symfony2 website and a web service secured via oauth using FOSOAuthServerBundle accessible via an iOS app. On the website I use FOSUserBundle and FOSFacebookBundle.
The only thing I miss is giving the possibility to the user to login with facebook on the iOS app and to return him an access_token for my oauth linked to his user account so that he can access my api like other users.
So basically I want to send the user facebookID and facebook_access_token to my webservice, check that the user is correct (token matches my app) and return an authentication token.
QUESTION: is there an easy way to add a "Facebook" grant_type to FOSOAuthServerBundle ?
I know some people have done this seeing these questions:
Design for Facebook authentication in an iOS app that also accesses a secured web service
Get application id from user access token (or verify the source application for a token)
But they do not explain how, they do not seem to use FOSOauthServerBundle and questions are quite old.
I have tried using this bundle:
https://github.com/TheFootballSocialClub/FSCOAuth2FacebookGrantBundle
but this bundle has been downloaded only 9 times before me and is not fully suited to my app (it considers that a Facebook user username equals his facebookId for instance). So I guess what I want to do is re-implement the same kind of thing my own way.
If anyone has already done that our could provide any guidance it would be very much appreciated.
Thank you
To do this, you have to add a Grant Extensions, see the official document "Adding Grant Extensions" : https://github.com/FriendsOfSymfony/FOSOAuthServerBundle/blob/master/Resources/doc/adding_grant_extensions.md
You can find my FacebookGrantExtension to get a token from a FB access_token :
class FacebookGrantExtension implements GrantExtensionInterface
{
protected $userManager = null;
protected $facebookSdk = null;
public function __construct(UserManager $userManager, \BaseFacebook $facebookSdk)
{
$this->userManager = $userManager;
$this->facebookSdk = $facebookSdk;
}
/**
* #see OAuth2\IOAuth2GrantExtension::checkGrantExtension
*/
public function checkGrantExtension(IOAuth2Client $client, array $inputData, array $authHeaders)
{
if (!isset($inputData['facebook_access_token'])) {
return false;
}
$this->facebookSdk->setAccessToken($inputData['facebook_access_token']);
try {
// Try to get the user with the facebook token from Open Graph
$fbData = $this->facebookSdk->api('/me');
if (empty($fbData) || !isset($fbData['id'])) {
return false;
}
// Check if a user match in database with the facebook id
$user = $this->userManager->findUserBy(array(
'facebookId' => $fbData['id']
));
// If no user found, register a new user and grant token
if (null === $user) {
return false;
}
// Else, return the access_token for the user
else {
return array(
'data' => $user
);
}
} catch(\FacebookApiExceptionion $e) {
return false;
}
}
}
And the config.yml :
my.oauth.facebook_extension:
class: My\CoreBundle\Oauth\FacebookGrantExtension
arguments:
userManager: "#fos_user.user_manager"
facebookSdk: "#fos_facebook.api"
tags:
- { name: fos_oauth_server.grant_extension, uri: 'http://grants.api.mywebsite.com/facebook_access_token' }

How to obtain email address from Facebook using ASP.MVC and DotNetOpenAuth

I'm trying to set up authentication in my app. I have the posting to the external website working (facebook and google)
[AcceptVerbs(HttpVerbs.Post | HttpVerbs.Get), ValidateInput(false)]
public ActionResult OpenIdLogOn(string returnUrl)
{
var redirectUrl = "~/Companies/LogIn";
var provider = Request.Form["provider"];
if (Common.IsNull(provider))
return View();
OpenAuth.RequestAuthentication(provider, redirectUrl);
return View();
}
This works by figuring out what button was clicked (Login with facebook / Google) Then requests authentication. When the authentication has finished and I get a post back, I try to retrieve the email address.
public ActionResult LogIn()
{
var redirectUrl = "~/Companies/LogIn/OpenIdLogOn";
var response = Response;
var userEmail = Request.Params[16];
if (!String.IsNullOrEmpty(ReturnUrl))
{
Response.Redirect("~/Bloggers");
}
ViewBag.OAuthProviders = OpenAuth.AuthenticationClients.GetAll().ToList();
return View();
}
The following will work if I log in with google -
var userEmail = Request.Params[16];
but facebook doesn't send the email back. Well I cant see it in the request. There must be a better way of doing this than using Request.Params[16];
also. Help would be much appreciated.
Without being able to see what OpenAuth is, it's pretty hard to say what the new code should look like. But suffice it to say that you should absolutely never scrape the email address directly from the request. All security offered by OAuth and OpenID are worthless when you do that. You need to get the email address from the security library you're using.
For Google, that means getting it from the FetchResponse or ClaimsResponse extensions of the response.
For Facebook, that means using the access token returned from the login to fetch the email address from Facebook and verify the audience field to make sure the access token isn't being redirected to your site as part of a user spoofing attack.

Resources