Migrating from V5 to V6 of the Facebook C# SDK - c#

I'm trying to migrate from V5.3.2 to V6 of the SDK. I've got an ASP.NET 4.0 Canvas application. I noticed that now there's no more facebook.web.dll (which I previously used), and found this info:
>
Removal of Facebook.Web.dll and Facebook.Web.Mvc.dll
Starting from v6, we are depreciating Facebook.Web.dll and Facebook.Web.Mvc.dll and will no longer be providing it.
...
Starting from v6, you will have to use the Facebook Javascript SDK to get the access token and pass it to the server using secure https connection or use the Facebook OAuth Login Dialog.
Decoding signed request (ParseSignedRequest/TryParseSignedRequest) has been moved to FacebookClient instead.
var fb = new FacebookClient();
dynamic signedRequest = fb.ParseSignedRequest("app_secret", Request.Params["signed_request"]);
<<<<
(http://blog.prabir.me/post/Facebook-CSharp-SDK-Glimpse-into-the-Future.aspx)
So that's all well and good. Previously, I had this in my code behind:
protected void Page_Load(object sender, EventArgs e)
{
var auth = new CanvasAuthorizer { Permissions = new[] { "user_about_me" } };
if (auth.Authorize())
{
ShowFacebookContent();
}
}
To change it, I've now got the Javascript SDK successfully loading and logging in my user:
<div id="fb-root"></div>
<script type="text/javascript">
FB.init({ appId: 'xxxxxxx', cookie: true, status: true, oauth: true });
FB.getLoginStatus(function (response) {
if (response) {
alert(response.authResponse.accessToken);
}
});
</script>
I've tested this and it successfully logs in the user. But how do I then get it to do a postback and call the server side method that it was calling before (ShowFacebookContent)? I assume that whatever I do will have to pass the accessToken or the SignedRequest so that fb.ParseSignedRequest will work and on the server we can generate a FacebookClient.
I'm guessing that several people will be in a similar situation, trying to migrate off of facebook.web.dll, so any guidance would be really great.

Depending on your need pass either the signed_request or the access_token.
And also don't pass the access_token over http. Use secure https connection.

Related

Using Blazor Client-Side consuming Web API with Windows Authentication

Currently I've got an application running with and angular client, consuming a Web API with Windows Authentication.
Now I'm looking into replacing this front end with Blazor (client-side), however I'm facing some challenges when it comes to authentication.
In angular I just set withCredentials to true in order to submit the required information.
The code below works as intended using Blazor server-side, but since I want to use Blazor client-side it's not an option and doesn't help me much.
IEnumerable<SearchView> searchResults;
int NumberOfItems;
protected override async Task OnInitAsync()
{
using (var client = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true }))
{
var result = await client.GetJsonAsync<Response<SearchView>>("http://localhost:80/search");
NumberOfItems = result.TotalItemCount;
searchResults = result.Items;
}
}
}
The above code throws an "PlatformNotsupportedException".
WASM: System.PlatformNotSupportedException: System.Net.Http.HttpClientHandler is not supported on the current platform.
WASM: at System.Net.Http.HttpClientHandler.set_UseDefaultCredentials (System.Boolean value) <0x1d63160 + 0x0000c> in <4399d2484a2a46159ade8054ed94c78e>:0
Clearly the code provided is not supported using Blazor client-side, but if there are any alternative ways to achieve what I want to, any pointers and help would be appreciated.
I've just hit the same problem and couldn't get it working with HttpClient, but I did manage it with a HttpRequestMessage:
string APIURL = "https://localhost:44390/api/models";
// create request object and pass windows authentication credentials
var request = new HttpRequestMessage(HttpMethod.Get, APIURL);
request.SetBrowserRequestCredentials(BrowserRequestCredentials.Include);
// send the request and convert the results to a list
var httpResponse = await Http.SendAsync(request);
models = await httpResponse.Content.ReadFromJsonAsync<myModel[]>();
This is not (yet) possible. Blazor client-side runs on the Mono runtime of the .net framework which does not support Windows Authentication.
Your best option is to implement a token based auth (JWT for instance) and use ADFS.

Google Data API Authorization Redirect URI Mismatch

