C# - Xamarin.iOs - Add a nonce to xamarin.auth request - c#

I don't find a way to add a nonce to my Xamarin.Auth request to connect to my okta login. I'm kind of new to xamarin and nugets package and i don't know how to modify the implementation of OAuth2Authenticator of the version 1.3.0.
I'm trying to use request parameter as :
auth.RequestParameters.Add("nonce", Guid.NewGuid().ToString("N"));
but i keep running in the nonce invalid error from okta.
If any of you has an idea how to fix it.
Here comes the full request:
//start login flow seting our openId flow
public void StartFlow(string responseType, string scope)
{
//webViewLogin.Hidden = false;
var auth = new OAuth2Authenticator(
clientId: OAuthClientId,
scope: scope,
authorizeUrl: new Uri(oktaTenantUrl),
redirectUrl: new Uri(OAuthRedirectUrl)
);
auth.RequestParameters.Add("nonce", Guid.NewGuid().ToString("N"));
auth.Completed += (sender, eventArgs) =>
{
DismissViewController(true, null);
if (eventArgs.IsAuthenticated)
{
// Use eventArgs.Account to do wonderful things
}
};
PresentViewController(auth.GetUI(), true, null);
}

There is a simpler way to achieve this. It seems this is a bug/not well documented process. This issue explains what to do:
You could probably override OnCreatingInitialUrl:
https://github.com/xamarin/Xamarin.Auth/blob/ad7f6d6453506ced0ab3af499a7492f13973e3a3/source/Xamarin.Auth.LinkSource/OAuth2Authenticator.cs#L322
The RequestParameters property doesn't look to be used from what I can
see, or at least not in that version.
So the solution would be to make another class that inherits OAuth2Authenticator and override the OnCreatingInitialUrl:
using System;
using System.Collections.Generic;
using Xamarin.Auth;
namespace Alerte.Health.App.Droid.Classes
{
public class MyOAuth2Authenticator : OAuth2Authenticator
{
public MyOAuth2Authenticator(string clientId, string scope, Uri authorizeUrl, Uri redirectUrl, GetUsernameAsyncFunc getUsernameAsync = null, bool isUsingNativeUI = false) : base(clientId, scope, authorizeUrl, redirectUrl, getUsernameAsync, isUsingNativeUI)
{
}
public MyOAuth2Authenticator(string clientId, string clientSecret, string scope, Uri authorizeUrl, Uri redirectUrl, Uri accessTokenUrl, GetUsernameAsyncFunc getUsernameAsync = null, bool isUsingNativeUI = false) : base(clientId, clientSecret, scope, authorizeUrl, redirectUrl, accessTokenUrl, getUsernameAsync, isUsingNativeUI)
{
}
protected override void OnCreatingInitialUrl(IDictionary<string, string> query)
{
query.Add("nonce",Guid.NewGuid().ToString("N"));
}
}
}
Then use this instead of OAuth2Authenticator, and proceed with the normal process:
var auth = new MyOAuth2Authenticator(
clientId,
scope,
new Uri(authorizeUrl),
new Uri(redirectUrl));
etc etc.

Finaly got working by modifying the OAuth2Authenticator, adding a nonce to the first request.
Here is the fix:
public override Task<Uri> GetInitialUrlAsync()
{
var url = new Uri(string.Format(
"{0}?client_id={1}&redirect_uri={2}&response_type={3}&scope={4}&state={5}&nonce={6}",
authorizeUrl.AbsoluteUri,
Uri.EscapeDataString(clientId),
Uri.EscapeDataString(this.redirectUrl.AbsoluteUri),
IsImplicit ? "token" : "code",
Uri.EscapeDataString(scope),
Uri.EscapeDataString(requestState),
Uri.EscapeDataString(nonce)));
var tcs = new TaskCompletionSource<Uri>();
tcs.SetResult(url);
return tcs.Task;
}
with
this.nonce = Guid.NewGuid().ToString("N");

