Consuming web api with JWT authentication in .NET 7 MAUI? - c#

I have a minimal API .NET 7 installed on an external web server and use JWT for authentication. For testing I created a few endpoints (with authentication and also without) so I can test the web API via Postman. I start Postman from my private machine and access the web address of the API to test everything.
Now everything works as expected. I can log in via Postman, then I get JWT and if I enter JWT in Postman, then I can also access protected endpoint and get the data from the Web API.
Now I have created a desktop application in MAUI .NET 7 and I want to use this web API. Also here the access to unprotected endpoint works as well as logging in with receiving the JWT. Only the last part of the whole thing does not work anymore and that is access to a protected endpoint with the delivery of JWT for which I constantly get the message 401 Unauthorized. If I then put the same JWT into Postman, then the request goes through Posstman and I get the data from Web API!
I have been looking for a solution and have tried all possible code examples from the internet. For example:
var requestMessage = new HttpRequestMessage
{
Method = HttpMethod.Get,
RequestUri = new Uri("http://api.mywebsite.com:64591/secret")
};
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", Token.token);
var response = await _httpClient.SendAsync(requestMessage);
or
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", Token.token);
var RawData = await _httpClient.GetStringAsync("http://api.mywebsite.com:64591/secret2");
In some places I read that there were problems with the change to .NET 6. The solution was new NuGet packages, but since I'm already on .NET 7, I installed the latest versions.
There was also a post suggesting that in the web API you set issuer and audience to false. I did that as well, but to no success.
ValidateIssuer = false,
ValidateAudience = false,
Does anyone have a working code for MAUI native app that consumes minimal API?
EDIT
Following Heretic Monkey's suggestion, I installed Wireshark software and analyzed Network Transfer.
Here is what I found:
the token I receive from Web Api after authentication, I also send in the same form when requesting to an Authorized Endpoint (so received and sent token from client are identical). My conclusion here is that the token is correct.
When the request with the JWT is sent from the client to the server (Web Api), I then get the error message 401. In the log I see below information with the reason Bearer error="invalid_token":
Hypertext Transfer Protocol
HTTP/1.1 401 Unauthorized\r\n
[Expert Info (Chat/Sequence): HTTP/1.1 401 Unauthorized\r\n]
[HTTP/1.1 401 Unauthorized\r\n]
[Severity level: Chat]
[Group: Sequence]
Response Version: HTTP/1.1
Status Code: 401
[Status Code Description: Unauthorized]
Response Phrase: Unauthorized
Transfer Encoding: chunked\r\n
Server: Microsoft-IIS/10.0\r\n
WWW-Authenticate: Bearer error="invalid_token"\r\n
X-Powered-By: ASP.NET\r\n
Date: Sun, 18 Dec 2022 09:07:00 GMT\r\n
\r\n
[HTTP response 1/1]
[Time since request: 0.047969000 seconds]
[Request in frame: 790]
[Request URI: http://api.myserver.com:64591/secret2]
HTTP chunked response
End of chunked encoding
Chunk size: 0 octets
\r\n
File Data: 0 bytes
There are only two error reasons I could think of:
I still have a bug in my minimal API (Web Api) and that is regarding the JWT I get from the client and somehow still need to convert/crimp the JWT maybe!? By the fact that I may use JWT in exactly the form that is sent to client, then it may be that it is wrong and that is why this error message "invalid_token" comes.
the second cause could be NET 7, so an error that occurs not because my code is wrong but because it is implemented incorrectly in NET 7 (is of course not probable but not impossible).
Maybe someone has a suggestion how I can fix this error?
If this doesn't work (i.e. a request to Web Api with JWT authentication), then Web Api is unusable in NET 7 and I really can't imagine that.
So I truly assume that the bug is in my implementation (either server/minimal Api or client MAUI NET 7).
Thanks

The problem was already kind of strange, because even the many tutorials and posts in the form as they are given in Internet will not work. But if you copy generated token out (e.g. from debug mode) and use it in Postman, then everything will work nicely and this is something that confuses you a lot. Fortunately, there are still people who have incredible mind and can detect such inconsistencies. I wouldn't have seen this in 1000 years either :)
See: https://learn.microsoft.com/en-us/answers/questions/1133200/401-unauthorized-consuming-web-api-with-jwt-authen.html

In my case I noticed that
response.Content.ReadAsStringAsync().Result
in .NET MAUI will return "+token+", I have trimmed the quotation mark (") , and it worked with me
using HttpResponseMessage response = await client.PostAsJsonAsync("Login", loginData);
response.EnsureSuccessStatusCode();
string token = response.Content.ReadAsStringAsync().Result;

Related

C# HttpClient POST requests from Azure Function with Authorization tag intended for third-party API are stripped of Headers and Body

UPDATE
I was able to get a working request posted. The third-party API has us sending the Token (which is basically a Guid) as a bearer token. Azure appears to do some sort of pre-validation on this. When I swapped out the GUID with a true randomly generated bearer token, it worked.
I do still wonder if there's a way to disable this check-in Azure. The "bad" Bearer token works for GET requests but fails for POST/PUT requests.
Summary of the Application
We have Azure Functions (i.e., Time Trigger, Orchestrator, Activities) that look for items in an on-prem queue table in SQL and then POST it to a third-party API via JSON.
The third-party API requires an Authorization header with the POST request.
Technical Overview
dotnet core 3.1
azure function runtime ~3
Additional Information
This codebase worked fine during UAT back in April-May of this year. It then sat idle until we rebooted the project a couple of weeks ago.
Outbound requests are not proxied through APIM. They're sent directly to the third-party API
Application Insights is configured for the Azure Function
What works
All of the GET requests. No issues at all.
What doesn't work
POST requests. I proxied the requests to a beeceptor to see exactly what was being received. When the Authorization header is included most of the headers are stripped (I.e., Content-Type, Content-Length) and the Body of the request is blank.
If I removed the Authorization header then all headers and body are received as expected.
Question
I can only assume at this point that some Azure service, pre-flight check, security policy is intercepting the Authorization header thinking it's intended for "itself", but I have absolutely no idea what it could be. I've been on Google now for days.
Simplified Version of Code
using var client = new HttpClient();
client.DefaultRequestHeaders.Clear();
// Request params are dynamic and a helper method builds the full request path
var path = PathBuilder(queueItem.RequestParams, queueItem.Request.UrlPath);
// This can change in code not shown if the request is sending files
var contentType = "application/json";
client.BaseAddress = new Uri(queueItem.Request.Client.BaseApiUrl);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.CacheControl = new CacheControlHeaderValue { NoCache = true };
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", queueItem.Request.Client.AuthToken);
// queueItem.Data is JSON
HttpContent json = new StringContent(queueItem.Data, Encoding.UTF8, contentType);
return await client.PostAsync(path, json);
Also...
I've confirmed the JSON body is valid
The code did work and has remain unchanged
Given all that you’ve tried, it might be a long shot, but have you tried to add the token like:
client.DefaultRequestHeaders.TryAddWithoutValidation(“Authorization”, “bearer token here…”);
and then check whether the try succeeded or not?

Google Identity Toolkit API in C#

Some Background Information
I am building a game in Unity, using C#. Since I am using Firebase and the ready-made Unity Firebase SDK won't work in my case1, I have resorted to interfacing with Firebase's REST API through C#'s HttpClient class (which comes with System.Net.Http).
I am currently struggling with the Firebase Authentication APIs. For those unfamiliar with OAuth APIs, when I call the Sign Up or Sign In endpoints, I get both an ID Token and a Refresh Token. ID Tokens expire; refresh tokens do not. When the ID Token expires, I must call another endpoint, called the Token Exchange, to get a new ID token using my refresh token.
1 There is a rumored Google Identity Toolkit C# library, but the first result returned by that search I found it by leads to a 404 error instead of documentation. 😢
What Does Work
Following the Firebase guides and the underlying Identity Toolkit guides, I have been successful so far interfacing the token exchange endpoint with a cURL command from bash:
curl 'https://securetoken.googleapis.com/v1/token?key=[MY FIREBASE API KEY]' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data 'grant_type=refresh_token&refresh_token=[MY REFRESH TOKEN]'
of course, I replace [MY FIREBASE API KEY] with my Firebase Web API key, and [MY REFRESH TOKEN] with the refresh token returned from the sign in/sign up endpoints.
However, despite my many attempts, I have not been able to replicate this cURL command in C#!
My Failed Attempts
1.
Here's my original code that didn't work.
public async Task<bool> ExchangeToken()
{
FormUrlEncodedContent body = new FormUrlEncodedContent(new Dictionary<string, string>() {
{"grant_type", "refresh_token" },
{"refresh_token", Uri.EscapeDataString(user.refreshToken) }
});
HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Post, "https://securetoken.googleapis.com/v1/token?key=" + apiKey);
message.Content = body;
message.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/x-www-form-urlencoded"));
HttpResponseMessage res = await client.SendAsync(message);
}
Unfortunately, I get this 401 (Unauthorized) error response:
{
"error": {
"code": 401,
"message": "Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",
"status": "UNAUTHENTICATED"
}
}
This is quite strange, considering that I get a 2XX (OK) response from what should be an equivalent cURL command...
2.
Thanks to a very nice website I just discovered, I was able to "convert" my cURL command into C# code. However, this did not work. I got the exact same error as attempt #1.
public async Task<bool> ExchangeToken()
{
using (var request = new HttpRequestMessage(new HttpMethod("POST"), "https://securetoken.googleapis.com/v1/token?key=[I WOULD INSERT MY KEY HERE]"))
{
request.Content = new StringContent("grant_type=refresh_token&refresh_token=[INSERT REFRESH TOKEN HERE]", Encoding.UTF8, "application/x-www-form-urlencoded");
var res = await client.SendAsync(request);
}
}
Possible Leads
All of my other requests to all of the other endpoints work. There are two notable differences: 1) the API is technically not Firebase's, but Google Identity Toolkit's. 2) This is the only endpoint that I'm using that uses a Content-Type header of application/x-www-form-urlencoded instead of application/json.
My Question / TL;DR
How do I interface with the Google Identity Toolkit API's Token Exchange endpoint using C#?
(Though I'm currently using the HttpClient class, I'm totally open to other solutions! They just have to be compatible with Unity3D.)
Thanks in advance!
Google wants you to be using Firebase for auth. While Firebase does not have a .NET client SDK shipped by Google, there are other options.
If you don't use Firebase and perform the requisite tasks with oAuth/OpenID Connect endpoints yourself, that works too. There is a Google .NET SDK that helps you with this...a little bit . The SDK lists Google APIs that are "supported" by the SDK; Google Cloud Identity v1 and v1beta APIs are both on the list. So you could call Google Cloud Identity endpoints via this .NET SDK. You do have to understand the implications of various auth flows whereas in Firebase much of that machinery is abstracted away from you as the app developer.
After playing around with the code a little more, I remembered that I was setting default authorization on my HttpClient:
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", idToken);
This was included for all of the other API calls, as they require authentication with the ID Token. However, this seemed to be confusing the token exchange endpoint; it must 1) look for an authorization header, then 2) use the refresh token supplied in the message contents.
How to fix this issue
Instead of setting the default header on your HttpClient (Which is supposed to be reused for all of your HTTP requests), do the following:
var req = new HttpRequestMessage(/* HttpMethod enum value, endpoint URL/IP */);
req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", user.idToken); //Set the authorization per-request, not globally
var res = await client.SendAsync(req);
Then just simply call the Token Exchange endpoint!
From the Firebase REST API Documentation:
You can refresh a Firebase ID token by issuing an HTTP POST request to the securetoken.googleapis.com endpoint.
https://securetoken.googleapis.com/v1/token?key=[API_KEY]
Just provide a refresh_token parameter in the body (along with the mandatory grant_type with value refresh_token). DO NOT provide an authorization header.
Hope this helps anyone with the same problem!
As an alternative, you can also use https://github.com/google/apis-client-generator to generate the c# client from the discovery docs. I had some naming conflicts when I generated mine, but after resolving those it works.

