How to call a web api that has Oauth 2.0 - c#

Hi so we have an external web api we want to call to get data out. It is using oauth 2.0. Can somebody please explain how we would go about doing this in .NET either vb.net or c#. I have in the past created api, however this one seems very complicated. Firstly you have to be signed into their oauth web page they have which generates some cookies, using these cookies by syncing them up in postman we can see the data, however we need this to be within our .net app. Can somebody please help how we go about this. Some code would be useful.
Thanks

This is how usually OAuth 2 authentication works.
You basically log in with username and password (optional second factor) and then you receive a token, the so called Json Web Token or JWT (it holds encrypted information about your user, your access roles or groups you are member of as well as some timestamp which is the expiration time of the token).
In every subsequent request you make to the server, you pass this token in the request header (or in your case as cookie).
Example code:
Login request:
HttpRequestMessage httpRequest = new HttpRequestMessage(HttpMethod.Post, new Uri(_baseUrl, "token"));
string body = JsonConvert.SerializeObject(new
{
Username = _userName,
Password = _password,
secondFactor = secondFactor
});
httpRequest.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.SendAsync(httpRequest);
var responseContent = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode)
{
TokenResult r = JsonConvert.DeserializeObject<TokenResult>(responseContent);
if (!string.IsNullOrWhiteSpace(r.token))
{
_token = r.token;
_tokenValidity = r.expirationDate;
_refreshToken = r.refreshToken;
_refreshTokenValidity = r.refreshTokenExpirationDate;
return _token;
}
else
{
throw new Exception($"Failed to get token from server.\r\n{responseContent}");
}
}
Now you use the _token in subsequent requests in the request header:
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _token);
using HttpResponseMessage response = await client.GetAsync(new Uri(_baseUrl, relativePath));
if (response.IsSuccessStatusCode)
{
using var stream = await response.Content.ReadAsStreamAsync();
stream.Position = 0;
using var reader = new StreamReader(stream);
reader.ReadToEnd();
}
Please note, that usually the token has a certain lifetime after which it is basically useless. Some APIs offer a refresh token with which a new token can be requested without the user having to log in again with username and password, but that's beyond the scope of this question.
You said you have to use the token as cookie? Well there are APIs which work like this but personally I've never seen one like this, which is why I can't you help very much, but it shouldn't be much more than putting the token you got into a cookie with a certain name.
Hope this helps.

Not sure what you are asking. I have a controller code where I use web api call to authenticate user. You can use your own model to pass the data. If your web api expects token for request, then you might have to get the token first to give a call to any method. Hope this helps.
OktaUserDetailsModel Model = new OktaUserDetailsModel();
Model.username = model.UserName;
Model.password = model.Password;
using (var httpClient = new HttpClient())
{
HttpContent inputContent = new StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(Model), System.Text.Encoding.UTF8, "application/json");
HttpResponseMessage response = httpClient.PostAsync(ConfigurationManager.AppSettings["OktaAPIuri"], inputContent).Result;
if (response.IsSuccessStatusCode)
{
string strResponse = (new JavaScriptSerializer()).Deserialize<string>(response.Content.ReadAsStringAsync().Result);
if (strResponse.ToUpper() == "TRUE")
return OktaSingleSignOnLogin(astrReturnUrl, model.UserName);
else
return ErrorPage();
}
else
{
return ErrorPage();
}
}

Related

OpenID Connect Authentication Successful. Now what?

I'm writing a windows service in C# that needs to authenticate with an API and make some calls. I'm able to authenticate successfully with this API I'm talking to, but I can't seem to figure out how to use the response. The response looks like this:
{"access_token":"Es-Zjs_LI0tcXyLe3aEfgKPNLHN7CwyUhTss-cTld1A","expires_in":1800,"token_type":"Bearer","scope":"example","auth_state":1,"company":"examplecompany"}
I can get the access token out of that string if I want, but no matter how I pass it to a request, I get a 401 error. This is what my current iteration looks like:
string results = "";
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer",token);
var request = new HttpRequestMessage
{
Method = HttpMethod.Get,
RequestUri = new Uri("https://example.ca/endpoint"),
//Headers =
//{
// { "authorization", "Bearer"},
//},
};
try
{
using (var response = await client.SendAsync(request))
{
response.EnsureSuccessStatusCode();
var body = await response.Content.ReadAsStringAsync();
results = body;
}
}
catch (Exception ex)
{
results = "ERROR: " + ex.Message;
}
return results;
Where "token" is the string "Es-Zjs_LI0tcXyLe3aEfgKPNLHN7CwyUhTss-cTld1A" in this example. I had previously tried stitching the access_token value as a string to the "Bearer" string in the commented out section in the middle there. What am I doing wrong? Do I need to make a JwtSecurityToken out of the response?
AuthenticationResult authResult = await daemonClient.AcquireTokenForClient(new[] { MSGraphScope })
.ExecuteAsync();
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
I've used the authResult.AccessToken. Not sure if it works in your scenario. The return type in my case was Microsoft.Identity.Client.AuthenticationResult type when I retrieved the token for a Graph API that I was using.
Be aware that the token you have received ("Es-Zjs_LI0tcXyLe3aEfgKPNLHN7CwyUhTss-cTld1A") is a reference token and not a JWT-token. Make sure your API accepts that type of token.
To use the token effectively in production then I would consider using the various helper methods found in the IdentityModel library and especially the Worker application helpers.
While I understand it's largely situational depending on what API you're trying to connect to, for me the solution was to use this method to pass in the authentication token:
request.Headers.TryAddWithoutValidation("Authorization", "Bearer " + token);