The problem is that w/o inheriting Authenticator it is impossible to intercept calls in ctor and 1st HTTP request happens already in ctor.
When the code gets to:
auth.RequestParameters.Add("nonce", Guid.NewGuid().ToString("N"));
Parameters are already created and HTTP request already fired.
Both answers above are fine for workarounds, but inheriting is needed.
It is planned to add another Dictionary to ctor for custom parameters, so they could be prepared on time.

Related

Custom HttpClientHandler using FlurlClient doesn't use ClientCertificate

I need to add a client certificate to my web requests and tried to achieve it in this way:
Stackoverflow
At the end of this answer the "FlurlClient way" is presented. Using and configuring a FlurlClient instead of a global FlurlHttp configuration. I've tried this, but it didn't work.
I've created a new .NET Core Console application to show you the problem:
static void Main(string[] args)
{
/****** NOT WORKING *******/
try
{
IFlurlClient fc1 = new FlurlClient(url)
.ConfigureClient(c => c.HttpClientFactory = new X509HttpFactory(GetCert()));
fc1.WithHeader("User-Agent", userAgent)
.WithHeader("Accept-Language", locale);
dynamic ret1 = fc1.Url.AppendPathSegments(pathSegments).GetJsonAsync()
.GetAwaiter().GetResult();
}
catch
{
// --> Exception: 403 FORBIDDEN
}
/****** NOT WORKING *******/
try
{
IFlurlClient fc2 = new FlurlClient(url);
fc2.Settings.HttpClientFactory = new X509HttpFactory(GetCert());
fc2.WithHeader("User-Agent", userAgent)
.WithHeader("Accept-Language", locale);
dynamic ret2 = fc2.Url.AppendPathSegments(pathSegments).GetJsonAsync()
.GetAwaiter().GetResult();
}
catch
{
// --> Exception: 403 FORBIDDEN
}
/****** WORKING *******/
FlurlHttp.Configure(c =>
{
c.HttpClientFactory = new X509HttpFactory(GetCert());
});
dynamic ret = url.AppendPathSegments(pathSegments).GetJsonAsync()
.GetAwaiter().GetResult();
// --> OK
}
The X509HttpFactory is copied from the linked StackOverflow answer (but using HttpClientHandler instead of WebRequestHandler):
public class X509HttpFactory : DefaultHttpClientFactory
{
private readonly X509Certificate2 _cert;
public X509HttpFactory(X509Certificate2 cert)
{
_cert = cert;
}
public override HttpMessageHandler CreateMessageHandler()
{
var handler = new HttpClientHandler();
handler.ClientCertificates.Add(_cert);
return handler;
}
}
So using the global FlurlHttp configuration is working and configuring the FlurlClient is not working. Why?
This all comes down to the order you're calling things:
fc.Url returns a Url object, which is little more than a string-building thing. It doesn't hold a reference back to the FlurlClient. (This allows Flurl to exist as a URL building library independent of Flurl.Http.)
Url.AppendPathSegments returns "this" Url.
Url.GetJsonAsync is an extension method that first creates a FlurlClient, then uses it with the current Url to make the HTTP call.
So as you can see, you've lost your reference to fc in step 1 of that flow. 2 possible solutions:
1. Build the URL first, then fluently add in the HTTP bits:
url
.AppendPathSegments(...)
.ConfigureClient(...)
.WithHeaders(...)
.GetJsonAsync();
2. OR, if you want to reuse the FlurlClient, "attach" it to the URL using WithClient:
var fc = new FlurlClient()
.ConfigureClient(...)
.WithHeaders(...);
url
.AppendPathSegments(...)
.WithClient(fc)
.GetJsonAsync();

Using a DelegatingHandler in HttpClient class from windows forms - Inner handler has not been set

