I am working on a Windows app and am having some issues with cookies. Please note that I am working with Windows.Web.Http, not the System namespace HttpClient.
The API I'm working with uses an auth-header for authentication. Basically after a POST to login, I need a way to get the cookies returned and then use those cookies to perform the subsequent API calls. I posted an example of what I currently have, which succeeds. I can see the cookies in the result object. I'm just not entirely sure where to go from here / how to proceed. Thanks! Any ideas?
using MyApi.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Web.Http;
using Newtonsoft.Json;
using MyApi.Models.Auth;
using MyApi.Models;
namespace MyApi
{
public class MyService
{
private const string MyBaseUrl = "http://api.my.com:3000";
private readonly HttpClient _httpClient = new HttpClient();
public async Task<SignInResponse> AttemptLogin(string username, string password)
{
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
throw new ArgumentException("Username or password is null or empty");
var uri = new Uri(string.Format("{0}/{1}", MyBaseUrl, "auth/signin"));
var authSignIn = new Models.Auth.SignInRequest();
authSignIn.Email = username;
authSignIn.Password = password;
var myObject = JsonConvert.SerializeObject(authSignIn);
// I see the headers in the result object, but I'm not
// sure the best way to a) get them out and b) shove them into
// all of the next calls
var result = await _httpClient.PostAsync(uri,
new HttpStringContent(myObject.ToString(),
Windows.Storage.Streams.UnicodeEncoding.Utf8,
"application/json"));
var content = await result.Content.ReadAsStringAsync();
var successResponse = new SignInResponse();
try
{
successResponse = JsonConvert.DeserializeObject<SignInResponse>(content);
}
catch (Exception)
{
var failResponse = JsonConvert.DeserializeObject<ErrorResponse>(content);
throw new Exception(failResponse.message);
}
return successResponse;
}
}
}
You can use HttpBaseProtocolFilter.CookieManager, e.g.:
var filter = new HttpBaseProtocolFilter();
var cookieManager = filter.CookieManager;
var uri = new Uri("http://api.my.com:3000");
foreach (var cookie in cookieManager.GetCookies(uri))
{
Debug.WriteLine(cookie.Name);
Debug.WriteLine(cookie.Value);
}
Notice, if the cookies are already in the HttpCookieContainer, the cookies will be automatically added in the next requests to http://api.my.com:3000, and no action is required from your side.
If you want to modify them or delete them, the HttpCookieContainer has methods to do that.
Take a look at Flurl. It presents a fluent interface over the Http bits, so you can say something like this to authenticate and reuse the connection with the cookies:
using (var fc = new FlurlClient().EnableCookies())
{
var url = new Url( "http://api.com/endpoint" ) ;
await url
.AppendPathSegment("login")
.WithClient(fc)
.PostUrlEncodedAsync(new { user = "user", pass = "pass" });
var page = await url
.AppendPathSegment("home")
.WithClient(fc)
.GetStringAsync();
// Need to inspect the cookies? FlurlClient exposes them as a dictionary.
var sessionId = fc.Cookies["session_id"].Value;
}
Related
Here is my code:
using System;
using System.Net;
using Google.Apis.Calendar.v3;
using Google.Apis.Calendar.v3.Data;
using Google.Apis.Services;
using Google.Apis.Auth.OAuth2;
using System.Threading;
using System.Linq;
namespace WebsiteTextExtractor {
class Program {
async static Task Main(string[] args) {
string websiteURL = "CensoredURLThatWorks";
WebClient client = new WebClient();
string websiteText = client.DownloadString(websiteURL);
//File.WriteAllText(Environment.GetFolderPath(Environment.SpecialFolder.Desktop)+ "\\ElieCours.ics", websiteText);
// Console.WriteLine($"ElieCours.ics Updated at {Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\ElieCours.ics"}.");
string[] scopes = new string[] { CalendarService.Scope.Calendar };
var credentials = GoogleWebAuthorizationBroker.AuthorizeAsync(
new ClientSecrets {
ClientId = "[redacted]",
ClientSecret = "[redacted]"
},
scopes,
"MyClient",
CancellationToken.None
).Result;
var service = new CalendarService(new BaseClientService.Initializer() {
HttpClientInitializer = credentials,
ApplicationName = "CalendarCleaner"
});
var events = service.Events.List("fc47dab8b05e7b6d0e549ff3207b9f400adfb8ff96b50ecc4ed51a9ecc75ebbe#group.calendar.google.com").Execute().Items;
//Deleting all events
ParallelOptions parallel = new() {
MaxDegreeOfParallelism = 3,
};
Console.WriteLine(events.Count);
await Parallel.ForEachAsync(events, parallel, async (ev, token) => {
await Task.Delay(300, token);
await service.Events.Delete("fc47dab8b05e7b6d0e549ff3207b9f400adfb8ff96b50ecc4ed51a9ecc75ebbe#group.calendar.google.com", ev.Id).ExecuteAsync(token);
});
Ical.Net.Calendar calendarICS = Ical.Net.Calendar.Load(websiteText);
var eventsICS = calendarICS.Events;
await Parallel.ForEachAsync(eventsICS, parallel, async (ev, token) => {
Event newEvent = new() {
Summary = ev.Summary,
Start = new EventDateTime { DateTime = ev.Start.AsSystemLocal },
End = new EventDateTime { DateTime = ev.End.AsSystemLocal },
Description = ev.Description,
};
await Task.Delay(300, token);
await service.Events.Insert(newEvent, "fc47dab8b05e7b6d0e549ff3207b9f400adfb8ff96b50ecc4ed51a9ecc75ebbe#group.calendar.google.com").ExecuteAsync(token);
});
}
}
}
the part where i'm supposed to be getting the events that are in the "events" var is troubling me: the array is empty. But the adding events part just below is working perfectly. Why?
I saw this post: Google Calendar Events Response is Empty
and i was even more confused, because my methods has nothing to do with what they have.
i tried adding a service account, freshly created, to my agenda: nothing. Making my agenda public: nothing. Whatever i do, this array is empty. Best thing is that it wasn't earlier this morning, and now it is. And i know this might be dumb to specify, but i triplechecked my agenda, and it's not empty.
Worst thing is : deleting used to work.
EDIT: saw this post: Google Calendar v3 API [Events: list] request return Empty List
An answer was really interesting... Detailed the way google doesn't really deleted the events or so...? I'm so confused.
"There is no way I know of to return all events in one call. You need to loop through the process getting a page at a time until the "NextPageToken" is no longer returned. This makes sense, because for users that have huge calendars with 1000's of appointments, it's inefficient to return everything in one request." Even more here, never heard of nextpagetoken or so
Blockquoteafter access token when I called graph API that returns Authorization_RequestDenied request for the access token
using (var webClient = new WebClient())
{
var requestParameters = new NameValueCollection();
requestParameters.Add("resource", resource);
requestParameters.Add("client_id", clientID);
requestParameters.Add("grant_type", "client_credentials");
requestParameters.Add("client_secret", secret);
var url = $"https://login.microsoftonline.com/{tenant}/oauth2/token";
var responsebytes = await webClient.UploadValuesTaskAsync(url,"POST",requestParameters);
var responsebody =Encoding.UTF8.GetString(responsebytes);
var obj = JsonConvert.DeserializeObject<JObject>(responsebody);
var token = obj["access_token"].Value<string>();
access_token = token;
}
after when i request form get the user list from Azure AD by this way
public async Task<List<listItems>> GetData1( string token)
{
HttpClient http = new HttpClient();
string query = "https://graph.microsoft.com/v1.0/users";
HttpRequestMessage httpClient = new HttpRequestMessage(HttpMethod.Get, query);
httpClient.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
var res = await http.SendAsync(httpClient);
var res1= await res.Content.ReadAsStringAsync();
List<listItems> lstUsers = new List<listItems>();
JObject results = JObject.Parse(res1); listItems itm;
foreach (var Jelem in results["value"])
{
string id = (string)Jelem["id"];
string displayName = (string)Jelem["displayName"];
itm = new listItems(); itm.id = id;
itm.displayname = displayName; lstUsers.Add(itm);
}
return lstUsers;
}
than i got "error": { "code": "Authorization_RequestDenied", "message": "Insufficient privileges to complete the operation.", "innerError": { "request-id": "1ba8a3e3-7e27-4bad-affd-6929b9af3a9f", "date": "2019-03-26T10:56:26" } the above error
please help me to solve this error
CAUSE
This problem occurs because the application does not have the required permission to access the user information. So you need to assign necessary privileged for this request.
SOLUTION
To access https://graph.microsoft.com/v1.0/users API One of the following permissions is required.
Permission type (from least to most privileged)
Delegated (work or school account) User.Read, User.ReadWrite,
User.ReadBasic.All,
User.Read.All, User.ReadWrite.All, Directory.Read.All,
Directory.ReadWrite.All,
Directory.AccessAsUser.All
Delegated (personal Microsoft account) User.Read, User.ReadWrite
Application User.Read.All, User.ReadWrite.All, Directory.Read.All,
Directory.ReadWrite.All
See the screen shot below:
AZURE PORTAL WAY OUT
To assign permission on azure portal see the screen shot below:
ASP.NET WEB FORM EXAMPLE:
1. Add New Aspx page To project
Take a new web form, here I have taken as Token.aspx and set its property like below
<%# Page Language="C#" AutoEventWireup="true" Async="true"
CodeBehind="Token.aspx.cs" Inherits="WebFormTest.Token" %>
2. Add New Reference from Nuget
In your project reference add a new service reference from nuget package manager console Like below:
3. Token.aspx.cs
Paste following code outside the scope of Page_Load method You might need to add following reference on your namespace once you encounter missing reference error.
using System.Net.Http;
using System.Net.Http.Headers;
class AccessToken
{
public string access_token { get; set; }
}
// Resource Owner Password Credentials Format
private async Task<string> GetTokenByROPCFormat()
{
string tokenUrl = $"https://login.microsoftonline.com/YourTenantId/oauth2/token";
var req = new HttpRequestMessage(HttpMethod.Post, tokenUrl);
req.Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "password",
["client_id"] = "ApplicationID",
["client_secret"] = "ApplicationSecret",
["resource"] = "https://graph.microsoft.com",
["username"] = "userEmailwithAccessPrivilege",
["password"] = "YourPassword"
});
dynamic json;
dynamic results;
HttpClient client = new HttpClient();
var res = await client.SendAsync(req);
json = await res.Content.ReadAsStringAsync();
//Token Output
results = JsonConvert.DeserializeObject<AccessToken>(json);
Console.WriteLine(results.access_token);
//New Block For Accessing Data from Microsoft Graph API
HttpClient newClient = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "https://graph.microsoft.com/v1.0/me");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", results.access_token);
HttpResponseMessage response = await newClient.SendAsync(request);
string output = await response.Content.ReadAsStringAsync();
Console.WriteLine("Responsed data Is-\n\n" + output + "");
return output;
}
4. Call GetTokenByROPCFormat() Method inside Page_Load
Now call GetTokenByROPCFormat inside the Page_Load like below
RegisterAsyncTask(new PageAsyncTask(GetTokenByROPCFormat));
5. Token Output
If you set debugger on results variable you would get your token like below
6. Access Microsoft Graph API
Now move to following line and set your debugger like below
string output = await response.Content.ReadAsStringAsync();
You would see following output
Hope it would solve your problem. Thank you.
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
}
I am new to Google Compute Engine. Some one please help me with creating Google Compute Engine VMs programmatically using REST APIs in C#.
Here [1] you can found the API documentation to create an instance and at the bottom of the document the C# examples [2]:
using Google.Apis.Auth.OAuth2;
using Google.Apis.Compute.v1;
using Google.Apis.Services;
using Newtonsoft.Json;
using System;
using System.Threading.Tasks;
using Data = Google.Apis.Compute.v1.Data;
namespace ComputeSample
{
public class ComputeExample
{
public static void Main(string[] args)
{
ComputeService computeService = new ComputeService(new BaseClientService.Initializer
{
HttpClientInitializer = GetCredential(),
ApplicationName = "Google-ComputeSample/0.1",
});
// Project ID for this request.
string project = "my-project"; // TODO: Update placeholder value.
// The name of the zone for this request.
string zone = "my-zone"; // TODO: Update placeholder value.
// TODO: Assign values to desired properties of `requestBody`:
Data.Instance requestBody = new Data.Instance();
InstancesResource.InsertRequest request = computeService.Instances.Insert(requestBody, project, zone);
// To execute asynchronously in an async method, replace `request.Execute()` as shown:
Data.Operation response = request.Execute();
// Data.Operation response = await request.ExecuteAsync();
// TODO: Change code below to process the `response` object:
Console.WriteLine(JsonConvert.SerializeObject(response));
}
public static GoogleCredential GetCredential()
{
GoogleCredential credential = Task.Run(() => GoogleCredential.GetApplicationDefaultAsync()).Result;
if (credential.IsCreateScopedRequired)
{
credential = credential.CreateScoped("https://www.googleapis.com/auth/cloud-platform");
}
return credential;
}
}
}
[1] https://cloud.google.com/compute/docs/reference/rest/v1/instances/insert
[2] https://cloud.google.com/compute/docs/reference/rest/v1/instances/insert#examples
I'm trying to attach an api key to the OperationContext outgoing message header as follows:
public static void AddApikeyToHeader(string apikey, IContextChannel channel, string address)
{
using (OperationContextScope scope = new OperationContextScope(channel))
{
MessageHeader header = MessageHeader.CreateHeader("apikey", address, apikey);
OperationContext.Current.OutgoingMessageHeaders.Add(header);
}
}
but then I have no idea how to retrieve the header on the server side. I'm using a Service authorisation manager and I get the current operating context and try to retrieve the header like this:
public string GetApiKey(OperationContext operationContext)
{
var request = operationContext.RequestContext.RequestMessage;
var prop = (HttpRequestMessageProperty)request.Properties[HttpRequestMessageProperty.Name];
return prop.Headers["apikey"];
}
but there is no apikey header attached there. Also, on debugging when I inspect the operationContext I cant seem to see my apikey header anywhere. Can anyone see where I'm going wrong?
You can add custom header by this way :
using (ChannelFactory<IMyServiceChannel> factory =
new ChannelFactory<IMyServiceChannel>(new NetTcpBinding()))
{
using (IMyServiceChannel proxy = factory.CreateChannel(...))
{
using ( OperationContextScope scope = new OperationContextScope(proxy) )
{
Guid apiKey = Guid.NewGuid();
MessageHeader<Guid> mhg = new MessageHeader<Guid>(apiKey);
MessageHeader untyped = mhg.GetUntypedHeader("apiKey", "ns");
OperationContext.Current.OutgoingMessageHeaders.Add(untyped);
proxy.DoOperation(...);
}
}
}
And service side, you can get header like :
Guid apiKey =
OperationContext.Current.IncomingMessageHeaders.GetHeader<Guid>("apiKey", "ns");
I'm assuming that you trying to consume your service using some Http Protocol based transport (SOAP, REST etc). I'm also assuming that what you want is to authorize the caller using the supplied API key. If both of those conditions apply to your question, you can read on.
I recently had to tackle a similar problem only that I did not pass an API key but a username/password hash combination using some HTTP custom headers. I ultimately solved it by implementing a custom authorization policy that once configured in Web.config hooked nicely into the WCF Pipeline.
The snippet below should be enough to get you started. You probably would have to replace the x-ms-credentials-XXX headers by a single one representing your API key.
internal class RESTAuthorizationPolicy : IAuthorizationPolicy
{
public RESTAuthorizationPolicy()
{
Id = Guid.NewGuid().ToString();
Issuer = ClaimSet.System;
}
public bool Evaluate(EvaluationContext evaluationContext, ref object state)
{
const String HttpRequestKey = "httpRequest";
const String UsernameHeaderKey = "x-ms-credentials-username";
const String PasswordHeaderKey = "x-ms-credentials-password";
const String IdentitiesKey = "Identities";
const String PrincipalKey = "Principal";
// Check if the properties of the context has the identities list
if (evaluationContext.Properties.Count > 0 ||
evaluationContext.Properties.ContainsKey(IdentitiesKey) ||
!OperationContext.Current.IncomingMessageProperties.ContainsKey(HttpRequestKey))
return false;
// get http request
var httpRequest = (HttpRequestMessageProperty)OperationContext.Current.IncomingMessageProperties[HttpRequestKey];
// extract credentials
var username = httpRequest.Headers[UsernameHeaderKey];
var password = httpRequest.Headers[PasswordHeaderKey];
// verify credentials complete
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
return false;
// Get or create the identities list
if (!evaluationContext.Properties.ContainsKey(IdentitiesKey))
evaluationContext.Properties[IdentitiesKey] = new List<IIdentity>();
var identities = (List<IIdentity>) evaluationContext.Properties[IdentitiesKey];
// lookup user
using (var con = ServiceLocator.Current.GetInstance<IDbConnection>())
{
using (var userDao = ServiceLocator.Current.GetDao<IUserDao>(con))
{
var user = userDao.GetUserByUsernamePassword(username, password);
...
Did you take a look at this question: How to add a custom HTTP header to every WCF call? ? It may contain your solution.