open id log in with google and spring mvc - authentication

I'm trying to developing a spring mvc project with open id and google log in.
I'm using java configuration. And the xml configuration is
<openid-login user-service-ref="openIdUserService" >
<attribute-exchange >
<openid-attribute name="email" type="http://schema.openid.net/contact/email" required="true" />
<openid-attribute name="firstName" type="http://axschema.org/namePerson/first" required="true" />
<openid-attribute name="lastName" type="http://axschema.org/namePerson/last" required="true" />
</attribute-exchange>
but couldn't figure out what is the corresponding code in java configuration.
Any suggestion and some code example for that.
I'm using spring security too.
Here is the provider:
public class OpenIdUserDetailsService implements UserDetailsService, AuthenticationUserDetailsService {
#Autowired
private CustomerRepository userRepository;
private static final List DEFAULT_AUTHORITIES = AuthorityUtils.createAuthorityList("ROLE_USER");
#Override
public UserDetails loadUserByUsername(String id) throws UsernameNotFoundException {
Customer user = userRepository.findByOpenIdIdentifier(id);
if (user == null) {
throw new UsernameNotFoundException(id);
}
OpenIdUser openIdUser = new OpenIdUser(user.getOpenIdIdentifier(), DEFAULT_AUTHORITIES);
openIdUser.setName(user.getFirstname());
return openIdUser;
}
#Override
public UserDetails loadUserDetails(OpenIDAuthenticationToken token) {
String id = token.getIdentityUrl();
Customer user = userRepository.findByOpenIdIdentifier(id);
if (user != null) {
OpenIdUser openIdUser = new OpenIdUser(user.getOpenIdIdentifier(), DEFAULT_AUTHORITIES);
openIdUser.setName(user.getFirstname());
return openIdUser;
}
String firstName = null;
String lastName = null;
String fullName = null;
List attributes = token.getAttributes();
for (OpenIDAttribute attribute : attributes) {
String name = attribute.getName();
if (name.equals("firstname")) {
firstName = attribute.getValues().get(0);
} else if (name.equals("lastname")) {
lastName = attribute.getValues().get(0);
} else if (name.equals("fullname")) {
fullName = attribute.getValues().get(0);
}
}
if (fullName == null) {
StringBuilder fullNameBldr = new StringBuilder();
if (firstName != null) {
fullNameBldr.append(firstName);
}
if (lastName != null) {
fullNameBldr.append(" ").append(lastName);
}
fullName = fullNameBldr.toString();
}
OpenIdUser openIdUser = new OpenIdUser(id, DEFAULT_AUTHORITIES);
openIdUser.setName(fullName);
openIdUser.setNewUser(true);
Customer u = new Customer();
u.setOpenIdIdentifier(openIdUser.getUsername());
u.setFirstname(openIdUser.getName());
userRepository.save(u);
return openIdUser;
}
}
Thanks for all help.

OpenIDLoginConfigurer builder will build OpenIDAuthenticationFilter and register filter in spring security filter chain.
OpenIDLoginConfigurer takes attributeExchange parameters to build OpenIDAuthenticationFilter.These are the callback parameters after authenticate with openid provider.
To authenticate with open id through spring security,requested url will be "/j_spring_openid_security_check" OpenIDAuthenticationFilter will process this request,by hitting to openid provider for authentication.Once authentication is done, openid user will be mapped with local user.
Here is simple example application that works openId login with spring security.

Related

Sharepoint 2013 - FBA and 2FA with custom login page

