C# SDK posting to my own Page feed - facebook-graph-api

The situation:
I have a website and creates "posts". As a new post is created I want to send the new post to my facebook pages feed. I have all of the code down to do this and it works fine as long as I get an access token from the graph API explorer tool. This is not going to work as it expires after about an hour. When I generate the access token from code, it appears that it is a app access token and it does not give me access to my page. So the big question is how do I obtain a user access token from code that will have access to post to my page.
Here is how I am getting the access token.
private static string GetApiAccessToken()
{
var client = new FacebookClient();
dynamic result = client.Get("oauth/access_token", new
{
client_id = SessionGetter.Instance.FacebookApiKey,
client_secret = SessionGetter.Instance.FacebookSecretKey,
grant_type = "client_credentials",
scope = "manage_pages"
});
return result.access_token;
}
Then I use the access token to try and get the Page access token and this is where it tells me that I don't have authorization and all I get back in the dictionary is an "id".
private static string GetPageAccessToken(string accessToken, string pageId)
{
try
{
var fb = new FacebookClient(accessToken);
var parameters = new Dictionary<string, object>();
parameters["fields"] = "access_token";
var result = (IDictionary<string, object>)fb.Get(pageId, parameters);
var pageAccessToken = (string)result["access_token"];
return pageAccessToken;
}
catch (FacebookApiException ex)
{
}
return null;
}
Now like I said, if I use the access token from the graph explorer, the code works fine.
Then the post is made to the graph API
var facebookClient = new FacebookClient(pageAccessToken);
var result = (IDictionary<string, object>)facebookClient.Post("me/feed", new Dictionary<string, object>
{{"message",postMessage}, {"picture", csLogo},
{"link", LinkHelper.AssignmentUrl(wrapper.Assignment)}});

Make sure the user access token has manage_pages extended permissions.
then make a request to me/accounts to the get the page access token.
Then post to {pageid}/feed using the page access token.

Related

is "Client_credential" grant type can be used to access powerBI scopes?

In my project, I am trying to get embed report information from powerBI without interactive login.
so far I am able to get the information using "password" grant; when I am trying to do the same thing using "Password" grant, it is giving me "unauthorized" error.
I have hosted the application in my link to github.
explaination of the use:
Get access token to get access token for authentication.
var form = new Dictionary<string, string>();
form["grant_type"] = "password";
form["username"] = _config.Azure.Username;
form["password"] = _config.Azure.Password;
form["client_id"] = _config.Azure.ClientID;
form["client_secret"] = _config.Azure.ClientSecret;
form["scope"] = string.Join(" ",_config.Azure.Scopes);
var content = new FormUrlEncodedContent(form);
var input = new HttpInputModel
{
BaseAddress = $"{_config.Azure.AuthorityUrl}/{_config.Azure.TenantID}/",
Content = content,
Headers = new Dictionary<string, string>
{
{ "Content-Type","application/x-www-form-urlencoded" }
},
Url = "oauth2/v2.0/token"
};
use the above accesstoken came from above to get powerBI client.
var token = await _azureService.GetAccessToken();
var tokenCredentials = new TokenCredentials(token, "Bearer");
_client = new PowerBIClient(new Uri(_config.PowerBI.ApiUrl), tokenCredentials);
use the client to retrieve report information.
var workspaceId = reportInput.WorkspaceID;
var reportId = reportInput.ReportID;
var report = await _client.Reports.GetReportInGroupAsync(workspaceId, reportId);
var generateTokenRequestParameters = new GenerateTokenRequest(accessLevel: "view");
var tokenResponse = await _client.Reports.GenerateTokenAsync(workspaceId, reportId, generateTokenRequestParameters);
var output = new ReportOutput
{
Username = _config.PowerBI.Username,
EmbdedToken = tokenResponse,
EmbedUrl = report.EmbedUrl,
Id = reportInput.ReportID.ToString()
};
return output;
Conclusion: at the step 1: you can see I am using password grant
type. When I tried "client_crdentials" in place of "password" and did
not give "username" and "password". The generated "accesstoken" was
giving "Unauthorized" exception in step 3.
Note:I have tried to search in a lot of forums and some of them say we can use "serviceprincipal". But I am not able to do that.
I tried to follow the microsoft article for it and in that article they have mentioned to add SPN in pwoerBI. I tried that alot of times but it never worked for me.
Then i used another account andbit worked there so it was my accounts problem which was not allowing me to add SPN.
I already posted whole solution in github eith steps.
Here is the link:
https://github.com/Donjay2101/API--access-powerbi-without-login

Using MSAL to get access token and cache it in SQL DB, without having to sign in using MSAL