Background
I am wanting to write a small, personal web app in .NET Core 1.1 to interact with YouTube and make some things easier for me to do and I am following the tutorials/samples in Google's YouTube documentation. Sounds simple enough, right? ;)
Authenticating with Google's APIs seems impossible! I have done the following:
Created an account in the Google Developer Console
Created a new project in the Google Developer Console
Created a Web Application OAuth Client ID and added my Web App debug URI to the list of approved redirect URIs
Saved the json file provided after generating the OAuth Client ID to my system
In my application, my debug server url is set (and when my application launches in debug, it's using the url I set which is http://127.0.0.1:60077).
However, when I attempt to authenticate with Google's APIs, I recieve the following error:
That’s an error.
Error: redirect_uri_mismatch
The redirect URI in the request, http://127.0.0.1:63354/authorize/,
does not match the ones authorized for the OAuth client.
Problem
So now, for the problem. The only thing I can find when searching for a solution for this is people that say
just put the redirect URI in your approved redirect URIs
Unfortunately, the issue is that every single time my code attempts to authenticate with Google's APIs, the redirect URI it is using changes (the port changes even though I set a static port in the project's properties). I cannot seem to find a way to get it to use a static port. Any help or information would be awesome!
NOTE: Please don't say things like "why don't you just do it this other way that doesn't answer your question at all".
The code
client_id.json
{
"web": {
"client_id": "[MY_CLIENT_ID]",
"project_id": "[MY_PROJECT_ID]",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://accounts.google.com/o/oauth2/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_secret": "[MY_CLIENT_SECRET]",
"redirect_uris": [
"http://127.0.0.1:60077/authorize/"
]
}
}
Method That Is Attempting to Use API
public async Task<IActionResult> Test()
{
string ClientIdPath = #"C:\Path\To\My\client_id.json";
UserCredential credential;
using (var stream = new FileStream(ClientIdPath, FileMode.Open, FileAccess.Read))
{
credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
new[] { YouTubeService.Scope.YoutubeReadonly },
"user",
CancellationToken.None,
new FileDataStore(this.GetType().ToString())
);
}
var youtubeService = new YouTubeService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = this.GetType().ToString()
});
var channelsListRequest = youtubeService.Channels.List("contentDetails");
channelsListRequest.Mine = true;
// Retrieve the contentDetails part of the channel resource for the authenticated user's channel.
var channelsListResponse = await channelsListRequest.ExecuteAsync();
return Ok(channelsListResponse);
}
Project Properties
The Original Answer works, but it is NOT the best way to do this for an ASP.NET Web Application. See the update below for a better way to handle the flow for an ASP.NET Web Application.
Original Answer
So, I figured this out. The issue is that Google thinks of a web app as a JavaScript based web application and NOT a web app with server side processing. Thus, you CANNOT create a Web Application OAuth Client ID in the Google Developer Console for a server based web application.
The solution is to select the type Other when creating an OAuth Client ID in the Google Developer Console. This will have Google treat it as an installed application and NOT a JavaScript application, thus not requiring a redirect URI to handle the callback.
It's somewhat confusing as Google's documentation for .NET tells you to create a Web App OAuth Client ID.
Feb 16, 2018 Updated Better Answer:
I wanted to provide an update to this answer. Though, what I said above works, this is NOT the best way to implement the OAuth workflow for a ASP.NET solution. There is a better way which actually uses a proper OAuth 2.0 flow. Google's documentation is terrible in regards to this (especially for .NET), so I'll provide a simple implementation example here. The sample is using ASP.NET core, but it's easily adapted to the full .NET framework :)
Note: Google does have a Google.Apis.Auth.MVC package to help simplifiy this OAuth 2.0 flow, but unfortunately it's coupled to a specific MVC implementation and does not work for ASP.NET Core or Web API. So, I wouldn't use it. The example I'll be giving will work for ALL ASP.NET applications. This same code flow can be used for any of the Google APIs you've enabled as it's dependent on the scopes you are requesting.
Also, I am assuming you have your application set up in your Google Developer dashboard. That is to say that you have created an application, enabled the necessary YouTube APIs, created a Web Application Client, and set your allowed redirect urls properly.
The flow will work like this:
The user clicks a button (e.g. Add YouTube)
The View calls a method on the Controller to obtain an Authorization URL
On the controller method, we ask Google to give us an Authorization URL based on our client credentials (the ones created in the Google Developer Dashboard) and provide Google with a Redirect URL for our application (this Redirect URL must be in your list of accepted Redirect URLs for your Google Application)
Google gives us back an Authorization URL
We redirect the user to that Authorization URL
User grants our application access
Google gives our application back a special access code using the Redirect URL we provided Google on the request
We use that access code to get the Oauth tokens for the user
We save the Oauth tokens for the user
You need the following NuGet Packages
Google.Apis
Google.Apis.Auth
Google.Apis.Core
Google.apis.YouTube.v3
The Model
public class ExampleModel
{
public bool UserHasYoutubeToken { get; set; }
}
The Controller
public class ExampleController : Controller
{
// I'm assuming you have some sort of service that can read users from and update users to your database
private IUserService userService;
public ExampleController(IUserService userService)
{
this.userService = userService;
}
public async Task<IActionResult> Index()
{
var userId = // Get your user's ID however you get it
// I'm assuming you have some way of knowing if a user has an access token for YouTube or not
var userHasToken = this.userService.UserHasYoutubeToken(userId);
var model = new ExampleModel { UserHasYoutubeToken = userHasToken }
return View(model);
}
// This is a method we'll use to obtain the authorization code flow
private AuthorizationCodeFlow GetGoogleAuthorizationCodeFlow(params string[] scopes)
{
var clientIdPath = #"C:\Path\To\My\client_id.json";
using (var fileStream = new FileStream(clientIdPath, FileMode.Open, FileAccess.Read))
{
var clientSecrets = GoogleClientSecrets.Load(stream).Secrets;
var initializer = new GoogleAuthorizationCodeFlow.Initializer { ClientSecrets = clientSecrets, Scopes = scopes };
var googleAuthorizationCodeFlow = new GoogleAuthorizationCodeFlow(initializer);
return googleAuthorizationCodeFlow;
}
}
// This is a route that your View will call (we'll call it using JQuery)
[HttpPost]
public async Task<string> GetAuthorizationUrl()
{
// First, we need to build a redirect url that Google will use to redirect back to the application after the user grants access
var protocol = Request.IsHttps ? "https" : "http";
var redirectUrl = $"{protocol}://{Request.Host}/{Url.Action(nameof(this.GetYoutubeAuthenticationToken)).TrimStart('/')}";
// Next, let's define the scopes we'll be accessing. We are requesting YouTubeForceSsl so we can manage a user's YouTube account.
var scopes = new[] { YouTubeService.Scope.YoutubeForceSsl };
// Now, let's grab the AuthorizationCodeFlow that will generate a unique authorization URL to redirect our user to
var googleAuthorizationCodeFlow = this.GetGoogleAuthorizationCodeFlow(scopes);
var codeRequestUrl = googleAuthorizationCodeFlow.CreateAuthorizationCodeRequest(redirectUrl);
codeRequestUrl.ResponseType = "code";
// Build the url
var authorizationUrl = codeRequestUrl.Build();
// Give it back to our caller for the redirect
return authorizationUrl;
}
public async Task<IActionResult> GetYoutubeAuthenticationToken([FromQuery] string code)
{
if(string.IsNullOrEmpty(code))
{
/*
This means the user canceled and did not grant us access. In this case, there will be a query parameter
on the request URL called 'error' that will have the error message. You can handle this case however.
Here, we'll just not do anything, but you should write code to handle this case however your application
needs to.
*/
}
// The userId is the ID of the user as it relates to YOUR application (NOT their Youtube Id).
// This is the User ID that you assigned them whenever they signed up or however you uniquely identify people using your application
var userId = // Get your user's ID however you do (whether it's on a claim or you have it stored in session or somewhere else)
// We need to build the same redirect url again. Google uses this for validaiton I think...? Not sure what it's used for
// at this stage, I just know we need it :)
var protocol = Request.IsHttps ? "https" : "http";
var redirectUrl = $"{protocol}://{Request.Host}/{Url.Action(nameof(this.GetYoutubeAuthenticationToken)).TrimStart('/')}";
// Now, let's ask Youtube for our OAuth token that will let us do awesome things for the user
var scopes = new[] { YouTubeService.Scope.YoutubeForceSsl };
var googleAuthorizationCodeFlow = this.GetYoutubeAuthorizationCodeFlow(scopes);
var token = await googleAuthorizationCodeFlow.ExchangeCodeForTokenAsync(userId, code, redirectUrl, CancellationToken.None);
// Now, you need to store this token in rlation to your user. So, however you save your user data, just make sure you
// save the token for your user. This is the token you'll use to build up the UserCredentials needed to act on behalf
// of the user.
var tokenJson = JsonConvert.SerializeObject(token);
await this.userService.SaveUserToken(userId, tokenJson);
// Now that we've got access to the user's YouTube account, let's get back
// to our application :)
return RedirectToAction(nameof(this.Index));
}
}
The View
#using YourApplication.Controllers
#model YourApplication.Models.ExampleModel
<div>
#if(Model.UserHasYoutubeToken)
{
<p>YAY! We have access to your YouTube account!</p>
}
else
{
<button id="addYoutube">Add YouTube</button>
}
</div>
<script>
$(document).ready(function () {
var addYoutubeUrl = '#Url.Action(nameof(ExampleController.GetAuthorizationUrl))';
// When the user clicks the 'Add YouTube' button, we'll call the server
// to get the Authorization URL Google built for us, then redirect the
// user to it.
$('#addYoutube').click(function () {
$.post(addYoutubeUrl, function (result) {
if (result) {
window.location.href = result;
}
});
});
});
</script>
As referred here, you need to specify a fix port for the ASP.NET development server like How to fix a port number in asp.NET development server and add this url with the fix port to the allowed urls. Also as stated in this thread, when your browser redirects the user to Google's oAuth page, you should be passing as a parameter the redirect URI you want Google's server to return to with the token response.
I noticed that there is easy non-programmatic way around.
If you have typical monotlith application built in typical MS convention(so not compatible with 12factor and typical DDD) there is an option to tell your Proxy WWW server to rewrite all requests from HTTP to HTTPS so even if you have set up Web App on http://localhost:5000 and then added in Google API url like: http://your.domain.net/sigin-google, it will work perfectly and it is not that bas because it is much safer to set up main WWW to rewrite all to HTTPS.
It is not very good practice I guess however it makes sense and does the job.
I've struggled with this issue for hours in a .net Core application. What finally fixed it for me was, in the Google developers console, to create and use a credential for "Desktop app" instead of a "Web application".
Yeah!! Using credentials of desktop app instead of web app worked for me fine. It took me more than 2 days to figure out this problem. The main problem is that google auth library dose not adding or supporting http://localhost:8000 as redirect uri for web app creds but credentials of desktop app fixed that issue. Cause its supporting http://___ connection instead of https: connection for redirect uri

