asp.net core webapp - OAUTH2 AUTHORIZATION - 401 Error - bentley-itwin

i want to use the itwin api and i created a little asp.net core 3.1 webapp.
The Authorization endpoint: https://ims.bentley.com/connect/authorize works fine and i get a code.
Now i want to use the code to get the access token with this backend method:
string url = "https://ims.bentley.com/connect/token";
string clientID = "webapp-XXXXXXXXXXXXXXXXXXXXXX";
string secretID = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
//string svcCredentials = Convert.ToBase64String(ASCIIEncoding.ASCII.GetBytes(clientID + ":" + secretID));
//string svcCredentials = Convert.ToBase64String(System.Text.Encoding.GetEncoding("ISO-8859-1").GetBytes(clientID + ":" + secretID));
string svcCredentials = Convert.ToBase64String(Encoding.UTF8.GetBytes(clientID + ":" + secretID));
var nvc = new List<KeyValuePair<string, string>>();
nvc.Add(new KeyValuePair<string, string>("grant_type", "authorization_code"));
nvc.Add(new KeyValuePair<string, string>("code", code));
nvc.Add(new KeyValuePair<string, string>("redirect_uri", "http://localhost:44343/signin-callback"));
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", svcCredentials);
HttpResponseMessage response = client.PostAsync(url, new FormUrlEncodedContent(nvc)).Result;
var token = response.Content.ReadAsStringAsync().Result;
}`
Everthing works fine but i get an 401 Error. Use i the wrong BASE64 encoding?
thank you for help.

I suggest trying the following:
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", svcCredentials);
Based on the documentation for AuthenticationHeaderValue the first parameter is the scheme and we use Bearer.

You need to url-encode client_id and client_secret before using them for Basic authentication, as described in documentation
The client must authenticate using the HTTP Basic method and provide the url-encoded clientId and the clientSecret
This is also stated in OAuth specification
The client identifier is encoded using the
"application/x-www-form-urlencoded" encoding algorithm per
Appendix B, and the encoded value is used as the username; the client
password is encoded using the same algorithm and used as the
password.

Related

Migrating a SOAP client from to .NET5 that uses WSE and Microsoft.Web.Services2

I am migrating a .NetFramework application that access a DMS system over SOAP
The working implementation involves some generated code where it was necessary to change the base class to Microsoft.Web.Services2.WebServiceClientProtocol in order for the security headers to be correctly built.
public partial class MyService: Microsoft.Web.Services2.WebServicesClientProtocol
The following code successfully calls the WsSearchDmsDocument
var token = new UsernameToken(DmsUsername, DmsPassword, PasswordOption.SendHashed);
var client = new MyService() {Url = ReinsUrl};
SoapContext requestContext = client.RequestSoapContext;
requestContext.Security.Timestamp.TtlInSeconds = 60;
requestContext.Security.Tokens.Add(token);
var myRequest = new Request();
var response = client.WsSearchDmsDocument(request);
Which sends the username/password security header looking like and returns the expected response
<wsse:UsernameToken wsu:Id='UsernameToken-238be95be3bf445fb8534666a7a8693c'>
<wsse:Username>***login***</wsse:Username>
<wsse:Password Type='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-usernametoken-profile-1.0#PasswordDigest'>***Base64 (SHA-1 (nonce + created + password) )***</wsse:Password>
<wsse:Nonce EncodingType='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soapmessage-security-1.0#Base64Binary'>***Base64 nonce***</wsse:Nonce>
<wsu:Created>2019-09-06T12:09:15.604Z</wsu:Created>
</wsse:UsernameToken>
In .Net5
I modified the MyService class in the following way
internal partial class MyService: System.ServiceModel.ClientBase<MyReinsServices>, MyReinsServices
Then I try to call the service
var token = new UsernameToken(DmsUsername, DmsPassword, PasswordOption.SendHashed);
MyService client = new MyService(ReinsServicesClient.EndpointConfiguration.ReinsServicesSoap11, ReinsUrl);
UserNamePasswordClientCredential credential = client.ClientCredentials.UserName;
credential.UserName = DmsUsername;
credential.Password = DmsPassword;
var myRequest = new Request();
var response = client.WsSearchDmsDocument(request);
But this fails with
com.sun.xml.wss.XWSSecurityException:
Message does not conform to configured policy [ AuthenticationTokenPolicy(S) ]:
No Security Header found;
nested exception is com.sun.xml.wss.XWSSecurityException:
com.sun.xml.wss.XWSSecurityException:
Message does not conform to configured policy [ AuthenticationTokenPolicy(S) ]:
No Security Header found
This is a similar error to what I was getting in the .Net Framework version before I used Microsoft.Web.Services2.WebServicesClientProtocol
I think I am very close to a solution but no matter what client.ClientCredentials I take it does not satisfy the security header needed by this particular SOAP service
EDIT
I am able to use SoapUI to call this service. I have to set in WS-Security setting a username and password with PasswordDigest and adding this as a Basic Auth to the outgoing WSS. I then copy the resulting SOAP Envelope into Soap.txt and try to send this via .NET5 in the following code
string soap = File.ReadAllText("soup.txt");
XmlDocument document = new XmlDocument();
document.LoadXml(soap); //loading soap message as string
XmlNamespaceManager manager = new XmlNamespaceManager(document.NameTable);
manager.AddNamespace("reins", "http://scor.com/dms-reins-webservices/schemas/2.0/reins");
manager.AddNamespace("wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
manager.AddNamespace("soapenv", "http://schemas.xmlsoap.org/soap/envelope/");
manager.AddNamespace("wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
//Build the token
RNGCryptoServiceProvider Generator = new RNGCryptoServiceProvider();
var _nonce = new byte[16];
Generator.GetBytes(_nonce);
string nonce = Convert.ToBase64String(_nonce);
var created = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ssZ");
string payLoad = nonce + created + DmsPassword;
byte[] payLoadBytes = System.Text.Encoding.UTF8.GetBytes(payLoad);
SHA1 sha = new SHA1CryptoServiceProvider();
var tokenBytes = sha.ComputeHash(payLoadBytes);
string token = Convert.ToBase64String(tokenBytes);
document.SelectSingleNode("//soapenv:Envelope/soapenv:Header/wsse:Security/wsse:UsernameToken/wsu:Created", manager).InnerText = created;
document.SelectSingleNode("//soapenv:Envelope/soapenv:Header/wsse:Security/wsse:UsernameToken/wsse:Password", manager).InnerText = token;
document.SelectSingleNode("//soapenv:Envelope/soapenv:Header/wsse:Security/wsse:UsernameToken/wsse:Nonce", manager).InnerText = nonce;
var httpClient = new HttpClient();
httpClient.DefaultRequestVersion = HttpVersion.Version11;
HttpResponseMessage response;
var soapMessage = new HttpRequestMessage
{
Method = HttpMethod.Post,
RequestUri = new Uri(url),
Content = new StringContent(s, Encoding.UTF8, MediaTypeNames.Text.Xml),
};
soapMessage.Headers.Add("Accept", "text/xml");
soapMessage.Headers.Add("Accept-Encoding", "gzip,deflate");
soapMessage.Headers.Add("SOAPAction", "");
soapMessage.Headers.Add("Connection", "Keep-Alive");
soapMessage.Headers.Add("User-Agent", "Apache-HttpClient/4.5.5 (Java/12.0.1)");
response = httpClient.Send(soapMessage);
When I make the request via SoapUi I get a StatusCode 200 and the data I want. When I use the above code that should perform the exact same operation I get an StatusCode 500 Internal Server error.
Any suggestions how to solve either the first part of this or to get this hack to work using .NET5 or .NET6 would be welcome

Pass the username and password in Authorization headers for OData

How to pass the username and password in Authorization headers for OData URL in Asp.net core 2.2
Use an HTTPClient where you can set the specific header value for authorization:
string username = "bbb";
string password = "abc";
string url = "https://yourOData.com"
HttpClient httpClient = new HttpClient();
httpClient .DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Basic",
Convert.ToBase64String(ASCIIEncoding.ASCII.GetBytes($"{username}:{password}")));
HttpResponseMessage response = await HttpClient.GetAsync(url);
If you got that one working, take a look at https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/ and refactor your code to not instantiate HttpClient in the way I did.

Using httpClient.postasync for web api calls .netcore

I am new to .netcore, I am working on web api that are running on docker container and while using postman the web api's are working really fine outputting the results. I want to make a program in .netcore calling the webapi endpoints and getting the response and using that particular response in other endpoints with MVC.
The explanation is given below.
The default username and password for admin is default set for example username:admin , password: helloworld
. The first time admin login the api requires a new personal password as shown in the Postman figure below.
The login api is: localhost://..../v1/users/login
The first question is How to give the values in Authorization->BasicAuth using .netcore.
The body of the api looks like the figure below.
After setting the new_password the response of the api is a token as given below.
The particular token is then use in the Environment to create user. The image for more clear problem is given below.
Lastly, the token then used to make other API calls such as creating a user.
API: https://localhost/..../v1/users
The image is below.
As a newbie in .netcore language, I am really struggling to do this kind of API calls, as most of the tutorials I tried are generating their own token from API, but here I just want to take the response token and save it and then use it in other API calls.
The StackOverflow community's support was always really handy for me.
The Code I'm trying is given below.
**Controller**
public class Login_AdminController : ControllerBase
{
[Route("/loginAdmin")]
[HttpPost]
public async Task<string> LoginAdminAsync([FromBody] dynamic content)
{
LoginAdmin L = new LoginAdmin();
var client = new HttpClient();
client.BaseAddress = new Uri("https://localhost:9090");
var request = new HttpRequestMessage(HttpMethod.Post, "/v1/users/login");
var byteArray = new UTF8Encoding().GetBytes($"<{L.username}:{L.df_Password}>");
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));
var formData = new List<KeyValuePair<string, string>>();
formData.Add(new KeyValuePair<string, string>("new_password", "helloWorld123!"));
request.Content = new FormUrlEncodedContent(formData);
var response = await client.SendAsync(request);
Console.WriteLine(response);
return content;
}
}
}
***Model***
public class LoginAdmin
{
public string username = "admin";
public string df_Password = "secret";
public string new_Password { get; set; }
}
Thank you.
Do you want to get token from response? If yes. Try this:
var client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:12345/Api");
var request = new HttpRequestMessage(HttpMethod.Post, "/token");
var keyValues = new List<KeyValuePair<string, string>>();
keyValues.Add(new KeyValuePair<string, string>("username", "yourusername"));
keyValues.Add(new KeyValuePair<string, string>("password", "yourpassword"));
request.Content = new FormUrlEncodedContent(keyValues);
var response = client.SendAsync(request).Result;
return response.Content.ReadAsStringAsync().Result;
Authorization is handled via the Authorization request header, which will include a token of some sort, prefixed by the scheme. What you're talking about here isn't really basic auth. With that, you literally pass the username and pass in the Authorization header with each request. What you're doing is just authenticating once to get an auth token, and then using that auth token to authorize further requests. In that scenario, you should really be posting the username and pass in the request body. Then, you'd do bearer auth with the token for the other requests, using the Authorization header. Still, to cover both bases:
Basic Auth
var token = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{password}"));
request.Headers.Add("Authorization", $"Basic {token}");
Bearer Auth
request.Headers.Add("Authorization", $"Bearer {token}");
// where `token` is what was returned from your auth endpoint
FWIW, List<KeyValuePair<string, string>> is just Dictionary<string, string>. It's better to use the real type. Then, you can just do formData.Add("new_password", "helloWorld123!") instead of formData.Add(new KeyValuePair<string, string>("new_password", "helloWorld123!"))

NTLM Auth with RestSharp

I am attempting to create some tests using RestSharp for a project I am working on.
This project uses Single Sign-on NTLM Authentication.
I am attemping to use a NTLMAuthenticator but my getUser request is always failing. I am not positive what URL to put in for the CredentialCache, the project or the SSO Id Provider.
SharedRequests shared = new SharedRequests();
var credential = new CredentialCache
{
{
new Uri("project or ID Provider URL or something else?"),
"NTLM",
new NetworkCredential("doamin\Username", "Password")
}
};
RestClient client = new RestClient();
client.BaseUrl=new Uri("projectURL");
client.Authenticator = new NtlmAuthenticator(credential);
client.PreAuthenticate = true;
RestRequest request = shared.GetCurrentUser();
IRestResponse response = client.Execute(request);
my response always gets a 500 error which is what is expected when no auth cookies are present.

Call authorized Web API using WebClient

After hours of searching the internet I decided to ask you guys for a little help.
I've written an Web API with couple of simple get/post methods. I'm using Individual user accounts authentication method.
Using the HttpClient I've managed to successfully call every AUTHORIZED get and post method as well as the /token endpoint used for generating authorization token.
The problem is that I must call these methods inside .NET Framework 3.5 project. So I've tried using WebClient to do this because I read that the HttpClient is not supported in .NET Framework 3.5.
GetAPIToken() METHOD generates Bearer token and it works.
private static string GetAPIToken(string userName, string password, string apiBaseUri)
{
using (WebClient client = new WebClient())
{
client.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
var response = client.UploadString(apiBaseUri + "/Token", "POST", "grant_type=password&username=" + userName + "&password=" + password);
var jObject = JObject.Parse(response);
return jObject.GetValue("access_token").ToString();
}
}
This GET method works when I remove [Authorize] attribute from the Web API but I can't make it work when authorized.
//GET ODRAĐENI POSTUPCI
private static string GetOdradjeniPostupci(int Id, string token)
{
string apiBaseUri = "http://localhost:60511/";
string serviceUrl = apiBaseUri + "api/ZOdradjeniPostupci";
using (WebClient client = new WebClient())
{
client.Headers.Clear();
client.Headers.Add("Content-Type", "application/json");
client.Headers.Add("Authorization", "Bearer " + token);
var response = client.DownloadString(serviceUrl + "/GetZOdradjeniPostupci?cZdrUst=" + Id.ToString());
return response;
}
}
I get error 401 unathorized no matter what I try. (Different combinations from the internet regarding Authorization header).
Hope you could give me any advice on how to solve this.
I would appreciate it a lot.
Thanks.