I want to authenticate AAD users to access powerBi resources through MSAL by using application ID and secret. So i want to get the access token and cache it in SQL Db.
went through the documentation but it explains the scenario of using MSAL for sign-in.
also went through the tutorial
i was able to to do the necessary implementations to get the token.
how can i get the access token and cache it, in a scenario like this?
As indicated in other answers, caching tokens are useful in case when you have users signing in, as once the access token expires (typically after 1 hour), you don't want to keep prompting the users to re-authenticate.
So help with these scenarios, Azure AD issues a refresh token along with an access token that is used to fetch access tokens once they expire. Caching is required to cache these refresh tokens as they are valid for 90 days.
When an app signs as itself (and not signing in a user), the client credentials flow is used and it only needs the app id (clientId) and the credential (secret/certificate) to issue an access token. The MSAL library will automatically detect when the access token expires and will use the clientId/credential combination to automatically get a new access token. So caching is not necessary.
The sample you should be looking at is this one.
I'n not sure to understand, I hope these few lines of code will help you.
First, customize token cache serialization :
public class ClientApplicationBuilder
{
public static IConfidentialClientApplication Build()
{
IConfidentialClientApplication clientApplication =
ConfidentialClientApplicationBuilder
.Create(ClientId)
.WithRedirectUri(RedirectUri)
.WithClientSecret(ClientSecret)
.Build();
clientApplication.UserTokenCache.SetBeforeAccessAsync(BeforeAccessNotification);
clientApplication.UserTokenCache.SetAfterAccessAsync(AfterAccessNotification);
return clientApplication;
}
private static async Task<byte[]> GetMsalV3StateAsync()
{
//TODO: Implement code to retrieve MsalV3 state from DB
}
private static async Task StoreMsalV3StateAsync(byte[] msalV3State)
{
//TODO: Implement code to persist MsalV3 state to DB
}
private static async Task BeforeAccessNotification(TokenCacheNotificationArgs args)
{
byte[] msalV3State = await GetMsalV3StateAsync();
args.TokenCache.DeserializeMsalV3(msalV3State);
}
private static async Task AfterAccessNotification(TokenCacheNotificationArgs args)
{
if (args.HasStateChanged)
{
byte[] msalV3State = args.TokenCache.SerializeMsalV3();
await StoreMsalV3StateAsync(msalV3State);
}
}
}
Here's an example to acquire token (by Authorization Code) :
public class MsAccountController
: Controller
{
private readonly IConfidentialClientApplication _clientApplication;
public MsAccountController()
{
_clientApplication = ClientApplicationBuilder.Build();
}
[HttpGet]
public async Task<IActionResult> Index()
{
Uri authorizationRequestUrl = await _clientApplication.GetAuthorizationRequestUrl(ClientApplicationHelper.Scopes).ExecuteAsync();
string authorizationRequestUrlStr = authorizationRequestUrl.ToString();
return Redirect(authorizationRequestUrlStr);
}
[HttpGet]
public async Task<IActionResult> OAuth2Callback(string code, string state)
{
AuthenticationResult authenticationResult = await _clientApplication.AcquireTokenByAuthorizationCode(scopes, code).ExecuteAsync();
return Ok(authenticationResult);
}
}
Finally, acquire a token silently and use auth result for your API client :
public class TaskController
: Controller
{
private readonly IConfidentialClientApplication _clientApplication;
public TaskController()
{
_clientApplication = ClientApplicationBuilder.Build();
}
[HttpGet]
public async Task<IActionResult> Index()
{
IEnumerable<IAccount> accounts = await _clientApplication.GetAccountsAsync();
AuthenticationResult result = await _clientApplication.AcquireTokenSilent(ClientApplicationHelper.Scopes, accounts.FirstOrDefault()).ExecuteAsync();
//TODO: Create your API client using authentication result
}
}
Regards
You can cache the access token (actually, the library does this already), but it is valid for 1 hour only. So it makes no sense to save it in a database, because it will expire quickly.
You should cache the credentials needed to obtain the token (user name and password, app ID and secret, or certificate) and obtain a token when needed.
I've done this for a confidential client application, where I connected to O365 in order to send email.
First, register your app in azure app as the docs say.
Then, set up your confidential client application and use as singleton.
var app = ConfidentialClientApplicationBuilder.Create(clientId)
.WithClientSecret(clientSecret)
.WithRedirectUri(redirectUri)
.WithLegacyCacheCompatibility(false)
.WithAuthority(AadAuthorityAudience.AzureAdAndPersonalMicrosoftAccount)
.Build();
app.AddDistributedTokenCache(services => {
services.AddDistributedTokenCaches();
services.AddDistributedSqlServerCache(options => {
options.SchemaName = "dbo";
options.TableName = "O365TokenCache";
options.ConnectionString = sqlCacheConnectionString;
options.DefaultSlidingExpiration = TimeSpan.FromMinutes(90);
});
});
services.AddSingleton<IConfidentialClientApplication>(app);
The first time you connect a user, you need to redirect to Microsoft identity. You can create the URL using:
var authUrl = await app.GetAuthorizationRequestUrl(new[] { "email", "offline_access", "https://outlook.office.com/SMTP.Send" }).ExecuteAsync();
(Check your scopes are what you want)
When they come back to your redirect url you then get the code from query string and acquire the refresh token:
var token = await app.AcquireTokenByAuthorizationCode(scopes, code).ExecuteAsync();
When you do this, MSAL will cache the access token and refresh token for you, but here's the thing they don't mention: you have to create the table in SQL yourself! If you don't, it just silently fails.
dotnet tool install -g dotnet-sql-cache
dotnet sql-cache create "<connection string>" dbo O365TokenCache
Once you have the access token the first time you can use the following later
var account = await app.GetAccountAsync(accountId);
var token = await app.AcquireTokenSilent(scopes, account).ExecuteAsync();
When you get the access token the first time, you need to look at token.Account.HomeAccountId.Identifier as this is the ID that you need when you call GetAccountAsync. For some reason, GetAccountsAsync (note the extra "s") always returns empty for me but passing the correct ID to GetAccountAsync does return the right one.
For me, I simply store that ID against the logged in user so that I can get that ID at a later time.