Braintree: Generate Client Token in ASP.NET MVC 4.5

I'm having some issues integrating Braintree, as well as understanding the concept of how the transaction takes place.
Here's how i currently understand Braintree:
Server Generates ClientToken -> Integrates into html/js -> User receives payment form and sends data to Braintree -> Nonce is sent to Server -> Server sends Transaction to Braintree
Is this correct?
I'm currently on step 1, trying to generate a client token, and i'm getting a NullReferenceException:
public ActionResult Payment(EditContainerViewModel newEdit)
{
//generate client token
newEdit.PaymentInfo.ClientToken = PaymentConstants.Gateway.ClientToken.generate();
return View(newEdit);
}
And heres the Gateway declaration:
public static class PaymentConstants
{
public static BraintreeGateway Gateway = new BraintreeGateway
{
Environment = Braintree.Environment.SANDBOX,
MerchantId = "id",
PublicKey = "publickey",
PrivateKey = "privatekey"
};
}
Heres my view:
#model Sandbox.Models.EditContainerViewModel
<h2>Payment</h2>
<form id="checkout" method="post" action="/checkout">
<div id="payment-form"></div>
<input type="submit" value="Pay $10">
</form>
<script src="https://js.braintreegateway.com/v2/braintree.js"></script>
<script>
// We generated a client token for you so you can test out this code
// immediately. In a production-ready integration, you will need to
// generate a client token on your server (see section below).
var clientToken = "#Html.Raw(Model.PaymentInfo.ClientToken)";
braintree.setup(clientToken, "dropin", {
container: "payment-form"
});
</script>
I'd really appreciate any insight on this topic, especially as i found that the provided ASP.NET examples didn't show the token generation stage.
Thank you!
As said in the comment, i was far too focused on Braintree that i made an extremely beginner error. This serves as a great reminder that you can find the answer by just taking a step back and looking at the error from a new perspective. :)