ASP.NET Core JWT Token not working via HttpClient

I'm new to JWT authentication and I've configured my ASP.NET Core 3 API for token authentication using the tutorial here:
https://chrissainty.com/securing-your-blazor-apps-authentication-with-clientside-blazor-using-webapi-aspnet-core-identity/
I've added the [Authorize] attribute to the controller, and using Postman I can login and get a token, and use the token via Postman to retrieve the data from the controller - all works as expected to here.
My issue is when I login using the C# HttpClient via a Xamarin app, and then I use the the token that is sent back I get a 401 error accessing the controller either via Postman or HttpClient.
The code I'm using is as follows:
var client = new HttpClient();
var model = new JwtLoginModel()
{
Email = email,
Password = password,
RememberMe = false
};
var json = JsonConvert.SerializeObject(model);
HttpContent httpContent = new StringContent(json);
var response = await client.PostAsync(Constants.BaseApiAddress + "api/Login", new StringContent(json, Encoding.UTF8, "application/json"));
var content = await response.Content.ReadAsStringAsync();
JObject jwtDynamic = JsonConvert.DeserializeObject<dynamic>(content);
var accessToken = jwtDynamic.Value<string>("token");
Using the returned accessToken via Postman (and HttpClient), both return a 401 error.
Can anyone suggest what I'm doing wrong?
Thanks all for the help
In the end I decided it was easier to go with Identity Server 4 implementation which is working perfectly

Resetting a user's password using Microsoft Graph

I'm trying to write a web portal that users can use to reset their own Azure AD password. Because of the requirements of my client, the Azure AD SSPR is not an option.
To achieve this I'm using Microsoft Graph. According to the documentation, it is possible to reset a users password using Microsoft Graph if you have User.ReadWrite.All or Directory.AccessAsUser.All permissions.
Then the permissions documentation, the remarks it states that even if you have the Directory.ReadWrite.All permissions you won't be able to reset a users password.
I've done a test to see if this will work but I get an HTTP 403 Forbidden response.
The code I'm using is:
string ResourceUrl = "https://graph.windows.net/";
string AuthorityUrl = "https://login.microsoftonline.com/companyxxx.onmicrosoft.com/oauth2/authorize/";
//Create a user password cradentials.
var credential = new Microsoft.IdentityModel
.Clients
.ActiveDirectory
.UserPasswordCredential("username#xxxx.com", "passwordxxx");
// Authenticate using created credentials
var authenticationContext = new AuthenticationContext(AuthorityUrl);
var authenticationResult = authenticationContext
.AcquireTokenAsync(ResourceUrl, "xxxxxxxx-3017-4833-9923-30d05726b32f", credential)
.Result;
string jwtToken = authenticationResult.AccessToken;
var cred = new Microsoft.Rest
.TokenCredentials(authenticationResult.AccessToken, "Bearer");
HttpClient client = new HttpClient();
var queryString = HttpUtility.ParseQueryString(string.Empty);
queryString["api-version"] = "1.6";
client.DefaultRequestHeaders
.Accept
.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", jwtToken);
var uri = "https://graph.windows.net/xxxxxxxx-18fe-xxxx-bb90-d62195600495/users/xxxxxxxx-aa58-4329-xxxx-b39af07325ee?" + queryString;
//var content = new StringContent("{\"passwordProfile\": {\"password\": \"Test123456\", \"forceChangePasswordNextLogin\": true }}");
var response = client.PatchAsync(new Uri(uri), content, jwtToken);
The PatchAsync method is an extension method as below:
public static class HttpClientExtensions
{
public static async Task<HttpResponseMessage> PatchAsync(this HttpClient client,
Uri requestUri, HttpContent iContent, string jwtToken)
{
var method = new HttpMethod("PATCH");
var request = new HttpRequestMessage(method, requestUri)
{
Content = iContent,
};
request.Content.Headers.ContentType =
new MediaTypeHeaderValue("application/json");
request.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", jwtToken);
HttpResponseMessage response = new HttpResponseMessage();
try
{
response = await client.SendAsync(request);
}
catch (TaskCanceledException e)
{
Console.WriteLine("ERROR: " + e.ToString());
}
return response;
}
}
Could someone please clarify if this is possible using the credentials grant flow with a username and password for authentication. If so how do I achieve this?
You're mixing up Microsoft Graph and Azure AD Graph API. These are two different APIs and calls to one are not interchangeable with the other.
You are correct in that you need to use the Directory.AccessAsUser.All scope for this activity. This scope allows the API to do anything to the AAD that the signed in user would be able to do themselves (i.e. change their own password).
Once you have a valid access_token for the user with Directory.AccessAsUser.All permission, you can update the user's passwordProfile:
PATCH https://graph.microsoft.com/v1.0/me
Content-type: application/json
{
"passwordProfile" : {
"forceChangePasswordNextSignIn": true,
"password": "password-value"
}
}