I'm a complete noob in Sharepoint. I've just started learning sharepoint 2 weeks ago coz my boss assigned me to a sharepoint project. I have to implement 2FA and FBA in an existing claims based intranet web application. I though it would be a simple task to do just by researching but I haven't found a clear guide or answer for my question.
Here are a few of my tasks:
1) Add forms based authentication to the site and use custom login page.
2) Authentication
Check user's name and password with AD upon login.
If valid, have to request OTP code from the 3rd party provider for
2FA.
User is authenticated after passing both.
Configurations and custom login page were not much trouble and it didn't take long to get them done. But I'm stuck at the 2FA part.
1) How to customize the authentication process? I don't remember where did I get the below code but I really hoped that I would be able to do something with it. So, can I do something with it or I'm going the wrong path? I'd really appreciate any help and thanks a lot in advance.
protected void btnLogin_Click(object sender, EventArgs e)
{
bool status = SPClaimsUtility.AuthenticateFormsUser(
Context.Request.UrlReferrer,
txtUsername.Value.ToString(),
txtPassword.Value.ToString());
if (!status) // if auth failed
{
lblInvalid.InnerText = "Wrong Username or Password";
lblInvalid.Visible = true;
}
else //if success
{
//What do I do here to change the user back to not authenticated?
}
}
After you properly log in set federated authentication cookie domain.
HttpCookie httpCookie = current.Response.Cookies["FedAuth"];
httpCookie.Domain = "." + ConfigurationManager.AppSettings["yourdomain"];
Sign out method is more complicated, long time ago i based my solution on this post
And sign out method (sorry for variable names but i'm decompiling my old dll) based on sharepoint SignOut page and fix from the post:
public static void SignOut(SPSite site, SPWeb web, IClaimsPrincipal principal)
{
HttpContext current = HttpContext.Current;
if (current.Session != null)
{
current.Session.Clear();
}
string value = string.Empty;
if (current.Request.Browser["supportsEmptyStringInCookieValue"] == "false")
{
value = "NoCookie";
}
HttpCookie httpCookie = current.Request.Cookies["WSS_KeepSessionAuthenticated"];
bool flag = false;
for (int i = 0; i < current.Request.Cookies.Count; i++)
{
HttpCookie httpCookie2 = current.Request.Cookies.Get(i);
if (httpCookie2.Name == "FedAuth" && !flag)
{
flag = true;
httpCookie2.Domain = WebConfigurationManager.AppSettings["yourdomain"];
}
}
if (httpCookie != null)
{
httpCookie.Value = value;
current.Response.Cookies.Remove("WSS_KeepSessionAuthenticated");
current.Response.Cookies.Add(httpCookie);
}
HttpCookie httpCookie3 = current.Request.Cookies["MSOWebPartPage_AnonymousAccessCookie"];
if (httpCookie3 != null)
{
httpCookie3.Value = value;
httpCookie3.Expires = new DateTime(1970, 1, 1);
current.Response.Cookies.Remove("MSOWebPartPage_AnonymousAccessCookie");
current.Response.Cookies.Add(httpCookie3);
}
SPIisSettings iisSettingsWithFallback = site.WebApplication.GetIisSettingsWithFallback(site.Zone);
if (iisSettingsWithFallback.UseClaimsAuthentication)
{
string iPUrl = Authentication.GetIPUrl(principal);
if (iPUrl != string.Empty)
{
string str = HttpUtility.UrlEncode(SPContext.Current.Site.RootWeb.Url);
string url = iPUrl + "?wa=wsignout1.0&wreply=" + str;
FederatedAuthentication.SessionAuthenticationModule.SignOut();
if (current.Session != null)
{
current.Session.Abandon();
}
current.Response.Redirect(url);
}
else
{
FederatedAuthentication.SessionAuthenticationModule.SignOut();
int num = 0;
foreach (SPAuthenticationProvider current2 in iisSettingsWithFallback.ClaimsAuthenticationProviders)
{
num++;
}
if (num != 1 || !iisSettingsWithFallback.UseWindowsIntegratedAuthentication)
{
if (current.Session != null)
{
current.Session.Abandon();
}
SPUtility.Redirect(web.ServerRelativeUrl, 0, current);
return;
}
}
}
if (AuthenticationMode.Forms == SPSecurity.AuthenticationMode)
{
FormsAuthentication.SignOut();
if (current.Session != null)
{
current.Session.Abandon();
}
SPUtility.Redirect(web.ServerRelativeUrl, 0, current);
}
else if (AuthenticationMode.Windows != SPSecurity.AuthenticationMode)
{
throw new SPException();
}
}
private static string GetIPUrl(IClaimsPrincipal principal)
{
string result;
if (principal == null)
{
result = string.Empty;
}
else
{
string text = string.Empty;
try
{
string text2 = principal.Identity.Name.Split(new char[] {'|'})[1];
if (SPSecurityTokenServiceManager.Local.TrustedLoginProviders[text2] != null)
{
text = SPSecurityTokenServiceManager.Local.TrustedLoginProviders[text2].ProviderUri.AbsoluteUri;
}
}
catch (Exception ex)
{
// log
}
result = text;
}
return result;
}
Further reading:
Writing A Custom Forms Login Page for SharePoint 2010
Custom Single Sign-On Scenario in SharePoint 2010
Normal aspx page
<html>
<head>One Head</head>
<body>
<form runat="server">
<table>
<tr>
<td>User Name:</td>
<td>
<asp:TextBox ID="txtUserName" runat="server" /></td>
</tr>
<tr>
<td>Password:</td>
<td>
<asp:TextBox ID="txtPassword" TextMode="Password" runat="server" /></td>
</tr>
<tr>
<td colspan="2">
<asp:Button ID="btnButton" Text="Button" OnClick="btnButton_Click" runat="server" />
</td>
</tr>
</table>
</form>
</body>
</html>
You may have trouble in adding Microsoft.SharePoint.identityModel, here is the location I got
C:\Windows\Microsoft.NET\assembly\GAC_MSIL\Microsoft.SharePoint.IdentityModel\v4.0_15.0.0.0__71e9bce111e9429c\Microsoft.SharePoint.IdentityModel.dll
List of Includes
using System;
using Microsoft.SharePoint;
using System.DirectoryServices;
using System.DirectoryServices.AccountManagement;
using Microsoft.SharePoint.IdentityModel;
using System.IdentityModel.Tokens;
Button click code
protected void btnButton_Click(object sender, EventArgs e)
{
string domn = "mydomain";
string membershipProviderName = "membership";
string roleProviderName = "rolemanager";
string cookieeee = string.Format("{0}\\{1}", domn, txtUserName.Text);
bool isAuthenticated = Authenticate(domn, txtUserName.Text, txtPassword.Text);
if (isAuthenticated)
{
SecurityToken token = SPSecurityContext.SecurityTokenForFormsAuthentication(new Uri(SPContext.Current.Web.Url),
membershipProviderName, roleProviderName, txtUserName.Text, txtPassword.Text,
SPFormsAuthenticationOption.PersistentSignInRequest);
SPFederationAuthenticationModule.Current.SetPrincipalAndWriteSessionToken(token);
Response.Redirect("/");
}
}
[DirectoryServicesPermission(System.Security.Permissions.SecurityAction.LinkDemand, Unrestricted = true)]
public static bool Authenticate(string domainName, string userAlias, string userPassword)
{
try
{
PrincipalContext context = new PrincipalContext(ContextType.Domain, domainName);
return context.ValidateCredentials(userAlias, userPassword, ContextOptions.Negotiate));
}
catch
{
throw;
}
}
Note: Make sure you have all FBA configurations set in web config files. This is only custom authentication this will not work if role and membership are not set properly in the central admin and web config on services and webapplication.