First off, the error message I am receiving is as follows: The inner handler has not been set
I'm writing a custom message handler to handle authentication cookie timeouts to my API. For example, if my code base makes a call to the API and in turn receives a 401 then it should retry the login url to get an updated cookie. I planned to accomplish this as follows:
public class CkApiMessageHandler : DelegatingHandler
{
private readonly string _email;
private readonly string _password;
private const string _loginPath = "Account/Login";
public CkApiMessageHandler(string email, string password)
{
_email = email;
_password = password;
//InnerHandler = new HttpControllerDispatcher(null);
}
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = await base.SendAsync(request, cancellationToken);
if(response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
Logging.Logger.LogInformation("Authentication cookie timed out");
var baseAddress = request.RequestUri.Host;
var loginPath = baseAddress + _loginPath;
var originalRequest = request;
var loginRequest = new HttpRequestMessage(new HttpMethod("POST"), loginPath);
var dict = new Dictionary<string, string>();
dict.Add("Email", _email);
dict.Add("Password", _password);
var form = new FormUrlEncodedContent(dict);
loginRequest.Content = form;
loginRequest.Headers.Clear();
foreach(var header in originalRequest.Headers)
{
loginRequest.Headers.Add(header.Key, header.Value);
}
var loginResponse = await base.SendAsync(loginRequest, cancellationToken);
if (loginResponse.IsSuccessStatusCode)
{
Logging.Logger.LogInformation("Successfully logged back in");
return await base.SendAsync(originalRequest, cancellationToken);
}
else
{
Logging.Logger.LogException(new Exception("Failed to log back in"), "Could not log back in");
}
}
return response;
}
}
I am converting an old service that used to access the database directly, to a service that accesses a web api and I'm trying to do it without having to change any of the consumers of this class.. Hence the weird handler. Here is an example method from the service class.
public void UpdateClientInstallDatabaseAndServiceData(string dbAcronym, string clientVersion, string clientEnginePort, Guid installationId, string sqlserver)
{
var dict = new Dictionary<string, string>();
dict.Add("dbAcronym", dbAcronym);
dict.Add("clientVersion", clientVersion);
dict.Add("clientEnginePort", clientEnginePort);
dict.Add("desktopClientId", installationId.ToString());
dict.Add("sqlServerIp", sqlserver);
var form = new FormUrlEncodedContent(dict);
_client.PostAsync(_apiPath + "/UpdateClientInstallDatabaseAndServiceData", form);
}
So if the above code fails with a 401, the service will auto log back in and retry the original code without the consumer of the service having to check requests and relog back in. The consumer should not know that it is dealing with a web api.
My problem is that my message handler is expecting an InnerHandler to be set which requires an instance of HttpConfiguration class. When I take a look at the specs here, it appears to be some type of class used to setup a web api service. This is fine, except that this code is not being executed in an api.. It is being executed on a windows forms app. The delegating handler is being used within an HttpClient class like so...
_client = new HttpClient(new CKEnterprise.Common.CkApiMessageHandler(email, password));
So my question is : How can I get this DelegatingHandler to do it's job outside of the context of a web api project?
Making an update. Looks like I may be able to just use HttpClientHandler class
https://blogs.msdn.microsoft.com/henrikn/2012/08/07/httpclient-httpclienthandler-and-webrequesthandler-explained/
I should have realized this sooner but it makes sense to perhaps set the inner handler to the default handler that HttpClient uses. So inside your child class of DelegatingHandler you should set your inner handler to the default handler used by HttpClient like so:
public CkApiMessageHandler(string email, string password, Guid moduleId)
{
_email = email;
_password = password;
_moduleId = moduleId;
InnerHandler = new HttpClientHandler();
}
Adrian answer is correct. You have to set an inner handler for your custom handler, but there is a better way to set the inner handler.
public MyDelegatingHandler( ) : base( new HttpClientHandler( ) )
Or you can use the HttpClientFactory
_client = HttpClientFactory.Create(new CKEnterprise.Common.CkApiMessageHandler(email, password))
https://learn.microsoft.com/en-us/aspnet/web-api/overview/advanced/httpclient-message-handlers#adding-message-handlers-to-the-client-pipeline

ClaimsPrincipal.Current.Identity.Name Empty when authenticated from client, fine in browser