Webex guest issuer integration with widgets -- Create team web api issue

I am trying to create a Webex team using the following process. But not working. I had created JWT token and exchanged it for access token which I had passed in to the Authorization header in the API call. But I keep getting the error message that I mentioned below. And another fact is I can execute the same API with GET method which returns the number of teams I had.I had followed steps in the webex developer site. But I can't create the team.
Request URL:
https://api.ciscospark.com/v1/teams
Request Method:
POST
The following is the header that I passed to the API
Accept:
application/json
Content-Type:
application/json
Authorization:
Bearer eyJhbGciOiJSUzI1NiJ9.eyJtYWNoaW5lX3R5cGUiOiJhcHB1c2VyIiwiY2x1c3RlciI6IlBGODQiLCJwcml2YXRlIjoiZXlKamRIa2lPaUpLVjFRaUxDSmxibU1pT2lKQk1USTRRMEpETFVoVE1qVTJJaXdpWVd4bklqb2laR2x5SW4wLi4zRnp6Mkh4c2k0TnI4Z24yMk03cm1BLjhtUnRIYUhOWXBkQ2ZydUR1d0s0MnJNMlhZTEZvNHJKN0NTTkJSQ21kZFRnNTdzUzZ1cUVxcjZFakQ0Yk1JOUY5NFluOWx6VGFWcnRHVkpLOGo2djNzX1VBaTJ2Rnk5TDl3Z05SaUlOczB3NU5yTkxLLUx3TDNQdGhiMzJEYzF2TlhGZkRtakFmSjlOZG1QeEdMQVYxWU9EVnhnNE5MbnpHNDczY24zaTJlbWlfdVBzbk9LRTZHYjEwMVBkekdCSVVQaGRwMkY4bVZhd3hzN2pxbUdzVmEtdXJaYXhpN243emZyaXBjUE5HZjNhSURWb011LVkzaXN0ak03U2s1Qno4TXcwNFZueXpGYWt4LWw0WHlfdUVJalpDeXNsNU56TGNTWkVHc2hJRnJsVHZmdzZZU1J4elV0cXJ2Y3hrX09GT01ZeFRsSDVvSXVsSXdXRWpVQ2o5RTBWZVJMNHB2Z3BQYTRkOE1zNHR6Tmh3cmNpRV9Hd0Fob0tmOE9oMEVQZTM4MmdaOW5wNFJvOWRVSnBnRGszR3NhYWx4ZmJubFRmMmt6RWVjQlJCa0NnSEJXRjhUZXNpdWZWV2djWktZX0E1dXh0aTlMSTB0Umk5X3ZsNjJuTy1uMlh5dm1VZlQxUUxLNmthamJwU01renRUOHdLRkxxLVdTVk54TUJlMWRid094Ni1nN0c3bldLZ1J5ZEpEVEdrazMtRW9wbVRURnZhTEk2dkp6S3NGTzlGRUFJVld1ZFd2RWlWWjVXdVN3dS5yS3RJTzZ6aVpFVE1XV3ZhOEVZaElnIiwicmVmZXJlbmNlX2lkIjoiODlkNjdlZDUtOTU5My00YjBjLWI0MGEtMGFjOTE0OTZlZjZkIiwiaXNzIjoiaHR0cHM6XC9cL2lkYnJva2VyLndlYmV4LmNvbVwvaWRiIiwidG9rZW5fdHlwZSI6IkJlYXJlciIsImNsaWVudF9pZCI6IkMzMTE3NzIzYTBhNGM5ODVhOGJkNmRkYTc2Zjc3NjZjNTEzMjRiNmY0MWJmNGZlZjFjMmM4Mjc4NGExZjI5NzVjIiwidXNlcl90eXBlIjoibWFjaGluZSIsInRva2VuX2lkIjoiQWFaM3IwWkRabU1XWXhaVEF0T0dGbU15MDBOR05oTFdKa09EY3RZVFZrTmpjM056STBNamhoTkRkaU9XVXlNbU10TkdOaiIsInVzZXJfbW9kaWZ5X3RpbWVzdGFtcCI6IjIwMTkwNTI3MDQxNzQ4LjEwMloiLCJyZWFsbSI6IjM5ZWQ0NDRmLTU2NGUtNDFlNC05N2VkLWI4MWFmNzM0ZWVkMCIsImNpc191dWlkIjoiNjJkZWU2NjktNDE5OC00N2YzLWE2ODgtNzA4YWQxM2VjNDc3IiwiZXhwaXJ5X3RpbWUiOjE1NTg5NTIyNzA0NTB9.VNUms24YsiwNbU_KN0Y7v1osAilq1MMiIQn6DRnyNitJAgeTLuyrwiYM0JIaox-dQxQKBp897uAj9vksg0ik2-j2MddcRmx4gsVF787uw7VpCIiZNNME346__xddvmmttna7IErBTz84hqZsnvSlmKLpus4lQs_k9I8ZHTsuPJ9FRXPry6ZAmPjDk1uWGGWFXyOSgH-VkDj9hY9Fy_6r7MKIn0YV21dxIKaZVZ6DMo1NSJM68I-90Vv-QbQY3gBeH25ZXDNDDBJg6-woMF2QPk_N8zrL8G1WPRXxGAA_lqGr_qGq65CfOwuqGikpLsRtjhWU4sYDX3xrK918W08CFw
This is the parameter I passed to the API
name=hearingteam
This is the server response
{"message":"The request could not be understood by the server due to malformed syntax.","errors":[{"description":"The request could not be understood by the server due to malformed syntax."}],"trackingId":"ROUTER_5CEB64F0-FC8D-01BB-05C2-7AA62A6905C2"}
The API requires parameters to be passed in JSON format.
Eg : JSON.stringyfy({"name":"hearingteam"});

