How to get birthday using Google Oauth2 - oauth-2.0

I'm trying to get the birthday using Google Oauth2
but not sure if I'm doing it right...
Here is the code I'm using:
var googleAuthOptions = new GoogleOAuth2AuthenticationOptions();
googleAuthOptions.ClientId = googleId;
googleAuthOptions.ClientSecret = googleSecret;
googleAuthOptions.Scope.Add("https://www.googleapis.com/auth/userinfo.email");
googleAuthOptions.Scope.Add("https://www.googleapis.com/auth/userinfo.profile");
googleAuthOptions.Scope.Add("https://www.googleapis.com/auth/plus.login");
googleAuthOptions.Provider = new GoogleOAuth2AuthenticationProvider
{
OnAuthenticated = (context) =>
{
context.Identity.AddClaim(new System.Security.Claims.Claim("GoogleAccessToken", context.AccessToken));
var expiryDuration = context.ExpiresIn ?? new TimeSpan();
context.Identity.AddClaim(new Claim("google:expires_in", DateTime.UtcNow.Add(expiryDuration).ToString(CultureInfo.InvariantCulture)));
if (context.Email != null) context.Identity.AddClaim(new Claim("google:email", context.Email));
if (context.Id != null) context.Identity.AddClaim(new Claim("google:id", context.Id));
if (context.GivenName != null) context.Identity.AddClaim(new Claim("google:given_name", context.GivenName));
if (context.FamilyName != null) context.Identity.AddClaim(new Claim("google:family_name", context.FamilyName));
if (context.Name != null) context.Identity.AddClaim(new Claim("google:name", context.Name));
if (context.Profile != null) context.Identity.AddClaim(new Claim("google:profile", context.Profile));
if (context.User.GetValue("birthday") != null) context.Identity.AddClaim(new Claim("google:birthday", context.User.GetValue("birthday").ToString()));
if (context.User.GetValue("locale") != null) context.Identity.AddClaim(new Claim("google:locale", context.User.GetValue("locale").ToString()));
if (context.User.GetValue("gender") != null) context.Identity.AddClaim(new Claim("google:gender", context.User.GetValue("gender").ToString()));
if (context.User.GetValue("picture") != null) context.Identity.AddClaim(new Claim("google:picture", context.User.GetValue("picture").ToString()));
// Add all other available claims
foreach (var claim in context.User)
{
var claimType = string.Format("google:{0}", claim.Key);
var claimValue = claim.Value.ToString();
if (!context.Identity.HasClaim(claimType, claimValue))
context.Identity.AddClaim(new System.Security.Claims.Claim(claimType, claimValue, "XmlSchemaString", "Google"));
}
return Task.FromResult(0);
}
};
app.UseGoogleAuthentication(googleAuthOptions);
I would love to see a sample of code that works for sure to get the birthday
Thanks in advance.