Basic Authentication Middleware with OWIN and ASP.NET WEB API

I created an ASP.NET WEB API 2.2 project. I used the Windows Identity Foundation based template for individual accounts available in visual studio see it here.
The web client (written in angularJS) uses OAUTH implementation with web browser cookies to store the token and the refresh token. We benefit from the helpful UserManager and RoleManager classes for managing users and their roles.
Everything works fine with OAUTH and the web browser client.
However, for some retro-compatibility concerns with desktop based clients I also need to support Basic authentication. Ideally, I would like the [Authorize], [Authorize(Role = "administrators")] etc. attributes to work with both OAUTH and Basic authentication scheme.
Thus, following the code from LeastPrivilege I created an OWIN BasicAuthenticationMiddleware that inherits from AuthenticationMiddleware.
I came to the following implementation. For the BasicAuthenticationMiddleWare only the Handler has changed compared to the Leastprivilege's code. Actually we use ClaimsIdentity rather than a series of Claim.
class BasicAuthenticationHandler: AuthenticationHandler<BasicAuthenticationOptions>
{
private readonly string _challenge;
public BasicAuthenticationHandler(BasicAuthenticationOptions options)
{
_challenge = "Basic realm=" + options.Realm;
}
protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()
{
var authzValue = Request.Headers.Get("Authorization");
if (string.IsNullOrEmpty(authzValue) || !authzValue.StartsWith("Basic ", StringComparison.OrdinalIgnoreCase))
{
return null;
}
var token = authzValue.Substring("Basic ".Length).Trim();
var claimsIdentity = await TryGetPrincipalFromBasicCredentials(token, Options.CredentialValidationFunction);
if (claimsIdentity == null)
{
return null;
}
else
{
Request.User = new ClaimsPrincipal(claimsIdentity);
return new AuthenticationTicket(claimsIdentity, new AuthenticationProperties());
}
}
protected override Task ApplyResponseChallengeAsync()
{
if (Response.StatusCode == 401)
{
var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode);
if (challenge != null)
{
Response.Headers.AppendValues("WWW-Authenticate", _challenge);
}
}
return Task.FromResult<object>(null);
}
async Task<ClaimsIdentity> TryGetPrincipalFromBasicCredentials(string credentials,
BasicAuthenticationMiddleware.CredentialValidationFunction validate)
{
string pair;
try
{
pair = Encoding.UTF8.GetString(
Convert.FromBase64String(credentials));
}
catch (FormatException)
{
return null;
}
catch (ArgumentException)
{
return null;
}
var ix = pair.IndexOf(':');
if (ix == -1)
{
return null;
}
var username = pair.Substring(0, ix);
var pw = pair.Substring(ix + 1);
return await validate(username, pw);
}
Then in Startup.Auth I declare the following delegate for validating authentication (simply checks if the user exists and if the password is right and generates the associated ClaimsIdentity)
public void ConfigureAuth(IAppBuilder app)
{
app.CreatePerOwinContext(DbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
Func<string, string, Task<ClaimsIdentity>> validationCallback = (string userName, string password) =>
{
using (DbContext dbContext = new DbContext())
using(UserStore<ApplicationUser> userStore = new UserStore<ApplicationUser>(dbContext))
using(ApplicationUserManager userManager = new ApplicationUserManager(userStore))
{
var user = userManager.FindByName(userName);
if (user == null)
{
return null;
}
bool ok = userManager.CheckPassword(user, password);
if (!ok)
{
return null;
}
ClaimsIdentity claimsIdentity = userManager.CreateIdentity(user, DefaultAuthenticationTypes.ApplicationCookie);
return Task.FromResult(claimsIdentity);
}
};
var basicAuthOptions = new BasicAuthenticationOptions("KMailWebManager", new BasicAuthenticationMiddleware.CredentialValidationFunction(validationCallback));
app.UseBasicAuthentication(basicAuthOptions);
// Configure the application for OAuth based flow
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
//If the AccessTokenExpireTimeSpan is changed, also change the ExpiresUtc in the RefreshTokenProvider.cs.
AccessTokenExpireTimeSpan = TimeSpan.FromHours(2),
AllowInsecureHttp = true,
RefreshTokenProvider = new RefreshTokenProvider()
};
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
}
However, even with settings the Request.User in Handler's AuthenticationAsyncCore method the [Authorize] attribute does not work as expected: responding with error 401 unauthorized every time I try to use the Basic Authentication scheme.
Any idea on what is going wrong?
I found out the culprit, in the WebApiConfig.cs file the 'individual user' template inserted the following lines.
//// Web API configuration and services
//// Configure Web API to use only bearer token authentication.
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
Thus we also have to register our BasicAuthenticationMiddleware
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
config.Filters.Add(new HostAuthenticationFilter(BasicAuthenticationOptions.BasicAuthenticationType));
where BasicAuthenticationType is the constant string "Basic" that is passed to the base constructor of BasicAuthenticationOptions
public class BasicAuthenticationOptions : AuthenticationOptions
{
public const string BasicAuthenticationType = "Basic";
public BasicAuthenticationMiddleware.CredentialValidationFunction CredentialValidationFunction { get; private set; }
public BasicAuthenticationOptions( BasicAuthenticationMiddleware.CredentialValidationFunction validationFunction)
: base(BasicAuthenticationType)
{
CredentialValidationFunction = validationFunction;
}
}