Why would my REST service .NET clients send every request without authentication headers and then retry it with authentication header?

We happen to run a REST web service with API requiring that clients use Basic authentication. We crafted a set of neat samples in various languages showing how to interface with our service. Now I'm reviewing IIS logs of the service and see that the following pattern happens quite often:
a request comes, gets rejected with HTTP code 401
the same request is resent and succeeds
which looks like the first request is sent without Authorization headers and then the second one is sent with the right headers and succeeds. Most of the time the log record contains "user-agent" which is the same string we planted into our .NET sample.
So I assume the problem is with .NET programs only. The problem is not reproduced with our sample code so I assume the users somehow modified the code or wrote their own from scratch.
We tried contacting the users but apparently they don't want to invest time into research. So it'd be nice to find what the most likely scenario is which leads to this behavior of .NET programs.
Why would they do this? Why would they not attach the headers on the first attempt?
This is the default behavior of HttpClient and HttpWebRequest classes which is exposed the following way.
Note: Below text explains suboptimal behavior causing the problem described in the question. Most likely you should not write your code like this. Instead scroll below to the corrected code
In both cases, instantiate a NetworkCredenatial object and set the username and password in there
var credentials = new NetworkCredential( username, password );
If you use HttpWebRequest - set .Credentials property:
webRequest.Credentials = credentials;
If you use HttpClient - pass the credentials object into HttpClientHandler (altered code from here):
var client = new HttpClient(new HttpClientHandler() { Credentials = credentials })
Then run Fiddler and start the request. You will see the following:
the request is sent without Authorization header
the service replies with HTTP 401 and WWW-Authenticate: Basic realm="UrRealmHere"
the request is resent with proper Authorization header (and succeeds)
This behavior is explained here - the client doesn't know in advance that the service requires Basic and tries to negotiate the authentication protocol (and if the service requires Digest sending Basic headers in open is useless and can compromise the client).
Note: Here suboptimal behavior explanation ends and better approach is explained. Most likely you should use code from below instead of code from above.
For cases when it's known that the service requires Basic that extra request can be eliminated the following way:
Don't set .Credentials, instead add the headers manually using code from here. Encode the username and password:
var encoded = Convert.ToBase64String( Encoding.ASCII.GetBytes(
String.Format( "{0}:{1}", username, password ) ) );
When using HttpWebRequest add it to the headers:
request.Headers.Add( "Authorization", "Basic " + encoded );
and when using HttpClient add it to default headers:
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue( "Basic", encoded );
When you do that the request is sent with the right authorization headers every time. Note that you should not set .Credentials, otherwise if the username or password is wrong the same request will be sent twice both time with the wrong credentials and both times of course yielding HTTP 401.

