I am currently using the Salesforce API populate data.
In my Account controller, I have a method that uses Salesforce Username/Password flow to authorize the API usage and I've attached this so that it does this upon login, this process is fine, it works and I've tested it.
But where my issue comes in to play, I need to access the AccessToken value and ServiceUrl value it generated upon logging in so I can use my, for example, "GetEvents" method in ANOTHER controller.
Currently I am using models, no Data context at all because I don't need it. But if I were to instantiate this model in my other controller, with the AccessToken property, the value will not have been passed through from the controller method that generated this Token.
Keep in mind these Methods work just fine, but I need to be Authorized to view ANY of these events, and if it doesn't have that AccessToken and ServiceUrl from the login method, I can't view any events.
How do I go about this?
METHOD IN HomeController
public async void GetAllEvents()
{
TokenModel tm = new TokenModel();
HttpClient queryClient = new HttpClient();
string restQuery = tm.ServiceUrl + "/services/data/v25.0/query?q=SELECT+Subject+from+Event";
Console.WriteLine(restQuery);
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, restQuery);
Console.WriteLine(request);
// Adding the token to the header
request.Headers.Add("Authorization", "Bearer " + tm.AccessToken);
// Return JSON to the caller
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
//Call Endpoint Async
HttpResponseMessage response = await queryClient.SendAsync(request);
string result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
}
METHOD IN AccountController
[HttpPost]
[AllowAnonymous]
public IActionResult Login(AuthRequestModel authRequest)
{
if (!ModelState.IsValid)
{
return View(authRequest);
}
if(authRequest == null)
{
return BadRequest("Authorization is missing");
}
var isAuth = Authentication.ActiveDirectoryValidation(authRequest.Username, authRequest.Password);
if(isAuth == true)
{
AuthR().Wait();
return RedirectToAction("Index", "Home");
}
return View();
}
// Salesforce Username / Password Validation
public async Task<string> AuthR()
{
TokenModel tm = new TokenModel();
var handler = new HttpClientHandler()
{
SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls
};
HttpClient authClient = new HttpClient(handler);
string clientId = _config.GetSection("Keys").GetSection("client_id").Value;
string clientSecret = _config.GetSection("Keys").GetSection("client_secret").Value;
string username = _config.GetSection("Keys").GetSection("username").Value;
string password = _config.GetSection("Keys").GetSection("password").Value;
HttpContent content = new FormUrlEncodedContent(new Dictionary<string, string>
{
{"grant_type","password"},
{"client_id",clientId},
{"client_secret",clientSecret},
{"username",username},
{"password",password}
}
);
HttpResponseMessage message = await
authClient.PostAsync("https://test.salesforce.com/services/oauth2/token", content);
string responseString = await message.Content.ReadAsStringAsync();
var model = JsonConvert.DeserializeObject<Dictionary<string, string>>(responseString);
tm.AccessToken = model["access_token"];
tm.ServiceUrl = model["instance_url"];
return tm.AccessToken;
}
I'm not a C# guy but probably you should have a separate class to make new login call or reuse existing session id. Could be static method, could be a singleton pattern (whatever is the right word for it in C#); if statics behave OK being called from another model (thread?). Sharing session id in some global variable probably could work OK too but is bit smelly.
Maybe you have to store the session id somewhere (if you don't have some in-memory cache available then maybe file? database?), encrypted or not.
Battle-tested pattern for logging in to SF would look bit like that
do we have cached session id?
no -> call login()
-> it worked? great, cache new session id somewhere
-> didn't work (account deactivated, password expired, network problems) - you have bigger problems your program probably can't recover from nicely, notify user? There's way to reset password via API but it's bit "pro"
yes -> try to use it
-> worked? great
-> error? check the details / HTTP error code and if it's for example session id expired - call new login & handle it like above
Depending on authentication method you'll get info when the session id is about to expire (for example 2h from now but every API usage resets the counter). Don't count on that 2h too much. SF admin can terminate your session at any time (preventing hacker access for example). Or there's limit of sessions that can be open for same credentials, used to be something like 5th login() call invalidates oldest session id.
Related
I use CallResource.Create();
But it makes a call without ring back tone and disconnected and I need pure C# code to make outbound call form browser due to browser compatibility of twilio.js and I'm confusing regarding https URI what to specify there because create already make calls on "To" number.
public ActionResult MakeCall(string number)
{
string AccountSid = "********";
string AuthToken = "*********";
TwilioClient.Init(AccountSid, AuthToken);
var to = new PhoneNumber(number);
var from = new PhoneNumber("********");
ServicePointManager.SecurityProtocol =(SecurityProtocolType)3072;
if (number!= null) {
var call = CallResource.Create(to: to, from: from, m
achineDetection: "Enable",timeout:600, url: new Uri("https://handler.twilio.com/"));
ViewBag.Message = call.Sid;
}
return View("Index");
}
We have a three tier infrastructure (front end which is all Web API 2, Middleware which accepts API calls from front end and runs business logic and databases access, then the DB)
I'm trying to find out why our app locks up when I take the middle tier down. We use Memcached for all the reads and the front end serves the cached data just fine, but one of the calls that is made checks to see if the user is logged in. Running on my local machine with one app pool, that call locks the thread (I think) and prevents the rest of the calls from doing anything until the timeout on the autologin call expires.
The code path looks like this:
call to api/autologin --> front end API calls Client.SendAsync (our custom method for passing along data to the middleware), this tries to call the middlewware by using HttpClient.SendAsAsync with a timeout of 3 minutes (Probably should shorten this)
My expectation is that this should release this thread while we are waiting. That does not appear to be the result.
The REALLY weird thing is that when the middleware is down the Client.SendAsync gets ran MANY time, like 10. I thought this was maybe HTTP 2.0 in Chrome, but I switched to Fiddler and it did the same thing. Very weird.
So, two questions.
1. What's with the multiple calls?
2. Why do the threads appear to be getting locked?
Here's the code.
/// <summary>
/// Auto login user if they have the persistent cookies.
/// </summary>
/// <returns>The groups the logged in user has access to in the form of a
LoggedInUserData object.</returns>
[Route("api/cms/autologin/")]
[HttpGet]
public async Task<HttpResponseMessage> AutoLogin()
{
HttpResponseMessage response = await Client.SendAsync(this.Request);
return this.LoginCacheHelper(response);
}
That calls
public static async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request)
{
return await Client.SendAsync<string>(request, null, null, false);
}
Which calls
public static async Task<HttpResponseMessage> SendAsync<T>(HttpRequestMessage request, T content = null, string route = null, bool isFile = false, TimeSpan? timeout = null) where T : class
{
// Validate all internal certs.
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
// Determine the route and make sure route has a starting forward slash.
if (!string.IsNullOrWhiteSpace(route) && route.StartsWith("http"))
{
// Check to make sure this is a selinc.com domain for security purposes.
if (Sel.Utils.Validation.UriValidation.IsSelincDomain(route))
{
request.RequestUri = new Uri(route);
}
else
{
return new HttpResponseMessage(HttpStatusCode.BadRequest);
}
}
else
{
string middlewareRoute = GetRoute(route, request);
// Change Uri to middle ware.
request.RequestUri = new Uri(Config.MwareSiteUrl + middlewareRoute);
}
// Remove host header
request.Headers.Host = string.Empty;
// Set content of request.
// File content will be kept on the request as is.
if (content != null && !isFile)
{
request.Content = new ObjectContent<T>(content, new JsonMediaTypeFormatter());
}
else if (!isFile)
{
request.Content = null;
}
// Client handler set use cookies to false which will pass along the current cookies
HttpClientHandler clientHandler = new HttpClientHandler() { UseCookies = false };
// The HttpClient object
HttpClient client = new HttpClient(clientHandler);
client.Timeout = timeout ?? new TimeSpan(0, 3, 0);
// Send the request
return await client.SendAsync(request);
}
Adding image of the Network log in Chrome to illustrate the behavior.
Note that if I remove the API call to the autologin, everything works fine. It's the only call in this stack that hits the back end.
Also note: If I modify the SendAsync method to just return a new HttpResponseMessage (and thus do no work) then the autologin basically does nothing, returns quickly and site loads as it should, with the middleware server down. This is just to prove that it is the autologin API call causing the problem. The autologin API call is the only method calling SendAsync at this time so it's a valid test.
// Send the request
////return await client.SendAsync(request);
return new HttpResponseMessage();
I am using TweetSharp to send tweets to users (currently testing it) however it keeps coming back with Bad Authentication Data
{"errors":[{"code":215,"message":"Bad Authentication data."}]}
I have checked my app settings and it has full read and write access. I have also tried to regenerate my consumer keys but still not luck.
here is my code
public ActionResult AccessToken()
{
string oauth_consumer_key = "<consumer key>";
string oauth_consumer_secret = "<consumer secret>";
var service = new TwitterService(oauth_consumer_key, oauth_consumer_secret);
// Now we need the Token and TokenSecret
OAuthRequestToken requestToken = service.GetRequestToken("http://localhost:37808/");
string authURL = service.GetAuthorizationUri(requestToken).ToString();
Process.Start(authURL);
SendTweetOptions options = new SendTweetOptions();
options.Status = "Hello there Twitter";
service.SendTweet(options);
var re = service.Response.Response;
return View();
}
Am I doing anything wrong?
Finally solved the issue and it works well. Based upon comments from Yort.
public ActionResult AccessToken()
{
// Step 1 - Retrieve an OAuth Request Token
TwitterService service = new TwitterService(ConfigurationManager.AppSettings["TwitterConsumerKey"], ConfigurationManager.AppSettings["TwitterConsumerSecret"]);
// This is the registered callback URL
OAuthRequestToken requestToken = service.GetRequestToken("http://localhost:37808/Twitter/OToken");
// Step 2 - Redirect to the OAuth Authorization URL
Uri uri = service.GetAuthorizationUri(requestToken);
return new RedirectResult(uri.ToString(), false /*permanent*/);
//return View();
}
public ActionResult OToken()
{
return View();
}
public ActionResult UserInfo(string oauth_token, string oauth_verifier)
{
var requestToken = new OAuthRequestToken { Token = oauth_token };
// Step 3 - Exchange the Request Token for an Access Token
TwitterService service = new TwitterService(ConfigurationManager.AppSettings["TwitterConsumerKey"],
ConfigurationManager.AppSettings["TwitterConsumerSecret"]);
OAuthAccessToken accessToken = service.GetAccessToken(requestToken, oauth_verifier);
// Step 4 - User authenticates using the Access Token
service.AuthenticateWith(accessToken.Token, accessToken.TokenSecret);
TwitterUser user = service.VerifyCredentials(new VerifyCredentialsOptions());
ViewBag.Message = string.Format("{0}", user.ScreenName);
// Step 5 - Send Tweet to User TimeLine
SendTweetOptions options = new SendTweetOptions();
string URL = "file:\\C:\\Users\\<User>\\Desktop\\test.jpg";
string path = new Uri(URL).LocalPath;
// Sending with Media
using (var stream = new FileStream(path, FileMode.Open))
{
service.SendTweetWithMedia(new SendTweetWithMediaOptions
{
Status = "<status>",
Images = new Dictionary<string, Stream> { { path, stream } }
});
}
var responseText = service.Response.StatusCode;
if (responseText.ToString() == "OK")
{
ViewBag.Message = "Tweet Successful";
}
else
{
ViewBag.Message = "Tweet Unsuccessful";
}
return View();
}
}
I don't believe you can send Tweets as just a consumer, the Tweets have to be "owned" by a user account. You need to register a Twitter account, then do the full oauth authentication process to get an access token (in addition to the consumer token), then reauthorise the TweetSharp service using both tokens.
Your code above nearly gets there (I think). After the Process.start call there needs to be logic to use the verifier returned in the browser (a number displayed after the user logs in) to complete the auth process and act as that user. At the moment, your code gets half way through that process but does not complete it, so when you try to tweet your TweetSharp service is only authed as the app and not the user.
The originalTweetSharp readme.md does include the missing bits of code. Step 3 needs the actual verifier returned in the browser after login:
// Step 3 - Exchange the Request Token for an Access Token
string verifier = "123456"; // <-- This is input into your application by your user
OAuthAccessToken access = service.GetAccessToken(requestToken, verifier);
// Step 4 - User authenticates using the Access Token
service.AuthenticateWith(access.Token, access.TokenSecret);
//Now your tweet call should work here.
It also looks like you're doing this in a web app on the server? In which case you're using entirely the wrong oauth flow (I believe). This one is designed for desktop apps, hence the call that starts a new browser process for the user to login with. I'm not entirely sure how the web flow works as I've never used it, but I believe you need to redirect the user to the authorisation url you receive, and the callback registered with Twitter should point back to your site. I think there is some kind of state parameter that can be passed back through the oauth flow so you can implement your own logic to pickup where you left off based on a session id or similar.
I worked on this subject before. You have to developer account before the send tweet because you need tokens and keys. It's my windows service project.
I wrote my tokens and key codes in App.config
<appSettings>
<add key="twitterAccessToken" value="*****"/>
<add key="twitterAccessTokenSecret" value="*****"/>
<add key="twitterConsumerKey" value="*****"/>
<add key="twitterConsumerSecret" value="*****"/>
public static void SendTweet()
{
try
{
GetPixelImageFile();
string key = ConfigurationSettings.AppSettings.Get("twitterConsumerKey");
string secret = ConfigurationSettings.AppSettings.Get("twitterConsumerSecret");
string token = ConfigurationSettings.AppSettings.Get("twitterAccessToken");
string tokenSecret = ConfigurationSettings.AppSettings.Get("twitterAccessTokenSecret");
string message = "Color, Colorful, Pixel, Art, PixelColouring, Follow";
var service = new TweetSharp.TwitterService(key, secret);
service.AuthenticateWith(token, tokenSecret);
using (var stream = new FileStream(#"C:\Images\Pixel.png", FileMode.Open))
{
var result = service.SendTweetWithMedia(new SendTweetWithMediaOptions
{
Status = message,
Images = new Dictionary<string, Stream> { { "john", stream } }
});
SendMail("SendTweet", (result == null ? "" : result.Text));
}
}
catch (Exception ex)
{
SendMail("SendTweet", ex.Message);
}
}
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
I am using ACS to authenticate in a Windows 8 application. I am observing exactly what I expect in that the UI displays the authentication dialog and on successfully entering my LiveID credentials I am returned to my code with a Success status but I do not receive a security token, I simply get "https://XXXXX.accesscontrol.windows.net/v2/wsfederation?wa=wsignin1.0" in result.ResponseData
The code is as follows:
string loginUriString = "https://XXXXX.accesscontrol.windows.net:443/v2/wsfederation?wa=wsignin1.0&wtrealm=http%2f%YYYYY.cloudapp.net";
string redirectUriSting = "https://XXXXX.accesscontrol.windows.net:443/v2/wsfederation";
string authToken;
bool IsAuthenticated = false;
private async Task AuthenticateAsync()
{
var requestUri = new Uri(loginUriString, UriKind.RelativeOrAbsolute);
var redirectUri = new Uri(redirectUriSting, UriKind.RelativeOrAbsolute);
//var testUri = WebAuthenticationBroker.GetCurrentApplicationCallbackUri();
var result = await WebAuthenticationBroker.AuthenticateAsync(
WebAuthenticationOptions.None,
requestUri,
redirectUri);
if (result.ResponseStatus != WebAuthenticationStatus.Success)
throw new Exception(string.Format("Login failed : {0}", result.ResponseErrorDetail));
//authToken = ExtractTokenFromResponse(result.ResponseData);
//if (!string.IsNullOrEmpty(authToken))
//{
_client.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("OAuth", result.ResponseData);
IsAuthenticated = true;
//}
}
I have seen one other SO question here with what seems like a similar problem but nothing else. Have I got something wrong?
The WebAuthenticationBroker simply keeps browsing until the next requested page is the one specified by the callbackUri parameter. At that point it returns the final URL to you so if you want to get anything back it needs to be encoded in that URL.
In the ACS control panel for the relying party you need to specify a return url that is somewhere on your site. For example https://YYYYY.cloudapp.net/federationcallback. Then create a controller to handle accept a post to that URL. The post will have a form field wresult which is some xml that will contain the token returned from ACS.
You can then send the token back to the WebAuthenticationBroker by redirecting to https://YYYYY.cloudapp.net/federationcallback/end?token={whatever you want to return}
You would then need to change the usage of the authentication broker to the following:
var webAuthenticationResult = await WebAuthenticationBroker.AuthenticateAsync(
WebAuthenticationOptions.None,
new Uri("https://XXXXX.accesscontrol.windows.net:443/v2/wsfederation?wa=wsignin1.0&wtrealm=http%3a%2f%2fYYYYY.cloudapp.net%2f"),
new Uri("https://YYYYY.cloudapp.net/federationcallback/end")
);
// The data you returned
var token = authenticateResult.ResponseData.Substring(authenticateResult.ResponseData.IndexOf("token=", StringComparison.Ordinal) + 6);