Single Sign-On When Using PowerBI REST API

I created a test asp.net mvc application to display PowerBI reports using the PowerBI REST API. I'm using Azure Active Directory authentication as the authentication mechanism for the site. Is there any way to re-use credentials from MVC Azure AD authentication framework to get access to the Power BI API? Currently, when the app makes the request to get the access token for the application, the user needs to log in again.
I was able to resolve this issue by following the example in: https://github.com/Azure-Samples/active-directory-dotnet-graphapi-web. They show code to authenticate against AAD and then use the token to access the graphAPI. I used their approach and used the token to access the PowerBI API.
When authenticating with Azure AD you should be getting a token back, you can use a Token Cache to store the users token for later. When you need to get an access token for the powerbi api you can use the refresh token from the initial token you got back.
public JsonResult GetPowerBiToken()
{
var clientId = ConfigurationManager.AppSettings["ida:ClientId"];
var appKey = ConfigurationManager.AppSettings["ida:ClientSecret"];
var aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
var tenantId = ConfigurationManager.AppSettings["ida:TenantId"];
string Authority = aadInstance + tenantId;
var claim = CurrentUserClaims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier);
AuthenticationContext authContext = new AuthenticationContext(Authority, new AdalTokenCache(claim.Value));
var refresh_token = authContext.TokenCache.ReadItems().FirstOrDefault().RefreshToken;
ClientCredential credential = new ClientCredential(clientId, appKey);
var resp = authContext.AcquireTokenByRefreshToken(refresh_token, credential, "https://analysis.windows.net/powerbi/api");
return Json(new { Token = resp.AccessToken }, JsonRequestBehavior.AllowGet);
}
Hope this helps.

Mirror API add timeline item in offine

I have a mirror api app, and authorised users via outh and saved their access token and refresh token in database for offline use. Now I want to add timeline item to those users offline (using saved access token and refresh token).
Here is my code,
String accessToken = db.getAccessToken();
String refreshToken = db.getRefreshToken();
BatchRequest batch = MirrorClient.getMirror(null).batch();
BatchCallback callback = new BatchCallback();
TimelineItem notifyTimelineItem = new TimelineItem();
notifyTimelineItem.setHtml(ZONE_HTML);
Credential userCredential = AuthUtil.getCredential(userUniqueId);
userCredential.setAccessToken(accessToken);
userCredential.setRefreshToken(refreshToken);
MirrorClient.getMirror(userCredential).timeline().insert(notifyTimelineItem).queue(batch, callback);
batch.execute();
Here am getting error like Failed to insert item, user need to login . How can I add timeline item in offline?
Actually I got userCredential as null. So I used below code and it works fine.
String accessToken = db.getAccessToken();
Mirror service = new Mirror.Builder(new NetHttpTransport(), new GsonFactory(), null).setApplicationName("GOauthAndroid").build();
TimelineItem timelineItem = new TimelineItem();
timelineItem.setHtml(DANGER_ZONE_HTML);
timelineItem.setNotification(new NotificationConfig().setLevel("DEFAULT"));
try {
service.timeline().insert(timelineItem).setOauthToken(newAccessToken).execute();
} catch (IOException e) {
e.printStackTrace();
}

Posting on user's wall doesn't fail but doesn't who either

I'm having an issue where I'm posting a new message using facebook SDK 6.0 with:
var client = new FacebookClient("<target users access token>")
{
AppId = "<my app id>",
AppSecret = "<my app secret>"
};
var args = new Dictionary<string, object>
{
{"message", "A message",
};
dynamic postResult = client.Post("/me/feed", args);
The interesting part is that the postResult has an id property, but the message doesn't appear on the users feed/wall. Using the API to get all feeds doesn't return the post and manually viewing it using https://graph.facebook.com/[message_id] simple returns "false".
I've tried this with sandbox mode on and off and it makes to difference.
Any ideas on what I've done wrong?