Persistent authentication across UWP app and Azure Mobile Service

Building on the example here I'm attempting to authenticate an MSA login on the client, and have it authenticate service-side as well. The difference with mine is I'm using the new WebAccount-related API's in Windows 10 instead of the now deprecated Live SDK.
So far I've got:
var provider = await WebAuthenticationCoreManager.FindAccountProviderAsync("https://login.microsoft.com", "consumers");
var request = new WebTokenRequest(provider, "service::wl.basic wl.emails::DELEGATION", "none");
var result = await WebAuthenticationCoreManager.RequestTokenAsync(request);
if (result.ResponseStatus == WebTokenRequestStatus.Success)
{
string token = result.ResponseData[0].Token;
//This calls my custom wrappers around the Live REST API v5 and runs successfully with this token
var acc = await LiveApi.GetLiveAccount(token);
var jtoken = new JObject
{
{"authenticationToken", token}
};
try
{
//Shouldn't this work? but raises a 401
await App.MobileService.LoginAsync(MobileServiceAuthenticationProvider.MicrosoftAccount, jtoken);
//Alternate method? Also raises a 401
//await App.MobileService.LoginWithMicrosoftAccountAsync(token);
}
}
As I mentioned in the comments, all I get are 401s.
As far as I can tell the application is configured correctly in Microsoft Account dev center:
I'm using the client ID and secret from the same app in the Azure portal.
JWT issuing is not restricted.
Redirect URL is of the format https://{appname}.azurewebsites.net/.auth/login/microsoftaccount/callback
Authentication works fine when I switch to use purely server-side authentication. i.e.
await App.MobileService.LoginAsync(MobileServiceAuthenticationProvider.MicrosoftAccount);
Any ideas? Am I missing something? Any help would be appreciated.
UPDATED:
The token I get back in the WebTokenRequestResult is 877 characters long and does not appear to be in the JWT format, with the dot (.) separators and I'm quite certain that this is the issue. The following error gets logged in service when the client calls the code above:
JWT validation failed: IDX10708: 'System.IdentityModel.Tokens.JwtSecurityTokenHandler' cannot read this string: 'EwCQAq1DBAAUGCCXc8wU/zFu9QnLdZXy+...Zz9TbuxCowNxsEPPOvXwE='.
Application: The string needs to be in compact JSON format, which is of the form: '<Base64UrlEncodedHeader>.<Base64UrlEndcodedPayload>.<OPTIONAL, Base64UrlEncodedSignature>'..
Application: 2015-12-07T17:47:09 PID[5740] Information Sending response: 401.71 Unauthorized
What format is the token currently in? Can it be transformed to a JWT?
Still no closer to a solution, so any help is appreciated.
Anyone feel free to correct me, but it looks like RequestTokenAsync gets you an access token which you can't use to login the backend. You need an authentication token for that, and as far as I can see RequestTokenAsync doesn't get you that.
There's some info here about the tokens.
If people end up here searching for a solution for App Service Mobile, the update to MobileService. Then there is now a solution
The code replicated here is:
async Task<string> GetDataAsync()
{
try
{
return await App.MobileService.InvokeApiAsync<string>("values");
}
catch (MobileServiceInvalidOperationException e)
{
if (e.Response.StatusCode != HttpStatusCode.Unauthorized)
{
throw;
}
}
// Calling /.auth/refresh will update the tokens in the token store
// and will also return a new mobile authentication token.
JObject refreshJson = (JObject)await App.MobileService.InvokeApiAsync(
"/.auth/refresh",
HttpMethod.Get,
null);
string newToken = refreshJson["authenticationToken"].Value<string>();
App.MobileService.CurrentUser.MobileServiceAuthenticationToken
= newToken;
return await App.MobileService.InvokeApiAsync<string>("values");
}
Hope it saves somebody time !