MVC4 how do I set a cookie and then redirect to an action

Hi I am trying to get a users role and set it to a cookie in my application
I have the following code which works
public ActionResult Index()
{
var user = User.Identity.Name; // set by 3rd party central login in manager
// key to check that we are in our environment with 3rd party login set up
if (ConfigurationManager.AppSettings["IsNGDC"] == "true")
{
// ActiveKey login
if (user.Contains("uid="))
{
var endIndex = user.IndexOf(",ou");
var userEmail = user.Substring(4, endIndex - 4);
user = userEmail;
}
SetAuthenticationCookie(user);
}
// view model is not needed I could just pass in a string
var viewModel = new SiteminderViewModel { Username = user };
if (ModelState.IsValid)
{
this.AssignRoles(viewModel);
return this.View();
}
return View(viewModel);
}
I need to change this because I am using a dynamic Navigation bar that shows different Items depending on the users role and it does not show the correct Nav bar until the user refreshes the page. I think this is because the view uses the cookie and the view is being rendered in the same action that sets the cookie.
I want to split this into 2 actions in my controller as follows
public void LogIn()
{
var user = User.Identity.Name; // set by 3rd party central login in manager
// key to check that we are in our environment with 3rd party login set up
if (ConfigurationManager.AppSettings["IsNGDC"] == "true")
{
// ActiveKey login
if (user.Contains("uid="))
{
var endIndex = user.IndexOf(",ou");
var userEmail = user.Substring(4, endIndex - 4);
user = userEmail;
}
SetAuthenticationCookie(user);
}
// view model is not needed I could just pass in a string
var viewModel = new SiteminderViewModel { Username = user };
this.AssignRoles(viewModel);
// default URL in Index action for this controller
this.Response.Redirect(FormsAuthentication.DefaultUrl, false);
}
public ActionResult Index()
{
ViewBag.Message = "Home App Description here";
return this.View();
}
When I try this it looks like the Cookie hasn't been set. Unfortunately I can only test this code on a replication of our Production Environment because of the 3rd party login so I have limited debug info. As far as I can tell the problem seems to be with how I am redirecting.
I have provided the methods I use cor creating the cookie and assigning the roles bellow.
Additional Info
private void SetAuthenticationCookie(string username)
{
var tkt = new FormsAuthenticationTicket(1, username, DateTime.UtcNow, DateTime.UtcNow.AddMinutes(20), true, string.Empty);
var encryptedTkt = FormsAuthentication.Encrypt(tkt);
var formsCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTkt);
this.Response.Cookies.Add(formsCookie);
}
private void AssignRoles(SiteminderViewModel viewModel)
{
var authCookie = System.Web.HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
var ticket = authCookie != null ? FormsAuthentication.Decrypt(authCookie.Value) : new FormsAuthenticationTicket(1, viewModel.Username, DateTime.UtcNow, DateTime.UtcNow.AddMinutes(20), true, string.Empty);
var user = this.userRepository.GetUser(viewModel.Username);
if (user != null)
{
var principleProperties = new PrincipleProperties(ticket.UserData)
{
UserName = user.Email,
UserRole = user.UserGroup.Role.Name.Replace(" ", string.Empty),
ContextId = contextRepository.GetContextByDataOwnerGroupId(user.UserGroupId)
};
if (user.DeletedIndicator)
{
principleProperties.UserRole = string.Empty;
}
this.SetPrinciple(ticket, principleProperties);
}
}
private FormsAuthenticationTicket SetPrinciple(FormsAuthenticationTicket ticket, PrincipleProperties properties)
{
var newticket = new FormsAuthenticationTicket(
ticket.Version,
ticket.Name,
ticket.IssueDate,
ticket.Expiration,
ticket.IsPersistent,
properties.Serialize(),
ticket.CookiePath);
var encryptedTkt = FormsAuthentication.Encrypt(newticket);
var formsCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTkt);
this.Response.Cookies.Set(formsCookie);
var referenceDataIdentity = new ReferenceDataIdentity(ticket);
var principle = new ReferenceDataPrinciple(referenceDataIdentity, properties);
Thread.CurrentPrincipal = principle;
return newticket;
}
The solution to this was that the cookie wasn't being added to the browser because I was redirecting before the cookie reached the client side the solution was to have the Login Action return a blank view and then from inside the view redirect to the Index action the final version of my code ended up as follows NOTE: Login changed to AuthenticateUser
public ActionResult Index()
{
var authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
var ticket = FormsAuthentication.Decrypt(authCookie.Value);
if (ticket != null && ticket.UserData != string.Empty)
{
return this.View();
}
}
return RedirectToAction("AuthenticateUser");
}
public ActionResult AuthenticateUser()
{
// set by Site minder
var user = User.Identity.Name;
// ActiveKey login
if (user.Contains("uid="))
{
var endIndex = user.IndexOf(",ou");
var userEmail = user.Substring(4, endIndex - 4);
user = userEmail;
}
SetAuthenticationCookie(user);
var viewModel = new SiteminderViewModel { Username = user };
this.AssignRoles(viewModel);
return this.View();
}
and the view is. There is no HTML to display so the redirect is not noticeable.
#{
ViewBag.Title = "AuthenticateUser";
Layout = null;
Response.Redirect( Url.Action("Index", "Home"), false);
}
This code is checking that there is a cookie and that the user data isn't empty, if theses checks pass it shows the user the home page. Otherwise it redirects to the authentication action that gets the email address that was set in the browser by our 3rd party central login software and gets the users details from the users details. If the user is not in our user table they are given basic access rights.