.NET client calling HTTPS Java web service

I have a HTTPS Java Web Service, I am trying to access the web service with .NET[.NET 2.0 style client / WCF client]. I am getting back this error from WS.
"HTTP Status 401 - This request requires HTTP authentication ()."
How do I find out what kind of security this WS has?
Besides SSL, I have user name and password to send it to WS, I believe this is part of message authentication.
Java client seems like successfully communicating, and it has few interesting lines,
System.setProperty("javax.net.ssl.keyStorePassword", new String(jsseKeyStorePassword));
System.setProperty("javax.net.ssl.trustStorePassword", new String(jsseTrustStorePassword));
----------------------------------------------
BindingProvider bindingProvider = (BindingProvider) port;
bindingProvider.getRequestContext().put(BindingProvider.USERNAME_PROPERTY, username);
bindingProvider.getRequestContext().put(BindingProvider.PASSWORD_PROPERTY, new String(password));
I would appreciate any help.
Long story short,
Two problems I had to solve,
HTTP Status 401 - This request
requires HTTP authentication ()
Changed Authentication schema to Basic in the VS generated customeBinding in app.config
HTTP/1.1 505 HTTP Version Not
Supported
Remove Expect: 100-continue SOAP header. Add this line ServicePointManager.Expect100Continue = false;. For the details of the issue, go here
Long story here http://www.irasenthil.com/2010/10/wcf-client-to-java-web-service.html
It seems as though the Java WS requires a server and root certificate stored in a couple of key stores. It requires knowledge of the passwords to these key stores to obtain the certificates, which seem to be available in the jsseKeyStorePassword and jsseTrustStorePassword variables.
Also, you should be using at least .NET Framework 3.0 in order to use Windows Communication Foundation.

Categories

Resources