I am getting the following error:
(OAuthException) client_secret should not be passed to
/oauth/access_token/
While I am calling the Facebook API to get me a "User Access Token" for further inquiries. My code is very simple:
string appId = "99999999"; // Long number - Given by FB in my application page
string appSecret = "98907kjlhkh908098"; // // Long string- Given by FB in my application page
string code = "089789uokjj"; // Access Code in response to my first FB call - It is in query string of response
var fb = new FacebookClient ( appId , appSecret );
var parameters = new Dictionary<string , object>
{
{ "client_id", appId },
{"redirect_uri" , "http://localhost:49773/Sample/Default.aspx"},
{ "client_secret", appSecret },
{ "code", code }
};
fb.Post( "https://graph.facebook.com/oauth/access_token/" , parameters );
How do I pass Client_Secret? Without that I cannot proceed and with that again I am getting exception!
Little tutorial for ASP.NET for server-side flow (I am using ver. 6 of FB C# SDK):
1) create login button and bind onclick event on login page:
var loginWindowPopup = null;
var loginWindowTimer = null;
$(function ()
{
$('#login_with_facebook').click(function ()
{
var popupWidth = 640;
var popupHeight = 337;
var xPosition = ($(window).width() - popupWidth) / 2;
var yPosition = ($(window).height() - popupHeight) / 2;
loginWindowPopup = window.open('/AuthSocialUser.aspx?facebookAuth=true',
'FacebookLoginWindow',
'location=1,scrollbars=1,menubar=0,status=0,toolbar=0' +
',width=' + popupWidth +
',height=' + popupHeight +
',left=' + xPosition +
',top=' + yPosition);
if (loginWindowTimer == null)
{
loginWindowTimer = setInterval(CheckLogonWindowClose, 1000);
}
}
);
function CheckLogonWindowClose()
{
if (loginWindowPopup.closed)
{
clearInterval(loginWindowTimer);
location.reload();
}
};
2) in AuthSocialUser.aspx popup window:
if (this.Request.QueryString["facebookAuth"] == "true")
{
var parameters = new Dictionary<string,object>();
parameters["client_id"] = "...";
// parameters["scope"] = "email";
string state = Guid.NewGuid().ToString();
parameters["state"] = state;
this.Session.Add("state", state); //CSRF protection
parameters["redirect_uri"] =
this.Request.Url.AbsoluteUri.Replace("facebookAuth=true", "facebookAuth=false");
parameters["response_type"] = "code"; // code can be exchanged for an access token
parameters["display"] = "popup";
this.Response.Redirect(new FacebookClient().GetLoginUrl(parameters).AbsoluteUri);
}
else
{
string code = this.Request.QueryString["code"];
string state = this.Request.QueryString["state"];
string currentState = (this.Session["state"] != null ?
this.Session["state"].ToString() : null);
if (string.IsNullOrWhiteSpace(code) == true)
{
// set info in session: app not authorized & inject close window JS script
return;
}
if (string.IsNullOrWhiteSpace(state) == true ||
string.IsNullOrWhiteSpace(currentState) == true)
{
// session state expired & inject close window JS script
return;
}
if (state != currentState)
{
throw new ArgumentException("State does not match (CSRF?)");
}
//// get access token
var fb = new FacebookClient();
Dictionary<string, object> parameters = new Dictionary<string, object>();
parameters.Add("client_id", "...");
parameters.Add("redirect_uri", "https://127.0.0.1
/AuthSocialUser.aspx?facebookAuth=false");
parameters.Add("client_secret", "...");
parameters.Add("code", code);
result = fb.Get("/oauth/access_token", parameters);
string accessToken = result["access_token"];
// use token in next requests, insert status to session state
// & inject close window JS script - simple: window.close();
}
Related
I am currently working on an dotnet maui app and I need to integrate Sign in With Apple. But when I click the sign in button, It shows "invalid_request invalid web redirect url"
Tried solutions
I tried the solutions available here, but it is not working.
Other than that I have also read the documentation, also got help from tutorials such as this, this and this
Code
Initializing request:
//Initiating apple sign in request
WebAuthenticatorResult result = null;
if (scheme.Equals(Constants.apple, StringComparison.Ordinal)
&& DeviceInfo.Platform == DevicePlatform.iOS
&& DeviceInfo.Version.Major >= 13)
{
// Make sure to enable Apple Sign In in both the
// entitlements and the provisioning profile.
var options = new AppleSignInAuthenticator.Options
{
IncludeEmailScope = true,
IncludeFullNameScope = true,
};
result = await AppleSignInAuthenticator.AuthenticateAsync(options);
}
else
{
var authUrl = new Uri(Constants.authenticationUrl + scheme);
var callbackUrl = new Uri(Constants.callbackUrl);
result = await WebAuthenticator.AuthenticateAsync(authUrl, callbackUrl);
}
AuthToken = string.Empty;
// Get Name and Email from callback url
//if (result.Properties.TryGetValue("name", out var name) && !string.IsNullOrEmpty(name))
// AuthToken += $"Name: {name}{Environment.NewLine}";
//if (result.Properties.TryGetValue("email", out var email) && !string.IsNullOrEmpty(email))
// AuthToken += $"Email: {email}{Environment.NewLine}";
AuthToken += result?.AccessToken ?? result?.IdToken;
AuthCredential credential = null;
Handling results:
// WebAuthenticator Endpoint - use for social login e.g. Google, Facebook, Apple etc.
const string callbackScheme = "socialloginauthenticator";
[HttpGet("{scheme}")]
public async Task Get([FromRoute] string scheme)
{
var auth = await Request.HttpContext.AuthenticateAsync(scheme);
if (!auth.Succeeded
|| auth?.Principal == null
|| !auth.Principal.Identities.Any(id => id.IsAuthenticated)
|| string.IsNullOrEmpty(auth.Properties.GetTokenValue("access_token")))
{
// Not authenticated, challenge
await Request.HttpContext.ChallengeAsync(scheme);
}
else
{
var claims = auth.Principal.Identities.FirstOrDefault()?.Claims;
var email = string.Empty;
email = claims?.FirstOrDefault(c => c.Type == System.Security.Claims.ClaimTypes.Email)?.Value;
// Get parameters to send back to the callback
var qs = new Dictionary<string, string>
{
{ "access_token", auth.Properties.GetTokenValue("access_token") },
{ "refresh_token", auth.Properties.GetTokenValue("refresh_token") ?? string.Empty },
{ "expires_in", (auth.Properties.ExpiresUtc?.ToUnixTimeSeconds() ?? -1).ToString() },
{ "email", email }
};
// Build the result url
var url = callbackScheme + "://#" + string.Join(
"&",
qs.Where(kvp => !string.IsNullOrEmpty(kvp.Value) && kvp.Value != "-1")
.Select(kvp => $"{WebUtility.UrlEncode(kvp.Key)}={WebUtility.UrlEncode(kvp.Value)}"));
// Redirect to final url
Request.HttpContext.Response.Redirect(url);
}
}
I have resolved the issue. The issue was with redirect uri in apple service I made.
The required uri was of format "www.example.com/signin-apple" while I was following "www.example.com/path/to/endpoints"
Trying to make use of the AndroidPublisherService from Play Developer API Client.
I can list active tracks and the releases in those tracks, but when I try to upload a new build there seems to be no way of attaching the authentication already made previously to read data.
I've authenticated using var googleCredentials = GoogleCredential.FromStream(keyDataStream) .CreateWithUser(serviceUsername); where serviceUsername is the email for my service account.
private static void Execute(string packageName, string aabfile, string credfile, string serviceUsername)
{
var credentialsFilename = credfile;
if (string.IsNullOrWhiteSpace(credentialsFilename))
{
// Check env. var
credentialsFilename =
Environment.GetEnvironmentVariable("GOOGLE_APPLICATION_CREDENTIALS",
EnvironmentVariableTarget.Process);
}
Console.WriteLine($"Using credentials {credfile} with package {packageName} for aab file {aabfile}");
var keyDataStream = File.OpenRead(credentialsFilename);
var googleCredentials = GoogleCredential.FromStream(keyDataStream)
.CreateWithUser(serviceUsername);
var credentials = googleCredentials.UnderlyingCredential as ServiceAccountCredential;
var service = new AndroidPublisherService();
var edit = service.Edits.Insert(new AppEdit { ExpiryTimeSeconds = "3600" }, packageName);
edit.Credential = credentials;
var activeEditSession = edit.Execute();
Console.WriteLine($"Edits started with id {activeEditSession.Id}");
var tracksList = service.Edits.Tracks.List(packageName, activeEditSession.Id);
tracksList.Credential = credentials;
var tracksResponse = tracksList.Execute();
foreach (var track in tracksResponse.Tracks)
{
Console.WriteLine($"Track: {track.TrackValue}");
Console.WriteLine("Releases: ");
foreach (var rel in track.Releases)
Console.WriteLine($"{rel.Name} version: {rel.VersionCodes.FirstOrDefault()} - Status: {rel.Status}");
}
using var fileStream = File.OpenRead(aabfile);
var upload = service.Edits.Bundles.Upload(packageName, activeEditSession.Id, fileStream, "application/octet-stream");
var uploadProgress = upload.Upload();
if (uploadProgress == null || uploadProgress.Exception != null)
{
Console.WriteLine($"Failed to upload. Error: {uploadProgress?.Exception}");
return;
}
Console.WriteLine($"Upload {uploadProgress.Status}");
var tracksUpdate = service.Edits.Tracks.Update(new Track
{
Releases = new List<TrackRelease>(new[]
{
new TrackRelease
{
Name = "Roswell - Grenis Dev Test",
Status = "completed",
VersionCodes = new List<long?>(new[] {(long?) upload?.ResponseBody?.VersionCode})
}
})
}, packageName, activeEditSession.Id, "internal");
tracksUpdate.Credential = credentials;
var trackResult = tracksUpdate.Execute();
Console.WriteLine($"Track {trackResult?.TrackValue}");
var commitResult = service.Edits.Commit(packageName, activeEditSession.Id);
Console.WriteLine($"{commitResult.EditId} has been committed");
}
And as the code points out, all action objects such as tracksList.Credential = credentials; can be given the credentials generated from the service account.
BUT the actual upload action var upload = service.Edits.Bundles.Upload(packageName, activeEditSession.Id, fileStream, "application/octet-stream"); does not expose a .Credential object, and it always fails with:
The service androidpublisher has thrown an exception: Google.GoogleApiException: Google.Apis.Requests.RequestError
Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project. [401]
Errors [
Message[Login Required.] Location[Authorization - header] Reason[required] Domain[global]
]
at Google.Apis.Upload.ResumableUpload`1.InitiateSessionAsync(CancellationToken cancellationToken)
at Google.Apis.Upload.ResumableUpload.UploadAsync(CancellationToken cancellationToken)
So, how would I go about providing the actual Upload action with the given credentials here?
Managed to figure this out during the day, I was missing one call to CreateScoped() when creating the GoogleCredential object as well as a call to InitiateSession() on the upload object.
var googleCredentials = GoogleCredential.FromStream(keyDataStream)
.CreateWithUser(serviceUsername)
.CreateScoped(AndroidPublisherService.Scope.Androidpublisher);
Once that was done I could then get a valid oauth token by calling
var googleCredentials = GoogleCredential.FromStream(keyDataStream)
.CreateWithUser(serviceUsername)
.CreateScoped(AndroidPublisherService.Scope.Androidpublisher);
var credentials = googleCredentials.UnderlyingCredential as ServiceAccountCredential;
var oauthToken = credentials?.GetAccessTokenForRequestAsync(AndroidPublisherService.Scope.Androidpublisher).Result;
And I can now use that oauth token in the upload request:
upload.OauthToken = oauthToken;
_ = await upload.InitiateSessionAsync();
var uploadProgress = await upload.UploadAsync();
if (uploadProgress == null || uploadProgress.Exception != null)
{
Console.WriteLine($"Failed to upload. Error: {uploadProgress?.Exception}");
return;
}
The full code example for successfully uploading a new aab file to google play store internal test track thus looks something like this:
private async Task UploadGooglePlayRelease(string fileToUpload, string changeLogFile, string serviceUsername, string packageName)
{
var serviceAccountFile = ResolveServiceAccountCertificateInfoFile();
if (!serviceAccountFile.Exists)
throw new ApplicationException($"Failed to find the service account certificate file. {serviceAccountFile.FullName}");
var keyDataStream = File.OpenRead(serviceAccountFile.FullName);
var googleCredentials = GoogleCredential.FromStream(keyDataStream)
.CreateWithUser(serviceUsername)
.CreateScoped(AndroidPublisherService.Scope.Androidpublisher);
var credentials = googleCredentials.UnderlyingCredential as ServiceAccountCredential;
var oauthToken = credentials?.GetAccessTokenForRequestAsync(AndroidPublisherService.Scope.Androidpublisher).Result;
var service = new AndroidPublisherService();
var edit = service.Edits.Insert(new AppEdit { ExpiryTimeSeconds = "3600" }, packageName);
edit.Credential = credentials;
var activeEditSession = await edit.ExecuteAsync();
_logger.LogInformation($"Edits started with id {activeEditSession.Id}");
var tracksList = service.Edits.Tracks.List(packageName, activeEditSession.Id);
tracksList.Credential = credentials;
var tracksResponse = await tracksList.ExecuteAsync();
foreach (var track in tracksResponse.Tracks)
{
_logger.LogInformation($"Track: {track.TrackValue}");
_logger.LogInformation("Releases: ");
foreach (var rel in track.Releases)
_logger.LogInformation($"{rel.Name} version: {rel.VersionCodes.FirstOrDefault()} - Status: {rel.Status}");
}
var fileStream = File.OpenRead(fileToUpload);
var upload = service.Edits.Bundles.Upload(packageName, activeEditSession.Id, fileStream, "application/octet-stream");
upload.OauthToken = oauthToken;
_ = await upload.InitiateSessionAsync();
var uploadProgress = await upload.UploadAsync();
if (uploadProgress == null || uploadProgress.Exception != null)
{
Console.WriteLine($"Failed to upload. Error: {uploadProgress?.Exception}");
return;
}
_logger.LogInformation($"Upload {uploadProgress.Status}");
var releaseNotes = await File.ReadAllTextAsync(changeLogFile);
var tracksUpdate = service.Edits.Tracks.Update(new Track
{
Releases = new List<TrackRelease>(new[]
{
new TrackRelease
{
Name = $"{upload?.ResponseBody?.VersionCode}",
Status = "completed",
InAppUpdatePriority = 5,
CountryTargeting = new CountryTargeting { IncludeRestOfWorld = true },
ReleaseNotes = new List<LocalizedText>(new []{ new LocalizedText { Language = "en-US", Text = releaseNotes } }),
VersionCodes = new List<long?>(new[] {(long?) upload?.ResponseBody?.VersionCode})
}
})
}, packageName, activeEditSession.Id, "internal");
tracksUpdate.Credential = credentials;
var trackResult = await tracksUpdate.ExecuteAsync();
_logger.LogInformation($"Track {trackResult?.TrackValue}");
var commitResult = service.Edits.Commit(packageName, activeEditSession.Id);
commitResult.Credential = credentials;
await commitResult.ExecuteAsync();
_logger.LogInformation($"{commitResult.EditId} has been committed");
}
Am using DocuSign Api's for signing documents. Now I have created template in DocuSign and Uploaded the PDF's there.
Now when user click's on submit, we need to auto populate docusign pdf's and I don't have custom fields added at docusign and it should be dynamic. Below is the code which is not working.
public string SignDocument()
{
var accountId = Login();
var url = GetRecipientDocumentUrl(accountId);
return url;
}
private string Login()
{
string authHeader = "{\"Username\":\"" + Username + "\", \"Password\":\"" + Password + "\", \"IntegratorKey\":\"" + IntegratorKey + "\"}";
DocuSign.eSign.Client.Configuration.Default.AddDefaultHeader("X-DocuSign-Authentication", authHeader);
// we will retrieve this from the login() results
string accountId = null;
// the authentication api uses the apiClient (and X-DocuSign-Authentication header) that are set in Configuration object
var authApi = new AuthenticationApi();
LoginInformation loginInfo = authApi.Login();
// find the default account for this user
foreach (LoginAccount loginAcct in loginInfo.LoginAccounts)
{
if (loginAcct.IsDefault == "true")
{
accountId = loginAcct.AccountId;
break;
}
}
if (accountId == null)
{ // if no default found set to first account
accountId = loginInfo.LoginAccounts[0].AccountId;
}
return accountId;
}
private string GetRecipientDocumentUrl(string accountId)
{
//var envelope = BuildEnvelopeDefinition(documents);
var envelope = BuildEnvelopeDefinition();
// |EnvelopesApi| contains methods related to creating and sending Envelopes (aka signature requests)
var envelopesApi = new EnvelopesApi();
//TemplateCustomFields
var summary = envelopesApi.CreateEnvelope(accountId, envelope);
//===========================================================
// Step 3: Create Embedded Signing View (URL)
//===========================================================
var viewOptions = BuildRecipientViewRequest(envelope);
var recipientView = envelopesApi.CreateRecipientView(accountId, summary.EnvelopeId, viewOptions);
return recipientView.Url;
}
private EnvelopeDefinition BuildEnvelopeDefinition()
{
TemplateRole templateRole = new TemplateRole();
templateRole.Email = "kpothireddy#firstam.com";
templateRole.Name = "Sample";
templateRole.RoleName = "1";
templateRole.Tabs = new Tabs();
templateRole.Tabs.TextTabs = new List<Text>();
Text textTab = new Text();
textTab.TabLabel = "Approved by";
textTab.Value = "Kranthi";
//textTab.XPosition = "100";
//textTab.YPosition = "100";
templateRole.Tabs.TextTabs.Add(textTab);
templateRole.ClientUserId = Guid.NewGuid().ToString();
List<TemplateRole> rolesList = new List<TemplateRole>();
rolesList.Add(templateRole);
//rolesList.Add(templateRole1);
var envelope = new EnvelopeDefinition
{
TemplateRoles = rolesList,
//TemplateId = "3b07a774-5ec5-4bbd-928a-a4b0bace2fc5",
TemplateId = "44d25c06-4fc3-4cbe-a9d0-7e0e1e3013bc", //Prefill
Status = "sent"
};
//Envelope e = new Envelope();
return envelope;
}
private RecipientViewRequest BuildRecipientViewRequest(EnvelopeDefinition envelope)
{
RecipientViewRequest viewOptions = new RecipientViewRequest()
{
ReturnUrl = ReturnUrl,
ClientUserId = envelope.TemplateRoles.First().ClientUserId, // must match clientUserId set in step #2!
AuthenticationMethod = "email",
UserName = envelope.TemplateRoles.First().Name,
Email = envelope.TemplateRoles.First().Email
//UserName = envelope.Recipients.Signers.First().Name,
//Email = envelope.Recipients.Signers.First().Email
};
return viewOptions;
}
Could you please help me out.
I downloaded CEF (chromuim embedded framework) binary distributation that comes with (cefclient & cefsimple) c++ examples, And Realized that cefclient can change proxy settings on run-time.
And the key to do that is to Grab the RequestContext and call the function SetPreference.
on CefClient all works just nice.
but on CefSharp calling SetPreference always returns false, and also HasPreference returns false for the preference name "proxy".
thanks to amaitland the proper way to actively inforce changing the request-context prefrences, is to run the code on CEF UIThread as following:
Cef.UIThreadTaskFactory.StartNew(delegate {
var rc = this.browser.GetBrowser().GetHost().RequestContext;
var v = new Dictionary<string, object>();
v["mode"] = "fixed_servers";
v["server"] = "scheme://host:port";
string error;
bool success = rc.SetPreference("proxy", v, out error);
//success=true,error=""
});
if anyone need any other soulition i found this solution.
Cef.UIThreadTaskFactory.StartNew(delegate
{
string ip = "ip or adress";
string port = "port";
var rc = this.browser.GetBrowser().GetHost().RequestContext;
var dict = new Dictionary<string, object>();
dict.Add("mode", "fixed_servers");
dict.Add("server", "" + ip + ":" + port + "");
string error;
bool success = rc.SetPreference("proxy", dict, out error);
});
I downloaded CefSharp.WinForms 65.0.0 and made class, that can help to start working with proxy:
public class ChromeTest
{
public static ChromiumWebBrowser Create(WebProxy proxy = null, Action<ChromiumWebBrowser> onInited = null)
{
var result = default(ChromiumWebBrowser);
var settings = new CefSettings();
result = new ChromiumWebBrowser("about:blank");
if (proxy != null)
result.RequestHandler = new _requestHandler(proxy?.Credentials as NetworkCredential);
result.IsBrowserInitializedChanged += (s, e) =>
{
if (!e.IsBrowserInitialized)
return;
var br = (ChromiumWebBrowser)s;
if (proxy != null)
{
var v = new Dictionary<string, object>
{
["mode"] = "fixed_servers",
["server"] = $"{proxy.Address.Scheme}://{proxy.Address.Host}:{proxy.Address.Port}"
};
if (!br.GetBrowser().GetHost().RequestContext.SetPreference("proxy", v, out string error))
MessageBox.Show(error);
}
onInited?.Invoke(br);
};
return result;
}
private class _requestHandler : DefaultRequestHandler
{
private NetworkCredential _credential;
public _requestHandler(NetworkCredential credential = null) : base()
{
_credential = credential;
}
public override bool GetAuthCredentials(IWebBrowser browserControl, IBrowser browser, IFrame frame, bool isProxy, string host, int port, string realm, string scheme, IAuthCallback callback)
{
if (isProxy == true)
{
if (_credential == null)
throw new NullReferenceException("credential is null");
callback.Continue(_credential.UserName, _credential.Password);
return true;
}
return false;
}
}
}
Using:
var p = new WebProxy("Scheme://Host:Port", true, new[] { "" }, new NetworkCredential("login", "pass"));
var p1 = new WebProxy("Scheme://Host:Port", true, new[] { "" }, new NetworkCredential("login", "pass"));
var p2 = new WebProxy("Scheme://Host:Port", true, new[] { "" }, new NetworkCredential("login", "pass"));
wb1 = ChromeTest.Create(p1, b => b.Load("http://speed-tester.info/check_ip.php"));
groupBox1.Controls.Add(wb1);
wb1.Dock = DockStyle.Fill;
wb2 = ChromeTest.Create(p2, b => b.Load("http://speed-tester.info/check_ip.php"));
groupBox2.Controls.Add(wb2);
wb2.Dock = DockStyle.Fill;
wb3 = ChromeTest.Create(p, b => b.Load("http://speed-tester.info/check_ip.php"));
groupBox3.Controls.Add(wb3);
wb3.Dock = DockStyle.Fill;
If you want dynamic proxy resolver (proxy hanlder), which allow you to use different proxy for different host - you should:
1) Prepare javascript
var proxy1Str = "PROXY 1.2.3.4:5678";
var proxy2Str = "PROXY 2.3.4.5:6789";
var ProxyPacScript =
$"var proxy1 = \"{(proxy1Str.IsNullOrEmpty() ? "DIRECT" : proxy1Str)}\";" +
$"var proxy2 = \"{(proxy2Str.IsNullOrEmpty() ? "DIRECT" : proxy2Str)}\";" +
#"function FindProxyForURL(url, host) {
if (shExpMatch(host, ""*example.com"")) {
return proxy1;
}
return proxy2;
}";
var bytes = Encoding.UTF8.GetBytes(ProxyPacScript);
var base64 = Convert.ToBase64String(bytes);
2) Set it correctly
var v = new Dictionary<string, object>();
v["mode"] = "pac_script";
v["pac_url"] = "data:application/x-ns-proxy-autoconfig;base64," + base64;
3) Call SetPreference as in accepted answer https://stackoverflow.com/a/36106994/9252162
As result all request to *example.com will flow throught proxy1, all others through proxy2.
To do it I spent all day but with help of source (https://cs.chromium.org/) I found solution. Hope it helps someone.
Main problems:
1) In new version (72 or 74 as I remember) there is no ability to use "file://..." as pac_url.
2) We can't use https://developer.chrome.com/extensions/proxy in cef.. or i can't find how to do it.
p.s. How to use another types of proxy (https, socks) - https://chromium.googlesource.com/chromium/src/+/master/net/docs/proxy.md#evaluating-proxy-lists-proxy-fallback
With the new version of CefSharp, it's quite simple to set proxy:
browser = new ChromiumWebBrowser();
panel1.Controls.Add(browser);
browser.Dock = DockStyle.Fill;
await browser.LoadUrlAsync("about:blank");
await Cef.UIThreadTaskFactory.StartNew(() =>
{
browser.GetBrowser().GetHost().RequestContext.SetProxy("127.0.0.1", 1088, out string _);
});
I have the following code(facebook C# SDK) to post to facebook wall :
public long? UploadPost(string intLinkTitle, string inMessage, string inLinkCaption, string inLinkUrl, string inLinkDescription, string inLinkUrlPicture)
{
object obj;
Facebook.JsonObject jsonObj;
FacebookClient client;
string access_token = ConfigurationManager.AppSettings["FacebookPageAccessToken"].ToString();
client = new FacebookClient(access_token);
var args = new Dictionary<string, object>();
args["message"] = inMessage;
args["caption"] = inLinkCaption;
args["description"] = inLinkDescription;
args["name"] = intLinkTitle;
args["picture"] = inLinkUrlPicture;
args["link"] = inLinkUrl;
if ((obj = client.Post("/" + ConfigurationManager.AppSettings["FacebookPageId"].ToString() + "/feed", args)) != null)
{
if ((jsonObj = obj as Facebook.JsonObject) != null)
{
if (jsonObj.Count > 0)
return long.Parse(jsonObj[0].ToString().Split('_').Last().ToString());
}
}
return null;
}
}
This works great as long as I post to my public facebook website page but when changing the FacebookPageId to a group id instead I get (FacebookApiException - #200) Permissions error.
My user are admin of both the group and the page.
I have tried to post the message from the Graph API Explorer with the following line : 294632750660619/feed/?message=test but there is a syntax problem here, have also tried 294632750660619/feed?message=test but with no success.
How do I post to the closed facebook group?
Okay, I found the correct way. This is what I hade to do :
Go to the https://developers.facebook.com/ and create a new application
Settings > Add platform(Website and set the site URL(for example localhost..)
Set the app to go live(status & review > Yes), to do this a email adress needs to be set under settings > Contact Email
Go to the Graph API Explorer
Choose the new app from the dropdown
Click Get Access Token
Choose correct permissions(user_groups, user_status, user_photos, manage_pages, publish_actions, read_insights and read_stream) and click Get Access Token. Now we bot a short lived tooken
Generate a extended user token (valid for 60 days) by using this URL(change parameters(3)) : https://graph.facebook.com/oauth/access_token?grant_type=fb_exchange_token&client_id=[app-id]&client_secret=[app-secret]&fb_exchange_token=[short-lived-token]
Use the generated none expiring access token in the application
Validate the Access token here : https://developers.facebook.com/tools/debug/access_token/
Use this code to upload post :
public long? UploadPost(string intLinkTitle, string inMessage, string inLinkCaption, string inLinkUrl, string inLinkDescription, string inLinkUrlPicture)
{
object obj;
Facebook.JsonObject jsonObj;
FacebookClient client;
string access_token = ConfigurationManager.AppSettings["FacebookPageAccessToken"].ToString();
client = new FacebookClient(access_token);
var args = new Dictionary<string, object>();
args["message"] = inMessage;
args["caption"] = inLinkCaption;
args["description"] = inLinkDescription;
args["name"] = intLinkTitle;
args["picture"] = inLinkUrlPicture;
args["link"] = inLinkUrl;
if ((obj = client.Post("/" + ConfigurationManager.AppSettings["FacebookPageId"].ToString() + "/feed", args)) != null)
{
if ((jsonObj = obj as Facebook.JsonObject) != null)
{
if (jsonObj.Count > 0)
return long.Parse(jsonObj[0].ToString().Split('_').Last().ToString());
}
}
return null;
}
And to get feed, use this :
private void GetFeed()
{
object obj;
Facebook.JsonObject jsonObj;
Facebook.JsonObject jsonPaging;
FacebookClient client;
int pageCount = 0;
string access_token;
string URL;
DateTime fetchFaceBookFeedFromDate;
DateTime? oldestPostFetched = null;
fetchFaceBookFeedFromDate = DateTime.Now.AddDays(-30);
access_token = ConfigurationManager.AppSettings["FacebookPageAccessToken"].ToString();
URL = "/" + ConfigurationManager.AppSettings["FacebookPageId"].ToString() + "/feed";
client = new FacebookClient(access_token);
while (URL.Length > 0 && pageCount < 1000)
{
if ((obj = client.Get(URL)) != null)
{
if ((jsonObj = obj as Facebook.JsonObject) != null && jsonObj.Count > 0)
{
if (jsonObj[0] is Facebook.JsonArray)
oldestPostFetched = SaveFacebookForumThread(jsonObj[0] as Facebook.JsonArray, fetchFaceBookFeedFromDate);
if (jsonObj.Keys.Contains("paging") && (jsonPaging = jsonObj["paging"] as Facebook.JsonObject) != null && jsonPaging.Keys.Contains("next"))
URL = jsonPaging["next"].ToString();
else
break;
}
}
pageCount++;
if (oldestPostFetched.HasValue && fetchFaceBookFeedFromDate > oldestPostFetched)
break;
}
}