I have the following Azure Function,
#r "Newtonsoft.Json"
using Newtonsoft.Json.Linq;
using System.Net;
using System.Security.Claims;
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
try
{
JObject pJOtClaims = new JObject();
foreach(Claim curClaim in ClaimsPrincipal.Current.Identities.First().Claims)
{
pJOtClaims.Add(curClaim.Type, new JValue(curClaim.Value));
}
return(req.CreateResponse(HttpStatusCode.OK, $"{pJOtClaims.ToString(Newtonsoft.Json.Formatting.None)}"));
}
catch(Exception ex)
{
return(req.CreateResponse(HttpStatusCode.OK, $"{ex.Message}"));
}
}
I have configured only Facebook authentication for this Function App. This function works for both in-browser and client authentication. When I invoke this method in browser I get a whole bunch of claims, including my registered Facebook email address. When I invoke this from client authentication, I get the following claims,
{
"stable_sid":"...",
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier":"...",
"http://schemas.microsoft.com/identity/claims/identityprovider":"...",
"ver":"...",
"iss":"...",
"aud":"...",
"exp":"...",
"nbf":"..."
}
Unfortunately none of these include my Facebook email address which I need. I have enabled the "email" scope for the Facebook authentication configuration. Any ideas how to get this?
Nick.
Okay so I haven't found the exact solution I wanted, but this should get me by. Technically I only need the email address during registration, after that I can just use the stable_sid as is part of the identity I do get. So What I have done is to pass on the x-zumo-auth header to the ".auth/me" method, get the property I need. I'm using this method
public static async Task<String> GetAuthProviderParam(String iAuthMeURL,
String iXZumoAUth,
String iParamKey)
{
using (HttpClient pHCtClient = new HttpClient())
{
pHCtClient.DefaultRequestHeaders.Add("x-zumo-auth", iXZumoAUth);
String pStrResponse = await pHCtClient.GetStringAsync(iAuthMeURL);
JObject pJOtResponse = JObject.Parse(pStrResponse.Trim(new Char[] { '[', ']' }));
if(pJOtResponse[iParamKey] != null)
{
return (pJOtResponse[iParamKey].Value<String>());
}
else
{
throw new KeyNotFoundException(String.Format("A parameter with the key '{0}' was not found.", iParamKey));
}
}
}
This can be called in the function like so,
if(req.Headers.Contains("x-zumo-auth"))
{
String pStrXZumoAuth = req.Headers.GetValues("x-zumo-auth").First();
String pStrParam = await FunctionsHelpers.GetAuthProviderParam("https://appname.azurewebsites.net/.auth/me",
pStrXZumoAuth,
"user_id");
//pStrParam = user_id
}

why HttpClient.GetAsync causes opening link in browser?