cURL and .Net (c#) API Token (Token is Invalid)

I've searched all over the internet and stackoverflow...can't really figure out if it's a code problem or the API is not valid anymore with some tokens...
I've looked through this Stackoverflow thread and tried it out and nothing. The only thing that returns is "TOKEN is invalid".
I'm trying to list the users from this event which has an API here.
This is my code:
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("https://services.sapo.pt/Codebits/user/1/");
// Add an Accept header for JSON format.
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = client.GetAsync("&token=NTUyI0FmY29zdGEjYW5kcmVjb3N0NEBnbWFpbC5jb20jZDkwMDdhNWUxOTNiN2VhMzcwMWJjNjI3MjMwMmI2YTQjMTQzMjU2NDU1NSNhMTc3YTdkZjNlYzk2MTg3ZjZmNzk1NTc3YjEwMDExOTcwNDE1ZTZj").Result;
HttpContent responseContent = response.Content;
// //// Get the stream of the content.
using (var reader = new StreamReader(await responseContent.ReadAsStreamAsync()))
{
// // return (await reader.ReadToEndAsync());
if (response.IsSuccessStatusCode)
{
tb1.Text = await reader.ReadToEndAsync();
}
else
{
tb1.Text = "x";
}
}
Since i'm kinda noob, I hope and think it is my problem but I just can't figure it out.
Thanks in advance!
What part of "TOKEN is invalid" is unclear?
Their API help page mentions:
AUTHENTICATION AND USERS
AUTH TOKEN
https://services.sapo.pt/Codebits/gettoken?user=wookiee#sapo.pt&password=grumph
Get the auth token. Will be used as a &token= argument with all the methods that require authentication. Please be aware that tokens may expire (due to timeout, IP change or others). The correct procedure for an app is to ask for a new token if the one it has fails in any of the methods that require authentication.
So when a call fails with "TOKEN is invalid", first make a gettoken call to obtain a valid token.

ThinkTecture Identity Model Get User based on Token

I am using the sweet Indentity Model library from Thinktecture. I looked high and low and am probably about to ask a dumb question. Basically, we have a Web Forms application which I am adding ASP.net Web api capabilities to it. I installed the Identity code samples and gleened what I think I needed to get it working for session token I am able to get a token and send back on future calls but in my API controller the user object does not seem to be set. I had assumed the framework would interrogate the token if provided and automatically set the Principal based on the token data and mark principal as authenticated? Is this an invalid assumption and is this something that I must set myself?
Basically I am using the token for Authorization and Authentication but need to pull off UserID from the token/session for additional business rules.
Again if dumb question feel free to haze me. I am testing using Unit Test if that has any impact on any recommendations.
Code from Unit Test:
1) Get Token, successfully validates user using our business logic and returns token successfully:
private string GetToken(string username, string password)
{
Uri _baseAddress = new Uri(Thinktecture.Samples.Constants.WebHostBaseAddress);
var client = new HttpClient { BaseAddress = _baseAddress };
client.SetBasicAuthentication(username, password);
var response = client.GetAsync("token").Result;
response.EnsureSuccessStatusCode();
var tokenResponse = response.Content.ReadAsStringAsync().Result;
var json = JObject.Parse(tokenResponse);
var token = json["access_token"].ToString();
var expiresIn = int.Parse(json["expires_in"].ToString());
var expiration = DateTime.UtcNow.AddSeconds(expiresIn);
return token;
}
2) HTTP HELPER METHOD:
private async Task<HttpResponseMessage> Post<T>(string path, T data, string Token)
{
//var handler = new HttpClientHandler {};
//using (var client = new HttpClient(handler))
using (var client = new HttpClient())
{
if (!String.IsNullOrEmpty(Token))
{
client.SetToken("Session", Token);
//client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Session", Token);
}
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.BaseAddress = new Uri("http://localhost/");
var response = await client.PostAsJsonAsync(path, data);
response.EnsureSuccessStatusCode(); // Throw on error code.
return response;
}
}
3) Calling Code:
string Token = GetToken("UserName", "Password");
removed proprietry code here....
Task<HttpResponseMessage> result = Post<GetCustomerDetailsRequest>("api/Account/GetCustomerProfile", GetCustomerDetailsRequest, Token);

Categories

Resources