LinkedIn full profile details using DotNetOpenAuth in MVC4

My MVC4 application allows login using LinkedIn account. I want to pull all details that are avaible from linkedIn of the logged in User. Currently i have done the following.
In My AuthConfig.cs,
Dictionary<string, object> linkedInExtraData = new Dictionary<string, object>();
linkedInExtraData.Add("Icon", "../Images/linkedIn.png");
OAuthWebSecurity.RegisterClient(
client: new App_Start.LinkedInCustomClient("xxxxxxxxxxxx", "yyyyyyyyyyyyyyy"),
displayName: "LinkedIn",
extraData: linkedInExtraData);
In linkedInCustomClient.cs , from LinkedIn Developer Kit
public class LinkedInCustomClient : OAuthClient
{
private static XDocument LoadXDocumentFromStream(Stream stream)
{
var settings = new XmlReaderSettings
{
MaxCharactersInDocument = 65536L
};
return XDocument.Load(XmlReader.Create(stream, settings));
}
/// Describes the OAuth service provider endpoints for LinkedIn.
private static readonly ServiceProviderDescription LinkedInServiceDescription =
new ServiceProviderDescription
{
AccessTokenEndpoint =
new MessageReceivingEndpoint("https://api.linkedin.com/uas/oauth/accessToken",
HttpDeliveryMethods.PostRequest),
RequestTokenEndpoint =
new MessageReceivingEndpoint("https://api.linkedin.com/uas/oauth/requestToken?scope=r_fullprofile",
HttpDeliveryMethods.PostRequest),
UserAuthorizationEndpoint =
new MessageReceivingEndpoint("https://www.linkedin.com/uas/oauth/authorize",
HttpDeliveryMethods.PostRequest),
TamperProtectionElements =
new ITamperProtectionChannelBindingElement[] { new HmacSha1SigningBindingElement() },
ProtocolVersion = ProtocolVersion.V10a
};
public LinkedInCustomClient(string consumerKey, string consumerSecret) :
base("linkedIn", LinkedInServiceDescription, consumerKey, consumerSecret) { }
/// Check if authentication succeeded after user is redirected back from the service provider.
/// The response token returned from service provider authentication result.
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
Justification = "We don't care if the request fails.")]
protected override AuthenticationResult VerifyAuthenticationCore(AuthorizedTokenResponse response)
{
// See here for Field Selectors API http://developer.linkedin.com/docs/DOC-1014
const string profileRequestUrl =
"https://api.linkedin.com/v1/people/~:(id,first-name,last-name,interests,headline,industry,summary,email-address,location:(name),picture-url,positions,associations,languages,honors,educations,date-of-birth,primary-twitter-account,three-current-positions,three-past-positions,group-memberships,specialties,skills)";
string accessToken = response.AccessToken;
string tokenSecret = (response as ITokenSecretContainingMessage).TokenSecret;
string Verifier = response.ExtraData.Values.First();
var profileEndpoint =
new MessageReceivingEndpoint(profileRequestUrl, HttpDeliveryMethods.GetRequest);
HttpWebRequest request =
WebWorker.PrepareAuthorizedRequest(profileEndpoint, accessToken);
try
{
using (WebResponse profileResponse = request.GetResponse())
{
using (Stream responseStream = profileResponse.GetResponseStream())
{
XDocument document = LoadXDocumentFromStream(responseStream);
return new AuthenticationResult(
isSuccessful: true,
provider: ProviderName,
providerUserId: userId,
userName: userName,
extraData: extraData);
}
}
}
catch (Exception exception)
{
return new AuthenticationResult(exception);
}
}
}
In my controller,
AuthenticationResult result = OAuthWebSecurity.VerifyAuthentication(Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl }));
if (!result.IsSuccessful)
{
return RedirectToAction("ExternalLoginFailure");
}
I need to get the following details in my controller as authentication result.
(id,first-name,last-name,interests,headline,industry,summary,email-address,location:(name),picture-url,positions,associations,languages,honors,educations,date-of-birth,primary-twitter-account,three-current-positions,three-past-positions,group-memberships,specialties,skills)
The response of your request from LinkedIn will be a xml file. The format and fields are mentioned in LinkedIn Profile Fields
For getting email field, you need to modify your request token url as
RequestTokenEndpoint = new MessageReceivingEndpoint("https://api.linkedin.com/uas/oauth/requestToken?scope=r_fullprofile+r_emailaddress",
HttpDeliveryMethods.PostRequest),
You can get the fields as required in the following code
XDocument document = LoadXDocumentFromStream(responseStream);
Eg : For getting the first name field from the xml file,
var firstName = document.Root.Element("first-name").Value;
Fields like languages, positions, skills etc will be returned as structured objects as part of the profile.
Eg : Language field.
var Lang = document.Root.Element("languages");
var languages = new List<string>();
if (Lang != null)
{
foreach (var l in Lang.Elements())
{
if (l.Element("language") != null && l.Element("language").Element("name") != null)
{
languages.Add(l.Element("language").Element("name").Value);
}
}
}
Then you can add fields to "extraData" which can be accessed in the controller.
extraData.Add("firstName", firstName);
extraData.Add("languages", lang);