Assume we have an application that wants access popular Russian social network VK and written on C# with WinForms GUI. VK uses OAuth2-similiar approach, so we need to open web browser with vk oauth authorization url. Then we subscribe to webBrowser's OnNavigated event and waiting until url will not be equal some pre-defined url with access token in query string.
From now on we can call vk methods using received access token, but some strange things take place here: when i try to invoke some vk methods with HttpClient.GetAsync(methodUri), everything goes according to plan, except to opening the link from the authorization web browser in the system web browser.
vk's client authorization Url looks like https://oauth.vk.com/authorize?client_id={clientId}&scope={scope}&redirect_uri=https://oauth.vk.com/blank.html&display={displayType}&response_type=token, Url with received accessToken looks like https://oauth.vk.com/blank.html#access_token={accessToken}&expires_in={expiresIn}&user_id={userId}, note the number sign instead on question mark.
code in main form:
var authenticationForm = new AuthenticationForm();
authenticationForm.Show();
_authenticatedUser = await application.ClientAuthenticator.Authenticate(authenticationForm.GetToken);
authenticationForm.Close();
var httpClient = new HttpClient();
var request = "https://api.vk.com/method/users.get.xml?user_ids=1&fields=online";
var response = await httpClient.GetAsync(request);
authenticationForm class code:
public partial class AuthenticationForm : Form
{
private readonly TaskCompletionSource<VkAccessToken> _tokenCompletitionSource = new TaskCompletionSource<VkAccessToken>();
private Uri _redirectUri;
public AuthenticationForm()
{
InitializeComponent();
}
public async Task<IVkAccessToken> GetToken(Uri authUri, Uri redirectUri)
{
authenticationBrowser.Navigate(authUri);
_redirectUri = redirectUri;
var token = await _tokenCompletitionSource.Task;
return token;
}
private async void authenticationBrowser_Navigated(object sender, WebBrowserNavigatedEventArgs e)
{
if (!(_redirectUri.IsBaseOf(e.Url) && _redirectUri.AbsolutePath.Equals(e.Url.AbsolutePath))) return;
//working with e.Url to achieve token, userId and expiresIn, creating token variable based on them
_tokenCompletitionSource.SetResult(token);
}
}
ClientAuthenticator.Authenticate code:
public async Task<IVkAuthenticatedUser> Authenticate(Func<Uri, Uri, Task<IVkAuthenticatedUser>> aunthenticationResultGetter)
{
var authorizationUri =
new Uri("https://oauth.vk.com/authorize?client_id={clientId}&scope={scope}&redirect_uri=https://oauth.vk.com/blank.html&display=page&response_type=token");
var token = await aunthenticationResultGetter(authorizationUri, _application.Settings.RedirectUri);
//...
return newUserBasedOnToken;
}
after stepping out(using debugger) var response = await httpClient.GetAsync(request); line from main form, my system browser opens link like https://oauth.vk.com/blank.html#access_token={accessToken}&expires_in={expiresIn}&user_id={userId} - #access_token={accessToken}&expires_in={expiresIn}&user_id={userId} with recent accessToken, expiresIn and userId values. Yes, with ... - #access_token=.... in url.
I have no idea why this might happen, but I am concerned that the number sign.
important addition: it only happens if the Web browser does not have information about a session or it is expired, that is, I have to enter username and password to vk's login form. if cookies contain the necessary information and it automatically redirect to the page containing token in it's url (with # sign again), everything works as expected

Facebook web application extended permissions second step dont show

Update2
This post is getting old but still relevant.. Below is whe way I solved it. I marked the other guys answer because I think it answers the question better. I'm calling a similar method(I'am about to refactor:)) in accountcontroller. The string should be a list... I think you get it.
/// <summary>
/// Use this method when an action fails due to lack of priviligies. It will redirect user to facebook with provided permission request.
/// Refactor to handle list of request.
/// </summary>
/// <param name="permission"></param>
private static void AddAdditionalPermissions(string permission)
{
System.Diagnostics.Trace.TraceInformation(permission + " not authorized for user.");
string facebook_urlAuthorize_base = "https://graph.facebook.com/oauth/authorize";
string scope = permission; //see: https://developers.facebook.com/docs/authentication/permissions/ for extended permissions
string urlAuthorize = facebook_urlAuthorize_base;
urlAuthorize += "?client_id=" + AppId;
urlAuthorize += "&redirect_uri=" + "https://fbd.anteckna.nu/";
urlAuthorize += "&scope=" + scope;
//redirect the users browser to Facebook to ask the user to authorize our Facebook application
HttpContext.Current.Response.Redirect(urlAuthorize, true); //this cannot be done using WebRequest since facebook may need to show dialogs in the users browser
}
Then every method making a call to facebook like /me/home with facebok C# SDK catches FacebookOAuthException and redirects to the folling method. This is how we apply the best practise of not asking permissions from users up front but when needed. This method should have aredirect url that matches as well but we've just get going :)
Hope it helps!
/// <summary>
/// Check for what permissions to request or different ways to handle FacebookOAuthExceptions.
/// </summary>
/// <param name="foae">The exception object</param>
public static void HandleAuthorizationsExceptions(FacebookOAuthException foae)
{
if (foae.Message.Contains("publish_permissions"))
{
AddAdditionalPermissions("publish_permissions");
}
else if (foae.Message.Contains("read_stream"))
{
AddAdditionalPermissions("read_stream");
}
else
{
System.Diagnostics.Trace.TraceError("Unhandled error at:" + foae.StackTrace);
}
}
Update: This behaviour is caused by .Net oauth implementation which has the scope hard coded in a sealed class. Added figure 4 to show the request parameter where the lack of additional scopes besides "email"(which is sent with all requests by .net oauth provider). Adding ",publish_stream" to the query string gives me the wanted behaviour. Anyone knows how to achieve this?
Please do not submit answers or comments about facebook best practices or alternative solutions. I have an alternative solution but would like this to work with default registerfacebookclient parameters. I have updated the application to oly use publish_stream according to the two answers specifying on what permissions I'm asking for.
figure 4
Original question:
I'm setting up an application (C#.Net4.5 MVC4, razor views) which need pretty much all available user permissions from facebook.
You can see code examples below how I have set it all up.
The problem is that when clicking "okay" in figure 1, Facebook sends me back to my application. As I understand there should be an additional screen(figure2) asking for the "heavier" permissions. As of now I only get the permissions stated in figure one. That part works...
Figure 1
Figure 2
So, using basic
AuthConfig.cs
var facebooksocialData = new Dictionary<string, object>();
facebooksocialData.Add("scope", "email,publish_stream,read_stream,publish_actions,manage_pages,create_event,offline_access");
OAuthWebSecurity.RegisterFacebookClient(
appId: "165359673639901",
appSecret: "15091cb2094a1996ae6c7b324f0300e6",
displayName: "Facebook",
extraData: facebooksocialData);
This is how I handle the response but here facebook has not prompted the user for the extended permissions but only for email,
AccountController.cs
//
// GET: /Account/ExternalLoginCallback
[AllowAnonymous]
public ActionResult ExternalLoginCallback(string returnUrl)
{
AuthenticationResult result = OAuthWebSecurity.VerifyAuthentication(Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl }));
if (!result.IsSuccessful)
{
return RedirectToAction("ExternalLoginFailure");
}
// Save the accesstoken into session
Session["accesstoken"] = result.ExtraData["accesstoken"];
Session["id"] = result.ExtraData["id"];
if (OAuthWebSecurity.Login(result.Provider, result.ProviderUserId, createPersistentCookie: false))
{
return RedirectToLocal(returnUrl);
}
if (User.Identity.IsAuthenticated)
{
// If the current user is logged in add the new account
OAuthWebSecurity.CreateOrUpdateAccount(result.Provider, result.ProviderUserId, User.Identity.Name);
return RedirectToLocal(returnUrl);
}
else
{
// User is new, ask for their desired membership name
string loginData = OAuthWebSecurity.SerializeProviderUserId(result.Provider, result.ProviderUserId);
ViewBag.ProviderDisplayName = OAuthWebSecurity.GetOAuthClientData(result.Provider).DisplayName;
ViewBag.ReturnUrl = returnUrl;
return View("ExternalLoginConfirmation", new RegisterExternalLoginModel { UserName = result.UserName, ExternalLoginData = loginData });
}
}
The closest to an answer I could find was a wp plugin which had the same issue. Their problem was solved by setting domain to localhost. This is how my application is set up.
I got the same problem. As you did, I configured the RegisterFacebookClient with dictionary to define my app's scope, and unfortunately the request didn't include the scope as I configured. So I found that. It seems that would work, but it wasn't enough. So I found this.
So here is what solve my problems:
First of all I added this new client to my code:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using DotNetOpenAuth.AspNet;
using Newtonsoft.Json;
namespace MyApp.UI.Infrastructure
{
public class FacebookScopedClient : IAuthenticationClient
{
private string appId;
private string appSecret;
private string scope;
private const string baseUrl = "https://www.facebook.com/dialog/oauth?client_id=";
public const string graphApiToken = "https://graph.facebook.com/oauth/access_token?";
public const string graphApiMe = "https://graph.facebook.com/me?";
private static string GetHTML(string URL)
{
string connectionString = URL;
try
{
System.Net.HttpWebRequest myRequest = (HttpWebRequest)WebRequest.Create(connectionString);
myRequest.Credentials = CredentialCache.DefaultCredentials;
//// Get the response
WebResponse webResponse = myRequest.GetResponse();
Stream respStream = webResponse.GetResponseStream();
////
StreamReader ioStream = new StreamReader(respStream);
string pageContent = ioStream.ReadToEnd();
//// Close streams
ioStream.Close();
respStream.Close();
return pageContent;
}
catch (Exception)
{
}
return null;
}
private IDictionary<string, string> GetUserData(string accessCode, string redirectURI)
{
string token = GetHTML(graphApiToken + "client_id=" + appId + "&redirect_uri=" + HttpUtility.UrlEncode(redirectURI) + "&client_secret=" + appSecret + "&code=" + accessCode);
if (token == null || token == "")
{
return null;
}
string access_token = token.Substring(token.IndexOf("access_token="), token.IndexOf("&"));
string data = GetHTML(graphApiMe + "fields=id,name,email,username,gender,link&" + access_token);
// this dictionary must contains
Dictionary<string, string> userData = JsonConvert.DeserializeObject<Dictionary<string, string>>(data);
return userData;
}
public FacebookScopedClient(string appId, string appSecret, string scope)
{
this.appId = appId;
this.appSecret = appSecret;
this.scope = scope;
}
public string ProviderName
{
get { return "facebook"; }
}
public void RequestAuthentication(System.Web.HttpContextBase context, Uri returnUrl)
{
string url = baseUrl + appId + "&redirect_uri=" + HttpUtility.UrlEncode(returnUrl.ToString()) + "&scope=" + scope;
context.Response.Redirect(url);
}
public AuthenticationResult VerifyAuthentication(System.Web.HttpContextBase context)
{
string code = context.Request.QueryString["code"];
string rawUrl = context.Request.Url.OriginalString;
//From this we need to remove code portion
rawUrl = Regex.Replace(rawUrl, "&code=[^&]*", "");
IDictionary<string, string> userData = GetUserData(code, rawUrl);
if (userData == null)
return new AuthenticationResult(false, ProviderName, null, null, null);
string id = userData["id"];
string username = userData["username"];
userData.Remove("id");
userData.Remove("username");
AuthenticationResult result = new AuthenticationResult(true, ProviderName, id, username, userData);
return result;
}
}
}
I put it on a folder "infrastructure" in my asp.net solution with oder stuff, next I change my old configuration, in order to use the new facebook client, as follows:
Old code:
OAuthWebSecurity.RegisterFacebookClient(
appId: "<app-id>",
appSecret: "<app-secret>",
displayName: "Facebook",
extraData: facebookExtraData);
New Code:
OAuthWebSecurity.RegisterClient(
new FacebookScopedClient(
"<app-id>",
"<app-secret>",
"scope"),
"Facebook",
null);
That's it. It may help you, as helped me.
Is your app registered for these scopes? I'm familiar with Google OAuth, they have a separate scope that maps to one permission. Your app should be registered for the scopes, in order to get the 2nd window. Else, you'll have access only to the public info that your 1st popup asks for..
First of all, offline_access does not exist any more, so it from the permissions you are asking for.
"[app] which need pretty much all available user permissions from facebook"
Facebook actively discourages asking for heaps of permissions straight from the beginning "just in case" because they might be needed later. One should only ask for an extended permission when it is actually needed for an action the user just triggered for the first time.
Also, you are supposed to ask for "read" and "write" permissions separately.
I don't know if these aspects are actually triggering your error - but I know that Facebook has already been sending out developer alerts for the read/write thing; although an FB employee confirmed that those alerts can be ignored for now, they might start enforcing this at some point in the future.

Categories