How to obtain email address from Facebook using ASP.MVC and DotNetOpenAuth - asp.net-mvc

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.

Related

AuthorizationCodeProvider: Create is never called, how do I generate the authorization code?

I'm setting up my own OAuth2 server. So far, I have succesfully implemented GrantResourceOwnerCredentials in my implementation of OAuthAuthorizationServerProvider. Now, because I am developing an app for our business, I want to implement the OAuth2 Authorization Code grant.
I have tried to follow directions here https://learn.microsoft.com/en-us/aspnet/aspnet/overview/owin-and-katana/owin-oauth-20-authorization-server but in my implementation, I have not found how to reach the Create call of the AuthorizationCodeProvider (which I set in OAuthAuthorizationServerOptions).
I have briefly checked whether accessing the TokenEndpointPath with a (wrong) code parameter works, and in the debugger I see that my AuthorizationCodeProvider's Receive call is hit. Of course there is no success because the code I send is 'sometestcode' instead of a real one, but the code is hit so that means I'm on the right path.
Here's what I have so far:
public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
{
if (OAuthRepository.GetClient(context.ClientId) != null)
{
var expectedRootUri = new Uri(context.Request.Uri, "/");
if (context.RedirectUri.StartsWith(expectedRootUri.AbsoluteUri))
{
context.Validated();
return Task.FromResult<object>(null);
}
}
context.Rejected();
return Task.FromResult<object>(null);
}
public override Task AuthorizeEndpoint(OAuthAuthorizeEndpointContext context)
{
// I know this is wrong but it's just a start and not the focus of this SO question.
context.Response.Redirect(context.AuthorizeRequest.RedirectUri);
context.RequestCompleted();
return Task.FromResult<object>(null);
}
public override Task GrantAuthorizationCode(OAuthGrantAuthorizationCodeContext context)
{
// Needs additional checks, not the focus of my question either
var newTicket = new AuthenticationTicket(context.Ticket.Identity, context.Ticket.Properties);
context.Validated(newTicket);
return Task.FromResult<object>(null);
}
Now, when I call my AuthorizeEndpointPath with a redirect_uri, I am sent to that Uri immediately. I know this is wrong: I should be sent to a separate login page. I'll fix my Web API later to redirect to the correct Uri.
The focus of my question is this: I am now in the process of implementing the login page, but I do not know how to get the authorization code from my WebAPI after the user has logged in. (I'm skipping the consent part for now and assume that if the user is logged in they're okay with it, I'll add giving consent later.)
I am basing my flow on the diagram shared here https://docs.apigee.com/api-platform/security/oauth/oauth-v2-policy-authorization-code-grant-type
I am using Thinktecture IdentityModel to create the login page in an MVC Controller. Now I need to retrieve the authorization code from the Web API in my MVC Controller. And after that I can then redirect the user back to the original client (app) that requested the Authorization Code flow.
To obtain the authorization code from my Web API, I see three methods in Thinktecture's OAuth2Client:
CreateAuthorizeUrl
CreateCodeFlowUrl
RequestAuthorizationCodeAsync
Neither seem to do what I want. How do I proceed so that my WebAPI is called to generate the code?
[HttpGet]
[ImportModelStateFromTempData]
public ActionResult Authorize(string clientId, string returnUrl, string responseType)
{
AuthorizeViewModel viewModel = new AuthorizeViewModel();
...
...
...
return View(viewModel);
}
[HttpPost]
[ExportModelStateToTempData]
public async Task<ActionResult> Authorize(AuthorizeViewModel viewModel)
{
// NOTE: This is in MVC and is postback from *.cshtml View.
OAuth2Client.?????? // <=== How to obtain authorization code from WebAPI?
...
return Redirect(returnUrl);
}
I think I have it correctly setup on the Web API side. I just don't know how to hit the Create part of the flow. I hope someone can help me understand what I am not seeing. I have a blind spot somewhere I think...
How do I have OAuth2Client get me the authorization code from my WebAPI?
I am also using Postman to test my Web API. If anyone can help me get the URL in Web API 2.0 that returns an authorization code, I would also accept that as an answer. Then I can write the code in MVC myself.
Edit
Okay, so I think I found a part of my blind spot. Firstly, I marked `AuthorizeEndpoint' as "not the focus of this SO question", but that was a big mistake.
When I adapt the AuthorizeEndpoint like so:
public override Task AuthorizeEndpoint(OAuthAuthorizeEndpointContext context)
{
System.Security.Claims.ClaimsIdentity ci = new System.Security.Claims.ClaimsIdentity("Bearer");
context.OwinContext.Authentication.SignIn(ci);
context.RequestCompleted();
return Task.FromResult<object>(null);
}
And if I adapt my implementation of AuthorizationCodeProvider.Create like so:
public void Create(AuthenticationTokenCreateContext context)
{
context.Ticket.Properties.IssuedUtc = DateTime.UtcNow;
context.Ticket.Properties.ExpiresUtc = DateTime.UtcNow.AddSeconds(60);
// Some random Guid
context.SetToken(Guid.NewGuid().ToString("n"));
}
Any call to /authorize is redirected to redirect_uri with a query parameter code=<THE_RANDOM_GUID>! :D
Obviously, this implementation is not where it should be, so my question is not yet resolved. Remaining issues:
Right now, anybody can request an authorization code, the client_id is ignored. ValidateClientAuthentication is apparently not hit as part of AuthorizeEndpoint. How do I obtain ClientId in AuthorizeEndpoint?
The authorization code is not coupled to a client. Anyone who intercepts the code could use it. How do I obtain the ClientId in AuthorizationCodeProvider.Create so that I can store it with the code?
The authorization code is not coupled to a user at all, it's an empty ClaimsIdentity. How do I put a user-login page in between and in AuthorizeEndpoint obtain the ClaimsIdentity for the logged-in user?
So, after quite some searching online, I got some success by searching github. Apparently, OAuthAuthorizationServerProvider offers AuthorizeEndpoint and that method should be used for both "Hey, you're not authorized, go log in you!" as well as for "Ahh, okay you're cool, here's an authorization code.". I had expected that OAuthAuthorizationServerProvider would have two separate methods for that, but it doesn't. That explains why on github, I find some projects that implement AuthorizeEndpoint in a rather peculiar way. I've adopted this. Here's an example:
public override async Task AuthorizeEndpoint(OAuthAuthorizeEndpointContext context)
{
if (context.Request.User != null && context.Request.User.Identity.IsAuthenticated)
{
var redirectUri = context.Request.Query["redirect_uri"];
var clientId = context.Request.Query["client_id"];
var authorizeCodeContext = new AuthenticationTokenCreateContext(
context.OwinContext,
context.Options.AuthorizationCodeFormat,
new AuthenticationTicket(
(ClaimsIdentity)context.Request.User.Identity,
new AuthenticationProperties(new Dictionary<string, string>
{
{"client_id", clientId},
{"redirect_uri", redirectUri}
})
{
IssuedUtc = DateTimeOffset.UtcNow,
ExpiresUtc = DateTimeOffset.UtcNow.Add(context.Options.AuthorizationCodeExpireTimeSpan)
}));
await context.Options.AuthorizationCodeProvider.CreateAsync(authorizeCodeContext);
context.Response.Redirect(redirectUri + "?code=" + Uri.EscapeDataString(authorizeCodeContext.Token));
}
else
{
context.Response.Redirect("/account/login?returnUrl=" + Uri.EscapeDataString(context.Request.Uri.ToString()));
}
context.RequestCompleted();
}
Source: https://github.com/wj60387/WebApiOAUthBase/blob/master/OwinWebApiBase/WebApiOwinBase/Providers/OAuthServerProvider.cs
As for my remaining three questions:
Right now, anybody can request an authorization code, the client_id is ignored. ValidateClientAuthentication is apparently not hit as part of AuthorizeEndpoint. How do I obtain ClientId in AuthorizeEndpoint?
Answer: You have to implement `ValidateClientAuthentication'.
The authorization code is not coupled to a client. Anyone who intercepts the code could use it. How do I obtain the ClientId in AuthorizationCodeProvider.Create so that I can store it with the code?
Answer: OAuthAuthorizationServerProvider takes care of this. As long as you set "client_id" in the ticket, it will check that the client that requests an access token for the authorization code is the same.
The authorization code is not coupled to a user at all, it's an empty ClaimsIdentity. How do I put a user-login page in between and in AuthorizeEndpoint obtain the ClaimsIdentity for the logged-in user?
Answer: You create a separate login page. What this does is sign the user in. If your WebAPI uses cookie-based authentication, you can just redirect the user to the AuthorizeEndpoint again. If you use access tokens, your login page has to make a request to `AuthorizeEndpoint' with the access token to obtain an authorization code. (Don't give the access token to the third party. Your login page requests the authorization code and sends that back.) In other words, if you use access tokens then there are two clients involved in this flow.

How to implement OAuth2 for a single tool, without using it as my application's authorization solution

I currently have a MVC site, in .NET Core, backed by a public API. My users must log in (there are no [Anonymous] controllers), and authentication is already successfully being done using the DotNetCore.Authentication provider. All that is well and good.
What I'm now trying to do (by user request) is implement functionality for a user to read and view their Outlook 365 calendar on a page within my site. It doesn't seem too hard on the surface... all I have to do is have them authenticate through microsoftonline with my registered app, and then -- once they have given approval -- redirect back to my app to view their calendar events that I am now able to pull (probably using Graph).
In principle that seems really easy and straightforward. My confusion comes from not being able to implement authentication for a single controller, and not for the entire site. All of the OAuth2 (or OpenID, or OWIN, or whatever your flavor) examples I can find online -- of which there are countless dozens -- all want to use the authorization to control the User.Identity for the whole site. I don't want to change my sitewide authentication protocol; I don't want to add anything to Startup.cs; I don't want anything to scope outside of the one single controller.
tldr; Is there a way to just call https://login.microsoftonline.com/common/oauth2/v2.0/authorize (or facebook, or google, or whatever), and get back a code or token that I can use for that user on that area of the site, and not have it take over the authentication that is already in place for the rest of the site?
For anybody else who is looking for this answer, I've figured out (after much trial and error) how to authenticate for a single user just for a short time, without using middleware that authenticates for the entire application.
public async Task<IActionResult> OfficeRedirectMethod()
{
Uri loginRedirectUri = new Uri(Url.Action(nameof(OfficeAuthorize), "MyApp", null, Request.Scheme));
var azureADAuthority = #"https://login.microsoftonline.com/common";
// Generate the parameterized URL for Azure login.
var authContext = GetProviderContext();
Uri authUri = await authContext.GetAuthorizationRequestUrlAsync(_scopes, loginRedirectUri.ToString(), null, null, null, azureADAuthority);
// Redirect the browser to the login page, then come back to the Authorize method below.
return Redirect(authUri.ToString());
}
public async Task<IActionResult> OfficeAuthorize()
{
var code = Request.Query["code"].ToString();
try
{
// Trade the code for a token.
var authContext = GetProviderContext();
var authResult = await authContext.AcquireTokenByAuthorizationCodeAsync(code, _scopes);
// do whatever with the authResult, here
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine(ex.ToString());
}
return View();
}
public ConfidentialClientApplication GetContext()
{
var clientId = "OfficeClientId;
var clientSecret = "OfficeClientSecret";
var loginRedirectUri = new Uri(#"MyRedirectUri");
TokenCache tokenCache = new MSALSessionCache().GetMsalCacheInstance();
return new ConfidentialClientApplication(
clientId,
loginRedirectUri.ToString(),
new ClientCredential(clientSecret),
tokenCache,
null);
}
I don't know if that will ever be helpful to anybody but me; I just know that it's a problem that doesn't seem to be easily solved by a quick search.

Redirect after confirmation in ASP.NET WEP API controller

I have web api account controller where I confirm an user email
[System.Web.Http.AllowAnonymous]
[System.Web.Http.HttpGet]
[System.Web.Http.Route("ConfirmEmail", Name = "ConfirmEmailRoute")]
public async Task<IHttpActionResult> ConfirmEmail(string userId = "", string code = "")
{
if (string.IsNullOrWhiteSpace(userId) || string.IsNullOrWhiteSpace(code))
{
ModelState.AddModelError("", "User Id and Code are required");
return BadRequest(ModelState);
}
IdentityResult result = await this.AppUserManager.ConfirmEmailAsync(userId, code);
if (result.Succeeded)
{
return Ok();
}
else
{
return GetErrorResult(result);
}
}
}
This method is called when user clicks the confirmation link from email. After that I want to redirect it to "ConfirmidSuccessfully" page
In MVC we could do it like:
return View("ConfirmidSuccessfully");
There are other ways to redirect like:
var response = Request.CreateResponse(HttpStatusCode.Moved);
response.Headers.Location = new Uri("/ConfirmidSuccessfully");
return response;
Actually there are 2 questions:
Is it good to redirect from web api method according to WEB API, or there's better approach
What is the most appropriate way to do it
It is not a good practice to redirect to a view or a web page when you are using REST hence ASP.Net Web API.
Just return a successful status code to the client. Let the client do the redirection itself.
For example, if you are using an AngularJS App to connect with your Web API then after a call to email confirmation finished with success, redirect to the web page/view by using the web page URL you store in the client side.
[EDIT]
Based on your comment
I use angularjs, but the call to email confirmation comes from user's email, not from client.
Then you must generate the confirmation email on server side by making the host's url to be your Angular JS app host. e.g. myangularjsapp.com/emilconfirmation/token. Send an email with this URL to your user.
With URL like that the user is redirect from his email to your AngularJS App. When he hit the app you initialize a call to the ASP.Net Web API by retrieving the token from your AngularJS App url.
Since you are returning IHttpActionResult, you can return redirect in the action and this is the preferable way:
return this.Redirect("/path/to/redirect");

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.

DotNetOpenAuth Authentication against Google Apps OpenID using 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.

Resources