SOLVED, so other than setting the right scope (https://www.googleapis.com/auth/plus.login)
Make sure that your Google Plus test account profile allow "show birthday year" and also that the birthday is available to the "public"
if that's not enough, google also sends the birthday in yyyy/mm/dd format...
So I'm using this to display MM/dd/yyyy
DateTime dateDateOfBirth;
DateTime.TryParse(vm.DateOfBirth, out dateDateOfBirth);
if (DateAndTime.IsDateValid(vm.DateOfBirth))
{
// By default Google returns yyyy/mm/dd
vm.DateOfBirth = dateDateOfBirth.ToString(#"MM/dd/yyyy");
}
else vm.DateOfBirth = string.Empty;

Related

Owin OAuth Providers Twitter and Microsoft

I am having issues with the below line returning null for twitter and microsoft:
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
this is in the account controller like below:
[AllowAnonymous]
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
var loginInfo = await
AuthenticationManager.GetExternalLoginInfoAsync();
if (loginInfo == null)
{
return RedirectToAction("Login");
}
var result = await SignInManager.ExternalSignInAsync(loginInfo, false);
switch (result)
{
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
//case SignInStatus.RequiresVerification:
// return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = false });
case SignInStatus.Failure:
default:
// If the user does not have an account, then prompt the user to create an account
ViewBag.ReturnUrl = returnUrl;
ViewBag.LoginProvider = loginInfo.Login.LoginProvider;
return View("ExternalLoginConfirmation", new AccountExternalLoginConfirmationViewModel { Email = loginInfo.Email });
}
}
In the startup.auth.cs the current configuration is:
app.UseTwitterAuthentication(
new TwitterAuthenticationOptions()
{
ConsumerKey = ConfigurationManager.AppSettings["TwitterAPIKey"],
ConsumerSecret = ConfigurationManager.AppSettings["TwitterAPISecret"],
Provider = new TwitterAuthenticationProvider()
{
OnAuthenticated = context =>
{
context.Identity.AddClaim(new Claim("urn:tokens:twitter:accesstoken", context.AccessToken));
context.Identity.AddClaim(new Claim("urn:tokens:twitter:accesstokensecret",
context.AccessTokenSecret));
return Task.FromResult(true);
}
}
});
app.UseMicrosoftAccountAuthentication(new MicrosoftAccountAuthenticationOptions()
{
ClientId = ConfigurationManager.AppSettings["MicrosoftAPIKey"],
ClientSecret = ConfigurationManager.AppSettings["MicrosoftAPISecret"],
// Scope = { "wl.basic", "wl.emails" },
Provider = new MicrosoftAccountAuthenticationProvider()
{
OnAuthenticated = context =>
{
context.Identity.AddClaim(new Claim("urn:microsoftaccount:access_token", context.AccessToken, "Microsoft"));
context.Identity.AddClaim(new Claim("urn:microsoft:email", context.Email));
return Task.FromResult(true);
}
}
});
It has been suggested including Scope = { "wl.basic", "wl.emails" } in MicrosoftAccountAuthenticationOptions. This returns a bad request however. Any ideas on the way to resolve this issue with twitter and microsoft login.
My urls I am using for microsoft are
Redirect Url: https://localhost/signin-microsoft
Logout Url: https://localhost/account/logout
Homepage: https://localhost
Twitter
Website: https://127.0.0.1
Call Back url: https://127.0.0.1/signin-twitter
I have tried with live urls on live also and am still getting null on
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
Try this:
var options = new TwitterAuthenticationOptions
{
SignInAsAuthenticationType = signInAsType,
ConsumerKey = "...",
ConsumerSecret = "...",
Provider = new TwitterAuthenticationProvider()
{
OnAuthenticated = async ctx =>
{
var manager = new OAuth.Manager(
"-your-twitter-access-token-",
"-your-twitter-access-token-secret-",
ctx.AccessToken,
ctx.AccessTokenSecret);
var url = "https://api.twitter.com/1.1/account/verify_credentials.json";
var authzHeader = manager.GenerateAuthzHeader(url, "GET");
var request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "GET";
request.PreAuthenticate = true;
request.AllowWriteStreamBuffering = true;
request.Headers.Add("Authorization", authzHeader);
using (var response = (HttpWebResponse)request.GetResponse())
{
if (response.StatusCode != HttpStatusCode.OK)
throw new Exception("NOK");
var responseStream = response.GetResponseStream();
var reader = new System.IO.StreamReader(responseStream);
var res = reader.ReadToEnd();
Newtonsoft.Json.Linq.JObject data = (Newtonsoft.Json.Linq.JObject)JsonConvert.DeserializeObject(res);
var claims = new List<Claim>();
claims.Add(new Claim(Core.Constants.ClaimTypes.RawData, ctx.Identity.Claims.ToJsonString()));
claims.Add(new Claim(Core.Constants.ClaimTypes.AccessToken, ctx.AccessToken));
claims.Add(new Claim(Core.Constants.ClaimTypes.AccessTokenSecret, ctx.AccessTokenSecret));
claims.Add(new Claim(Core.Constants.ClaimTypes.Subject, ctx.UserId));
claims.Add(new Claim(Core.Constants.ClaimTypes.Name, data["name"].TokenString()));
claims.Add(new Claim(Core.Constants.ClaimTypes.Locale, GenerateLocale(data["lang"].TokenString())));
claims.Add(new Claim(Core.Constants.ClaimTypes.ZoneInfo, GenerateZone(data["location"].TokenString(), data["time_zone"].TokenString())));
claims.Add(new Claim(Core.Constants.ClaimTypes.WebSite, data["url"].TokenString()));
claims.Add(new Claim(Core.Constants.ClaimTypes.ProfileUrl, "https://twitter.com/" + ctx.ScreenName));
claims.Add(new Claim(Core.Constants.ClaimTypes.Picture, data["profile_image_url"].TokenString()));
await PrepClaims(ctx.Identity, claims);
}
}
}

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.

Dashed routing in ASP.NET Core MVC?

Before Core, I used something like this in MVC:
public class HyphenatedRouteHandler : MvcRouteHandler
{
protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
{
requestContext.RouteData.Values["controller"] = requestContext.RouteData.Values["controller"].ToString().Replace("-", "_");
requestContext.RouteData.Values["action"] = requestContext.RouteData.Values["action"].ToString().Replace("-", "_");
return base.GetHttpHandler(requestContext);
}
}
How can I in ASP.Net Core use dashes in urls? ... like http://www.example.com/my-friendly-url ... and convert it to action my_friendly_url.
I don't want to use Attribute Routes.
Thanks
Add this convention in Startup in ConfigureServices method:
options.Conventions.Add(new DashedRoutingConvention());
Routes in UseMvc will not work. They simply will not be considered by ASP.Net itself. I have created issue on GitHub... but not sure how it will go. For now you can specify routes with attributes on methods. Convention will reuse/copy original routes and update/add new dashed path in format {controller}/{action}.
public class DashedRoutingConvention : IControllerModelConvention
{
public void Apply(ControllerModel controller)
{
string parent = this.Convert(controller.ControllerName);
foreach (ActionModel action in controller.Actions)
{
string child = this.Convert(action.ActionName);
string template = $"{parent}/{child}";
if (this.Lookup(action.Selectors, template) == true)
continue;
List<SelectorModel> selectors = action.Selectors.Where(item => item.AttributeRouteModel?.Template == null).ToList();
if (selectors.Count > 0)
{
foreach (SelectorModel existing in selectors)
{
if (existing.AttributeRouteModel == null)
existing.AttributeRouteModel = new AttributeRouteModel();
existing.AttributeRouteModel.Template = template;
}
}
else
{
selectors = action.Selectors.Where(item => item.AttributeRouteModel?.Template != null).ToList();
foreach (SelectorModel existing in selectors)
{
SelectorModel selector = new SelectorModel(existing);
selector.AttributeRouteModel.Template = template;
if (action.Selectors.Any(item => this.Compare(item, selector)) == false)
action.Selectors.Add(selector);
}
}
}
}
private string Convert(string token)
{
if (token == null)
throw new ArgumentNullException(nameof(token));
if (token == string.Empty)
throw new ArgumentException("Failed to convert empty token.");
return Regex.Replace(token, "(?<!^)([A-Z][a-z]|(?<=[a-z])[A-Z])", "-$1", RegexOptions.Compiled).Trim().ToLower();
}
private bool Lookup(IEnumerable<SelectorModel> selectors, string template)
{
foreach (SelectorModel selector in selectors)
{
string current = selector.AttributeRouteModel?.Template;
if (string.Compare(current, template, StringComparison.OrdinalIgnoreCase) == 0)
return true;
}
return false;
}
private bool Compare(SelectorModel existing, SelectorModel adding)
{
if (existing.AttributeRouteModel == null && adding.AttributeRouteModel != null)
return false;
if (existing.AttributeRouteModel != null && adding.AttributeRouteModel == null)
return false;
if (existing.AttributeRouteModel != null && adding.AttributeRouteModel != null)
{
if (existing.AttributeRouteModel.Template != adding.AttributeRouteModel.Template)
return false;
if (existing.AttributeRouteModel.Order != adding.AttributeRouteModel.Order)
return false;
}
if (existing.ActionConstraints == null && adding.ActionConstraints != null)
return false;
if (existing.ActionConstraints != null && adding.ActionConstraints == null)
return false;
if (existing.ActionConstraints != null && adding.ActionConstraints != null)
{
if (existing.ActionConstraints.Count != adding.ActionConstraints.Count)
return false;
}
return true;
}
}

ID not showing in the parameter

I have a code that sends email to user. In the email, there's a link in there that they should visit with the corresponding ID in order for them to be directed to a certain page.
Here's my code:
public void Notify(int appr_id = 0)
{
var check = db.rms_approval_routing.Where(s => s.appr_id == appr_id && s.appr_isactive == true).FirstOrDefault();
try
{
if (check != null)
{
check.status_id = 8;
db.Entry(check).State = EntityState.Modified;
db.SaveChanges();
var getinfo = db.rms_approval_route_vw.Where(s => s.appr_id == appr_id && s.appr_isactive == true).FirstOrDefault();
var getpayment = db.rms_payment.Where(s => s.appr_id == appr_id).FirstOrDefault();
if (getinfo != null && getpayment != null)
{
var ref_email = getinfo.ref_email;
var cc_email = getinfo.user_email;
var pay = getpayment.p_amount;
var body = "";
body = "Good day!<br><br>Please be informed that you have successfully referred <u>" + getinfo.Fullname + "</u> and you are entitled to receive <u>P " + pay + "</u> which will be credited on the next payout for your successful referral.<br>Kindly visit the link to acknowledge the payment: http://localhost:8119/ReferralLetter/Acknowledge/" + appr_id + " <br>Thanks!";
SendEmailController email = new SendEmailController();
email.SendReferrer(ref_email, cc_email, body);
}
}
}
catch (Exception)
{
throw;
}
}
public ActionResult Acknowledge(int appr_id = 0)
{
var check = db.rms_emails.Where(s => s.appr_id == appr_id && s.email_date_ack == null && s.email_isactive == true).FirstOrDefault();
if (check != null) {
ViewBag.email_id = check.email_id;
ViewBag.appr_id = appr_id;
return PartialView();
}
return RedirectToAction("Denied");
}
In this line: http://localhost:8119/ReferralLetter/Acknowledge/" + appr_id
The appr_id value is 0 when I tried to breakpoint the Acknowledge function. When I received the email, it showed there this line: http://localhost:8119/ReferralLetter/Acknowledge/23
Meaning there's an ID in there but why in the Acknowledge function the ID was 0?

using #User.IsInRole("Admin") in MVC VIEW

I using #User.IsInRole("Admin") in mvc4
when i use this command in global.asax it return true but when I use in view of mvc it return sql server connection error .
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
if (Context.User == null)
return;
var userService = ObjectFactory.GetInstance<IUserService>();
UserStatus userStatus = userService.GetStatus(Context.User.Identity.Name);
if (userStatus.IsBaned)
FormsAuthentication.SignOut();
HttpCookie authCookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie == null || authCookie.Value == "")
return;
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
// retrieve roles from UserData
if (authTicket == null) return;
string[] roles = authTicket.UserData.Split(',');
if (userStatus.Role != roles[0])
FormsAuthentication.SignOut();
Context.User = new GenericPrincipal(Context.User.Identity, roles);
}

Resources