OAuth2 and DotNetOpenAuth - implementing Google custom client

I'm having an issue implementing custom OAuth2Client for google using DotNetOpenAuth and MVC4.
I've got to the point where I can successfully make the authorization request to the google endpoint
https://accounts.google.com/o/oauth2/auth
and Google asks if the user will allow my application access to their account. All good so far. When the user clicks 'OK', google then calls my callback URL as expected.
The problem is when I call the VerifyAuthentication method on the OAuthWebSecurity class (Microsoft.Web.WebPages.OAuth)
var authenticationResult = OAuthWebSecurity.VerifyAuthentication(Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl }));
It's always returning an AuthenticationResult with IsSuccessful = false and Provider = ""
I've looked into the code for this, and the OAuthWebSecurity class tries to get the Provider name from
Request.QueryString["__provider__"]
but Google is not sending this information back in the querystring. The other provider I have implemented (LinkedIn) is sending the provider name back and it all works just fine.
I'm not sure what I can do from this point, apart from abandon the Microsoft.Web.WebPages.OAuth classes and just use DotNetOpenAuth without them, but I was hoping someone might have another solution I can try...
I've searched extensively, but can't seem to find anything to help ... I've found it really difficult even just to find examples of people doing the same thing, which has really surprised me.
Any help much appreciated!
Update: As Matt Johnson mentions below he has packaged up a solution to this which you can get from GitHub: https://github.com/mj1856/DotNetOpenAuth.GoogleOAuth2
As he notes:
DNOA and OAuthWebSecurity for ASP.Net MVC 4 ship with only an OpenId provider for Google. This is an OAuth2 client that you can use instead.
IMPORTANT - If you are using ASP.Net MVC 5, this package is not applicable. You should use Microsoft.Owin.Security.Google instead. (It also ships with the MVC 5 starter templates in VS 2013.)
I got round this in the end by catching the request when it comes in, and doing my own check to see which provider it has come from. Google allow you to send a parameter to the OAuth request called 'state', which they simply pass straight back to you when they make the callback, so I'm using this to pass the provider name for google, and I check for this in the absence of the "__provider__".
something like this:
public String GetProviderNameFromQueryString(NameValueCollection queryString)
{
var result = queryString["__provider__"];
if (String.IsNullOrWhiteSpace(result))
{
result = queryString["state"];
}
return result;
}
I've then implemented a custom OAuth2Client for Google, and I manually call the VerifyAuthentication method on that myself, bypassing the Microsoft wrapper stuff.
if (provider is GoogleCustomClient)
{
authenticationResult = ((GoogleCustomClient)provider).VerifyAuthentication(context, new Uri(String.Format("{0}/oauth/ExternalLoginCallback", context.Request.Url.GetLeftPart(UriPartial.Authority).ToString())));
}
else
{
authenticationResult = OAuthWebSecurity.VerifyAuthentication(returnUrl);
}
This has allowed me to keep the stuff I already had in place for the other providers using the Microsoft wrappers.
As requested by #1010100 1001010, here is my custom OAuth2Client for Google (NOTE: IT NEEDS SOME TIDYING! I HAVEN'T GOT ROUND TO TIDYING THE CODE UP YET. It does work though) :
public class GoogleCustomClient : OAuth2Client
{
ILogger _logger;
#region Constants and Fields
/// <summary>
/// The authorization endpoint.
/// </summary>
private const string AuthorizationEndpoint = "https://accounts.google.com/o/oauth2/auth";
/// <summary>
/// The token endpoint.
/// </summary>
private const string TokenEndpoint = "https://accounts.google.com/o/oauth2/token";
/// <summary>
/// The _app id.
/// </summary>
private readonly string _clientId;
/// <summary>
/// The _app secret.
/// </summary>
private readonly string _clientSecret;
#endregion
public GoogleCustomClient(string clientId, string clientSecret)
: base("Google")
{
if (string.IsNullOrWhiteSpace(clientId)) throw new ArgumentNullException("clientId");
if (string.IsNullOrWhiteSpace(clientSecret)) throw new ArgumentNullException("clientSecret");
_logger = ObjectFactory.GetInstance<ILogger>();
this._clientId = clientId;
this._clientSecret = clientSecret;
}
protected override Uri GetServiceLoginUrl(Uri returnUrl)
{
StringBuilder serviceUrl = new StringBuilder();
serviceUrl.AppendFormat("{0}?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile", AuthorizationEndpoint);
serviceUrl.Append("&state=google");
serviceUrl.AppendFormat("&redirect_uri={0}", returnUrl.ToString());
serviceUrl.Append("&response_type=code");
serviceUrl.AppendFormat("&client_id={0}", _clientId);
return new Uri(serviceUrl.ToString());
}
protected override IDictionary<string, string> GetUserData(string accessToken)
{
RestClient client = new RestClient("https://www.googleapis.com");
var request = new RestRequest(String.Format("/oauth2/v1/userinfo?access_token={0}", accessToken), Method.GET);
IDictionary<String, String> extraData = new Dictionary<String, String>();
var response = client.Execute(request);
if (null != response.ErrorException)
{
return null;
}
else
{
try
{
var json = JObject.Parse(response.Content);
string firstName = (string)json["given_name"];
string lastName = (string)json["family_name"];
string emailAddress = (string)json["email"];
string id = (string)json["id"];
extraData = new Dictionary<String, String>
{
{"accesstoken", accessToken},
{"name", String.Format("{0} {1}", firstName, lastName)},
{"firstname", firstName},
{"lastname", lastName},
{"email", emailAddress},
{"id", id}
};
}
catch(Exception ex)
{
_logger.Error("Error requesting OAuth user data from Google", ex);
return null;
}
return extraData;
}
}
protected override string QueryAccessToken(Uri returnUrl, string authorizationCode)
{
StringBuilder postData = new StringBuilder();
postData.AppendFormat("client_id={0}", this._clientId);
postData.AppendFormat("&redirect_uri={0}", HttpUtility.UrlEncode(returnUrl.ToString()));
postData.AppendFormat("&client_secret={0}", this._clientSecret);
postData.AppendFormat("&grant_type={0}", "authorization_code");
postData.AppendFormat("&code={0}", authorizationCode);
string response = "";
string accessToken = "";
var webRequest = (HttpWebRequest)WebRequest.Create(TokenEndpoint);
webRequest.Method = "POST";
webRequest.ContentType = "application/x-www-form-urlencoded";
try
{
using (Stream s = webRequest.GetRequestStream())
{
using (StreamWriter sw = new StreamWriter(s))
sw.Write(postData.ToString());
}
using (WebResponse webResponse = webRequest.GetResponse())
{
using (StreamReader reader = new StreamReader(webResponse.GetResponseStream()))
{
response = reader.ReadToEnd();
}
}
var json = JObject.Parse(response);
accessToken = (string)json["access_token"];
}
catch(Exception ex)
{
_logger.Error("Error requesting OAuth access token from Google", ex);
return null;
}
return accessToken;
}
public override AuthenticationResult VerifyAuthentication(HttpContextBase context, Uri returnPageUrl)
{
string code = context.Request.QueryString["code"];
if (string.IsNullOrEmpty(code))
{
return AuthenticationResult.Failed;
}
string accessToken = this.QueryAccessToken(returnPageUrl, code);
if (accessToken == null)
{
return AuthenticationResult.Failed;
}
IDictionary<string, string> userData = this.GetUserData(accessToken);
if (userData == null)
{
return AuthenticationResult.Failed;
}
string id = userData["id"];
string name;
// Some oAuth providers do not return value for the 'username' attribute.
// In that case, try the 'name' attribute. If it's still unavailable, fall back to 'id'
if (!userData.TryGetValue("username", out name) && !userData.TryGetValue("name", out name))
{
name = id;
}
// add the access token to the user data dictionary just in case page developers want to use it
userData["accesstoken"] = accessToken;
return new AuthenticationResult(
isSuccessful: true, provider: this.ProviderName, providerUserId: id, userName: name, extraData: userData);
}
You can add a provider query parameter to the end of your callback url.
e.g. https://mywebsite.com/Account/ExternalLoginCallback?provider=google
The you will get it and you don't need the work around.