Authorizer.IsAuthorized is returning false after I have authenticated

I just started working with the Facebook SDK this weekend. I'm trying to integrate Facebook into a site. I followed the samples and documentation and got the Facebook Login button along with displaying "faces" fairly quickly (nice Toolkit).
<fb:login-button autologoutlink="true" perm="email" show-faces="true" width="200" max-rows="1"></fb:login-button>
I would like to check if the user has already been authenticated (via Facebook). This is no different than what I've seen in many of the samples. The issue, I'm having is that in my Page_Load, the authorizer.IsAuthorized is returning False even after I authenticate (my profile picture is displayed as well). Is there something that I'm missing?
protected void Page_Load(object sender, EventArgs e)
{
try
{
Facebook.FacebookApp app = new Facebook.FacebookApp();
Facebook.Web.Authorizer authorizer = new Facebook.Web.Authorizer(app);
if (authorizer.IsAuthorized())
{
}
else
{
}
}
catch (Exception ex)
{
}
}
I used 4.1.1 version of Facebook C# SDK until I've got the same problem.
Then I downloaded Version 4.2.1 and removed any Authorizer.Perms.
FacebookApp fbApp = new FacebookApp(access_token);
Authorizer fbAuth = new Authorizer(fbApp);
fbAuth.IsAuthorized();// returns true
Problem was solved.
The reason is than somehow UserId becomes 0 even you authorized application in Facebook.
They resolved it. Now when you use FacebookApp(access_token) constructor, it gets UserId from
access_token.

Categories