Using Microsoft.AspNet.Membership.OpenAuth I've very quickly been able to get an app logging people in via various external provider (facebook, twitter, google, microsoft). That's great.
My question is, can I use this framework to get the users profile information easily? I'd like to get their profile picture, email address, DOB etc if the service has them.
I know there is some extra information in AuthenticationResult.ExtraData however it isn't standard and doesn't contain what I need.
Is there something I can do from here to get the information using Microsoft.AspNet.Membership.OpenAuth (or another .net lib) or will I need to use the access token in ExtraData to access the different services manually via the separate service APIs?
Thanks
There are two main issues to overcome with the authentication workflow stuff. One, as the OP rightly pointed out concerns the contents of ExtraData and the other concerns the permissions that you need to ask Facebook for. In the end I went with the DotNetOpenAuth library rather than the Microsoft one but extended it in certain places by rolling my own classes and borrowing parts of the framework where necessary.
The first thing I did was create a FacebookClient that extended the OAuth2Client from DotNetOpenAuth and allowed me to pass in a value for scope, which got past the limitation on which data you could request from Facebook. The permission I ask for are publish_stream, manage_pages, email, user_interests. They simply get appended to the service login URL and passed across to Facebook. The useful method within my implementation of OAuth2Client is GetUserData:
protected override IDictionary<string, string> GetUserData(string accessToken)
{
var token = accessToken.EscapeUriDataStringRfc3986();
FacebookGraphData graphData;
var request = WebRequest.Create(string.Format("https://graph.facebook.com/me?access_token={0}", token));
using (var response = request.GetResponse())
{
using (var responseStream = response.GetResponseStream())
{
graphData = JsonHelper.Deserialize<FacebookGraphData>(responseStream);
}
}
var userData = new Dictionary<string, string> {{"accessToken", accessToken}};
userData.AddItemIfNotEmpty("id", graphData.Id);
userData.AddItemIfNotEmpty("name", graphData.Name);
userData.AddItemIfNotEmpty("email", graphData.Email);
userData.AddItemIfNotEmpty("firstName", graphData.FirstName);
userData.AddItemIfNotEmpty("lastName", graphData.LastName);
userData.AddItemIfNotEmpty("link", graphData.Link == null ? null : graphData.Link.AbsoluteUri);
userData.AddItemIfNotEmpty("username", graphData.Username);
userData.AddItemIfNotEmpty("gender", graphData.Gender);
userData.AddItemIfNotEmpty("locale", graphData.Locale);
FacebookFriendData friendData;
request = WebRequest.Create(string.Format("https://graph.facebook.com/me/friends?access_token={0}", token));
using (var response = request.GetResponse())
{
using (var responseStream = response.GetResponseStream())
{
friendData = JsonHelper.Deserialize<FacebookFriendData>(responseStream);
}
}
if (friendData.Friends != null)
{
userData.Add("connections", friendData.Friends.Count().ToString());
}
return userData;
}
I've basically created a few data classes that are deserialized when the response comes back from Facebook. I can also make any other Graph API calls from here that I need. Serialization classes look like this:
[DataContract]
public class FacebookFriendData
{
[DataMember(Name = "data")]
public IEnumerable<Friend> Friends { get; set; }
}
[DataContract]
public class Friend
{
[DataMember(Name = "id")]
public string Id { get; set; }
[DataMember(Name = "name")]
public string Name { get; set; }
}
[DataContract]
public class FacebookGraphData
{
[DataMember(Name = "id")]
public string Id { get; set; }
[DataMember(Name = "name")]
public string Name { get; set; }
[DataMember(Name = "email")]
public string Email { get; set; }
[DataMember(Name = "first_name")]
public string FirstName { get; set; }
[DataMember(Name = "last_name")]
public string LastName { get; set; }
[DataMember(Name = "link")]
public Uri Link { get; set; }
[DataMember(Name = "username")]
public string Username { get; set; }
[DataMember(Name = "gender")]
public string Gender { get; set; }
[DataMember(Name = "locale")]
public string Locale { get; set; }
}
As per #radm4 I still check the provider string to decide which methods to call in certain places - still working on a more elegant solution to that one...
After looking around I can't see a way to do this only with the Microsoft.AspNet.Membership.OpenAuth stuff.
My solution has been to skip the .NET oauth stuff if the user wants to use facebook. In this case I use the facebook C# SDK to authenticate and then I can access email, birthday, photo etc.
if(provider != "facebook")
{
//do normal oauth
}
else
{
FacebookClient client = new FacebookClient();
var loginUrl = client.GetLoginUrl(new
{
client_id = ConfigurationManager.AppSettings["facebookId"],
client_secret = ConfigurationManager.AppSettings["facebookClientSecret"],
redirect_uri = redirectUrl,
response_type = "code",
scope = "email,user_birthday"
});
Response.Redirect(loginUrl.AbsoluteUri);
}
When the user returns you can then use the SDK to access this extra information.
I'm going to add this type of thing for Google too. If users want to use other services to log in they can but I'll just use asp.net oauth and they won't get as customised an experience as facebook or google until I have more time to spend on each provider.
So basically the answer is ASP.net oauth is fine for logging in and very basic information but if you need more you'll need to extend or by pass it for each provider.
Related
I am using Duende Identity server and I have an external authentication provider lets say google. While logging into google we get tokens from google which we can make use of calling some google API's.
I need to return the google token also to the client side(Angular/WPF/MVC etc) through Duende token endpoint.
I can see from the code that Duende token endpoint response has a Custom property, but I have no clue how or from where I can insert my values.
From Duende Source Code
internal class ResultDto
{
public string id_token { get; set; }
public string access_token { get; set; }
public int expires_in { get; set; }
public string token_type { get; set; }
public string refresh_token { get; set; }
public string scope { get; set; }
[JsonExtensionData]
public Dictionary<string, object> Custom { get; set; }
}
I would like to see some code snippets or direction on how to add values to this Custom property by existing Duende functionality.
If you need to customize token response you can ICustomTokenResponseGenerator (It is for identity server 3, if you are using version 4 and above I am not sure but it should be ITokenResponseGenerator):
class CustomTokenResponseGenerator : ICustomTokenRequestValidator
{
public Task<TokenResponse> GenerateAsync(ValidatedTokenRequest request, TokenResponse response)
{
response.Custom.Add("custom_field", "custom data");
return Task.FromResult(response);
}
}
and then add it with factory:
factory.CustomTokenResponseGenerator = new Registration<ICustomTokenResponseGenerator, CustomTokenResponseGeneratorService>();
I have used HTTP Client for calling RESTFul services. Now i have requirement where i have to pass the FormCollection object to the API. The API is not a REST API. More information about API, you can see in this link. http://docs.pay4later.com/docs/requests
I thought of using HTTPCLINET to impliment this. with the below code, i was able to get the response.
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("https://testurl/");
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("Identification[api_key]", "somekey"),
new KeyValuePair<string, string>("Identification[InstallationID]", "installationid"),
new KeyValuePair<string, string>("action", "credit_application_link"),
new KeyValuePair<string, string>("Goods[Description]", "test"),
new KeyValuePair<string, string>("Identification[RetailerUniqueRef]", Guid.NewGuid().ToString()),
new KeyValuePair<string, string>("Goods[Price]", "100000"),
new KeyValuePair<string, string>("Finance[Code]", "PQERTS"),
new KeyValuePair<string, string>("Finance[Deposit]", "92000")
});
var result = client.PostAsync("", content).Result;
string resultContent = result.Content.ReadAsStringAsync().Result;
}
The above one works perfectly. However, i wanted to build a model and wanted to send that model using HttpClient. But that was not successful, since the request was going as json object. The model looks like below,
public class CreditApplicationInitializationRequest
{
public string action { get; set; }
public Identification Identification { get; set; }
public Goods Goods { get; set; }
public Finance Finance { get; set; }
}
public class Identification
{
public string api_key { get; set; }
public string RetailerUniqueRef { get; set; }
public string InstallationID { get; set; }
}
I wanted to know, whether this appoach is possible or is there any other standard approach using httpclient to do so.
Thanks for your help.
Looks like you're on the right track; checkout Newtonsoft.Json - it's a NuGet package that provides ways to work with Json. In particular, you can annotate your properties with attributes to control the way the package serialises and de-serialises your objects into Json objects.
An example looks like:
[JsonObject(MemberSerialization.OptIn)]
public class Person
{
// "John Smith"
[JsonProperty]
public string Name { get; set; }
// "2000-12-15T22:11:03"
[JsonProperty]
public DateTime BirthDate { get; set; }
// new Date(976918263055)
[JsonProperty]
[JsonConverter(typeof(JavaScriptDateTimeConverter))]
public DateTime LastModified { get; set; }
// not serialized because mode is opt-in
public string Department { get; set; }
}
You can find more info at http://www.newtonsoft.com/json/help/html/SerializationAttributes.htm.
HttpClient is only concerned with sending/receiving raw content; serializing is left to other libraries. I was going to suggest Flurl, which allows you to form-post an object with its PostUrlEncodedAsync method, but it assumes the object properties are simple types (it just does a ToString with the values). The serialization rules you're working with look fairly custom, so I think you're stuck rolling your own thing.
I am wondering if anyone can assist me, I am having a problem with power bi. What I am trying to do is push some data into Power BI. I am finding it difficult to find an approach were a user can enter there user name and password and then I will be able to push data into the Power BI account.
I am getting stuck at the first hurdle of getting an Access Token. I just keeping bad request. I have also tried getting-started-for-dotnet which for some strange reason I can also not get to work.
Error given is: The remote server returned an error: (401) Unauthorized.
Registered as Web application
public class PowerBICreds
{
public string resourceUri { get; set; }
public string clientID { get; set; }
public string grantType { get; set; }
public string username { get; set; }
public string password { get; set; }
public string scope { get; set; }
public string clientSecret { get; set; }
public string loginAddress { get; set; }
public string baseurl { get; set; }
}
public static string AccessToken(PowerBICreds Creds)
{
StringBuilder Httpbody = new StringBuilder();
Httpbody.Append("resource=" + HttpUtility.UrlEncode(Creds.resourceUri));
Httpbody.Append("&client_id=" + HttpUtility.UrlEncode(Creds.clientID));
Httpbody.Append("&grant_type=" + HttpUtility.UrlEncode(Creds.grantType));
Httpbody.Append("&username=" + HttpUtility.UrlEncode(Creds.username));
Httpbody.Append("&password=" + HttpUtility.UrlEncode(Creds.password));
Httpbody.Append("&scope=" + HttpUtility.UrlEncode(Creds.scope));
Httpbody.Append("&client_secret=" + HttpUtility.UrlEncode(Creds.clientSecret));
using (WebClient web = new WebClient())
{
web.Headers.Add("client-request-id", Guid.NewGuid().ToString());
web.Headers.Add("return-client-request-id", "true");
string jsonstring = web.UploadString(Creds.loginAddress, Httpbody.ToString());
dynamic result = JsonConvert.DeserializeObject(jsonstring);
try
{
return result.access_token;
}
catch
{
}
return null;
}
}
Update when I try the sample to show how to use the Power BI API provided by Mircosoft here https://github.com/PowerBI/getting-started-for-dotnet
Additional technical information:
Correlation ID: f1281ec2-4e09-41e6-8847-3acfd3eb7922
Timestamp: 2015-12-04 22:48:58Z
AADSTS65005: The client application has requested access to resource 'https://analysis.windows.net/powerbi/api'. This request has failed because the client has not specified this resource in its requiredResourceAccess list.
The error you got when using our sample application may mean that the app you registered with AAD doesn't request any permission for Power BI. Try using our new app registration page at http://dev.powerbi.com/apps. If you just want to push data into Power BI, you just need the dataset read/write permission.
Can the Microsoft.Phone.Maps.Services namespace be used in a Windows Store Apps app?
If not, is there a suitable alternative available?
I was directed to this which shows some code that's just what the non-doctor ordered for getting gobs of geolocation data based on a search term such as an address.
HOWEVER
The classes used in that snippet (in the Maps_GeoCoding event and the QueryCompleted callback) are from the Microsoft.Phone.Maps.Services namespace, and I need this or similar code for a Windows store apps app (I knew that "Windows Store app" nomenclature would lead to some awkwardness).
Is anybody aware of an analogous set of functionality? Or, is it possible, though counter-intuitive sounding, that one could actually use the Microsoft.Phone.Maps.Services namespace within a Windows Store apps app?
UPDATE
This is what I did (ad[a,o]pted from Justin "Teen" Angel's code below, with appId and appCode not shown):
private async static Task<string> GetCoordinatesForAddress(string address) // AKA Geocoding (reverse geocoding is getting address for coordinates)
{
// build URL for Here.net REST service
string currentgeoLoc = "0.0,0.0";
string queryString = address; //"Ferry Building, San-Francisco";
string appID = "<appId>"; // MAKE SURE TO GET YOUR OWN from developers.here.net
object appCode = "<appCode>"; // MAKE SURE TO GET YOUR OWN from developers.here.net
var hereNetUrl = string.Format(
"http://demo.places.nlp.nokia.com/places/v1/discover/search?at={0}&q={1}&app_id={2}&app_code={3}&accept=application/json",
currentgeoLoc, queryString, appID, appCode);
// get data from HERE.net REST API
var httpClient = new HttpClient();
var hereNetResponse = await httpClient.GetStringAsync(hereNetUrl);
// deseralize JSON from Here.net
using (var tr = new StringReader(hereNetResponse))
using (var jr = new JsonTextReader(tr))
{
var rootObjectResponse = new JsonSerializer().Deserialize<JsonDOTNetHelperClasses.RootObject>(jr);
var firstplace = rootObjectResponse.results.items.First();
return string.Format("{0};{1}", firstplace.position[0], firstplace.position[1]);
}
}
The WP8 Nokia <Maps /> control and its associated services (routing, geocoding, etc) aren't currently available in the Win8 SDK. Win8 apps are expected to use Bing Maps APIs.
However, if you do want to use Nokia Maps functionality in your Win8 app, that's definitely possible. Here.net (Nokia's location portal) exposes publicly documented web APIs. You can use the "core plan" that allows up to 2,500 free queries/day from the here.net REST APIs. Those REST APIs include geocoding, reverse geocoding, pedestrian routing, driving routing and more.
You can see examples for these REST APIs # http://developer.here.net/javascript_api_explorer (click "REST API Explorer" in the top right since this view defaults to the javascript API explorer). The Geocoding APIs will be available under "Places".
For example, here's how to replicate the WP8 Maps GeoCoding sample using REST APIs on Win8:
private async void GeocodingWin8Query()
{
// build URL for Here.net REST service
string currentgeoLoc = "0.0,0.0";
string queryString = "Ferry Building, San-Francisco";
string appID = "<appId>"; // MAKE SURE TO GET YOUR OWN from developers.here.net
object appCode = "<appCode>"; // MAKE SURE TO GET YOUR OWN from developers.here.net
var hereNetUrl = string.Format(
"http://demo.places.nlp.nokia.com/places/v1/discover/search?at={0}&q={1}&app_id={2}&app_code={3}&accept=application/json",
currentgeoLoc, queryString, appID, appCode);
// get data from HERE.net REST API
var httpClient = new HttpClient();
var hereNetResponse = await httpClient.GetStringAsync(hereNetUrl);
// deseralize JSON from Here.net
using (var tr = new StringReader(hereNetResponse))
using (var jr = new JsonTextReader(tr))
{
var rootObjectResponse = new JsonSerializer().Deserialize<RootObject>(jr);
// print the details of the first geocoding result
var firstplace = rootObjectResponse.results.items.First();
await new MessageDialog("Name: " + firstplace.title + Environment.NewLine +
"Geolocation: " + firstplace.position[0] + ", " + firstplace.position[1] + Environment.NewLine +
"Address: " + HtmlUtilities.ConvertToText(firstplace.vicinity) + Environment.NewLine +
"Type: " + firstplace.type + Environment.NewLine,
"Win8 Nokia Maps Geocoding").ShowAsync();
}
}
When we run this code snippet we can see that Win8 has access to the same Geocoding data as WP8:
There's lots more this API can do, like reverse geocoding, routing, etc. As I mentioned you can explore those features at the Here.net REST APIs here (click "REST API explorer" in the top right). Also, don't forgot to sign up for an AppID and AppCode after signing in.
For the code above to work I've used JSON.Net. You'll need to install JSON.net from NuGet and copy some strongly-typed generated classes over from json2csharp. Here's how to install JSON.net:
And here are the generated C# JSON.net classes:
public class Category
{
public string id { get; set; }
public string title { get; set; }
public string href { get; set; }
public string type { get; set; }
}
public class Item
{
public List<double> position { get; set; }
public int distance { get; set; }
public string title { get; set; }
public Category category { get; set; }
public string icon { get; set; }
public string vicinity { get; set; }
public List<object> having { get; set; }
public string type { get; set; }
public string href { get; set; }
public string id { get; set; }
public double? averageRating { get; set; }
}
public class Results
{
public List<Item> items { get; set; }
}
public class Location
{
public List<double> position { get; set; }
}
public class Context
{
public Location location { get; set; }
public string type { get; set; }
}
public class Search
{
public Context context { get; set; }
}
public class RootObject
{
public Results results { get; set; }
public Search search { get; set; }
}
While it may be possible, I don't think its reliable.
Why would you want to do this when there is a Maps SDK built in (which runs on Bing Maps)
Here's a tutorial
I think you should look at
Consider a WCF service using WsHttpBinding for which only the domain users are allowed to call this service.
How can you find the Active Directory username of the caller?
Get the value of System.ServiceModel.ServiceSecurityContext.Current.WindowsIdentity.Name property.
It does not matter which binding you use as long as the security mode is different from None for the binding.
If the security mode is None then System.ServiceModel.ServiceSecurityContext.Current will be null.
You can get identity of the user by calling:
ServiceSecurityContext.Current.WindowsIdentity.Name
or
OperationContext.Current.ServiceSecurityContext.WindowsIdentity.Name
You will have to add some sort of User information to the message structure you are using to contact the service.
e.g.
public class UserInformation
{
public string User { get; set; }
public string Password { get; set; }
}
[DataContract]
public class Request
{
[DataMember]
public UserInformation User { get; set; }
[DataMember]
public MyRequest RequestBody { get; set; }
}
This way you can query active directory at your client side, populate the UserInformation object and send over the user details as part of the message structure.