Basic authorization in WEB API C# using HttpWebRequest - c#

I have looked up all the questions regarding basic authorization in web api using httpweb request and none of them solved my problem. I have a web api (written in C#) and I want to establish basic authorization for the api. I also have a web page that I am using to call the api. However, it keeps returning "(401)Unauthorized" and I don't know what I'm doing wrong. Right now I'm using the username and password in the code but I want there to be a pop up asking for credentials.
This is the code for my web page, calling the api:
string url = String.Format("http://example.com");
HttpWebRequest requestObj = (HttpWebRequest)WebRequest.Create(url);
requestObj.Method = "Get";
requestObj.Credentials = new NetworkCredential("testing", "123456");
HttpWebResponse responseObj = null;
responseObj = (HttpWebResponse)requestObj.GetResponse();
string strresult = null;
using (Stream stream = responseObj.GetResponseStream())
{
StreamReader sr = new StreamReader(stream);
strresult = sr.ReadToEnd();
sr.Close();
}
In my api, I opened a class called BasicAuthenticationAttribute and wrote this:
public class BasicAuthenticationAttribute : AuthorizationFilterAttribute
{
public static bool IsAuthorizedUser(string Username, string Password)
{
return Username == "testing" && Password == "123456";
}
public override void OnAuthorization(HttpActionContext actionContext)
{
base.OnAuthorization(actionContext);
if (actionContext.Request.Headers.Authorization != null)
{
var authToken = actionContext.Request.Headers.Authorization.Parameter;
var decodeauthToken = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(authToken));
var arrUserNameandPassword = decodeauthToken.Split(':');
if (IsAuthorizedUser(arrUserNameandPassword[0], arrUserNameandPassword[1]))
{
Thread.CurrentPrincipal = new GenericPrincipal(new GenericIdentity(arrUserNameandPassword[0]), null);
}
else
{
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
}
}
else
{
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
}
}
}
And in my controller, I have this:
[BasicAuthentication]
public class EventsController : ApiController
{
}
This is the error I'm receiving:
The remote server returned an error: (401) Unauthorized.

Try adding credentials to credential cache with type of authorization and instead of using credentials directly use cache.
string url = String.Format("http://example.com");
CredentialCache credentialCache = new CredentialCache();
credentialCache.Add(new Uri(url), "Basic", new NetworkCredential("testing", "123456"));
HttpWebRequest requestObj = (HttpWebRequest)WebRequest.Create(url);
requestObj.PreAuthenticate = true;
requestObj.Method = "Get";
requestObj.Credentials = **credentialCache**;
HttpWebResponse responseObj = null;
responseObj = (HttpWebResponse)requestObj.GetResponse();
string strresult = null;
using (Stream stream = responseObj.GetResponseStream())
{
StreamReader sr = new StreamReader(stream);
strresult = sr.ReadToEnd();
sr.Close();
}

As mentioned in the messages above, I believe the issue is because you are not removing "Basic" from the authentication header. https://learn.microsoft.com/en-us/aspnet/web-api/overview/security/basic-authentication
What I'd do...
string authToken = actionContext.Request.Headers.Authorization.Parameter;
authToken = authToken.Replace("Basic", string.Empty);
var decodeauthToken = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(authToken));
And try this when you split. About the only thing different in your implementation than what I've done multiple times.
var arrUserNameandPassword = decodeauthToken.Split(':'c);

Related

Calling WEB API with basic authentication in C#

I have a working WEB API that I wrote, and I added basic authentication to the API (username is "testing", password is "123456"). However, when trying to call that API from my web form, I keep getting the "(401) Unauthorized" message. What should I change in the web code to call the API successfully?
string url = String.Format("http://example.com"); //here I have the correct url for my API
HttpWebRequest requestObj = (HttpWebRequest)WebRequest.Create(url);
requestObj.Method = "Get";
requestObj.PreAuthenticate = true;
requestObj.Credentials = new NetworkCredential("testing", "123456");
HttpWebResponse responseObj = null;
responseObj = (HttpWebResponse)requestObj.GetResponse();
string strresult = null;
using (Stream stream = responseObj.GetResponseStream())
{
StreamReader sr = new StreamReader(stream);
strresult = sr.ReadToEnd();
sr.Close();
}
This is what my API searches for in terms of authentication:
actionContext.Request.Headers.Authorization.Parameter
Should I be adding a header instead of NetworkCredential or is it the same thing?
This should help:
HttpMessageHandler handler = new HttpClientHandler()
{
};
var httpClient = new HttpClient(handler)
{
BaseAddress = new Uri(url),
Timeout = new TimeSpan(0, 2, 0)
};
httpClient.DefaultRequestHeaders.Add("ContentType", "application/json");
//This is the key section you were missing
var plainTextBytes = System.Text.Encoding.UTF8.GetBytes("testing:123456");
string val = System.Convert.ToBase64String(plainTextBytes);
httpClient.DefaultRequestHeaders.Add("Authorization", "Basic " + val);
HttpResponseMessage response = httpClient.GetAsync(url).Result;
string content = string.Empty;
using (StreamReader stream = new StreamReader(response.Content.ReadAsStreamAsync().Result, System.Text.Encoding.GetEncoding(_encoding)))
{
content = stream.ReadToEnd();
}
This is the line I needed:
requestObj.Headers["Authorization"] = "Basic " + Convert.ToBase64String(Encoding.Default.GetBytes("username:password"));
I just found out that with .NET Core 3.1 you could do it like this:
HttpRequestMessage request = new HttpRequestMessage(
HttpMethod.Post,
"your-api-url-here");
request.Headers.Authorization = new BasicAuthenticationHeaderValue(username, password);
I think your API might need a header being added to it (if you haven't done so already). Take a look at this article:
https://en.wikipedia.org/wiki/Basic_access_authentication#Client_side
But essentially, your API will need an Authorization header added to it. The Authorization key will contain the word Basic followed by a space, then the username and password encrypted using Base64. So in your instance, testing:123456 would be encrypted using base64 as dGVzdGluZzoxMjM0NTY=. So the header record will look like this:
Authorization: Basic dGVzdGluZzoxMjM0NTY=
(Basic Authentication) Here is the other solution to call Authenticated API
class Program
{
static void Main(string[] args)
{
BaseClient clientbase = new BaseClient("https://website.com/api/v2/", "username", "password");
BaseResponse response = new BaseResponse();
BaseResponse response = clientbase.GetCallV2Async("Candidate").Result;
}
public async Task<BaseResponse> GetCallAsync(string endpoint)
{
try
{
HttpResponseMessage response = await client.GetAsync(endpoint + "/").ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
baseresponse.ResponseMessage = await response.Content.ReadAsStringAsync();
baseresponse.StatusCode = (int)response.StatusCode;
}
else
{
baseresponse.ResponseMessage = await response.Content.ReadAsStringAsync();
baseresponse.StatusCode = (int)response.StatusCode;
}
return baseresponse;
}
catch (Exception ex)
{
baseresponse.StatusCode = 0;
baseresponse.ResponseMessage = (ex.Message ?? ex.InnerException.ToString());
}
return baseresponse;
}
}
public class BaseResponse
{
public int StatusCode { get; set; }
public string ResponseMessage { get; set; }
}
public class BaseClient
{
readonly HttpClient client;
readonly BaseResponse baseresponse;
public BaseClient(string baseAddress, string username, string password)
{
HttpClientHandler handler = new HttpClientHandler()
{
Proxy = new WebProxy("http://127.0.0.1:8888"),
UseProxy = false,
};
client = new HttpClient(handler);
client.BaseAddress = new Uri(baseAddress);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var byteArray = Encoding.ASCII.GetBytes(username + ":" + password);
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));
baseresponse = new BaseResponse();
}
}

How my .NET controller can correctly return a JSON to the client (obtained by an external REST call)

I am pretty new in .NET* (I came from Java) and I have the following problem.
I created this controller class calling an external REST endpoint (it works fine):
namespace MyPrj.Controllers
{
public class MailProtocolloController : ApiController
{
private String urlBaseProtocolloApi = "http://myserver.westeurope.cloudapp.azure.com:8081/MY_API";
[SharePointContextWebAPIFilter]
[HttpGet]
[ActionName("GetAooList")]
public IHttpActionResult GetAooList()
{
Console.WriteLine("INTO GetAooList()");
string jsonRequest = urlBaseProtocolloApi + "/api/ProtocollaMail/GetDepartmentsList";
NetworkCredential myCreds = new NetworkCredential("myname", "mypswd", "mydomain");
CredentialCache credCache = new CredentialCache();
credCache.Add(new Uri(jsonRequest), "NTLM", myCreds);
//credCache.Add(new Uri(jsonRequest), "NTLM", CredentialCache.DefaultNetworkCredentials);
HttpWebRequest spRequest = (HttpWebRequest)HttpWebRequest.Create(jsonRequest);
spRequest.Credentials = credCache;
spRequest.UserAgent = "Mozilla/4.0+(compatible;+MSIE+5.01;+Windows+NT+5.0";
spRequest.Method = "GET";
spRequest.Accept = "application/json;odata=verbose";
HttpWebResponse endpointResponse = (HttpWebResponse)spRequest.GetResponse();
//string sResult = "Pippo";
string sResult = endpointResponse.ToString();
/*
using (StreamReader sr = new StreamReader(endpointResponse.GetResponseStream()))
{
sResult = sr.ReadToEnd();
JObject jobj = JObject.Parse(sResult);
}
*/
Console.WriteLine(sResult);
return Ok(sResult);
}
}
}
The called REST endpoint (I renamed it for privacy) return a JSON like this:
[
{
"Name":"Protocollo Generale",
"Value":"PG"
}
]
My controller have to return this JSON to the client. I tried to do in this way:
string sResult = endpointResponse.ToString();
return Ok(sResult);
but it is not what I need because it returns:
<string>System.Net.HttpWebResponse</string>
instead the desired JSON.
What is the correct way to return this JSON to the client? How can I fix it?

C# Credential validation against Jira using the REST API

I'm working on a C# Windows Form application and I would like to have the ability to test a users' credentials against Jira. Basically the user would input their username and password, click OK and the program will tell them if their credentials are accepted or not.
I already have working code (see below) that uses basic authentication via HttpWebRequest to create new tickets (aka issues), close tickets, add watchers, etc - so I figured this would be easy but I'm struggling with it.
As an analog, you can do a credentials check against Active Directory very easily using the System.DirectoryServices.AccountManagement namespace. Basically the method authenticateAD() will simply return true or false:
private bool authenticateAD(string username, string password)
{
PrincipalContext pc = new PrincipalContext(ContextType.Domain, "example.com");
bool isValid = pc.ValidateCredentials(username,password);
return isValid;
}
This is exactly the kind of thing I want to do with Jira.
For reference, here's the code I'm using to add/close/update tickets in jira - maybe it can be modified to do what I want?
private Dictionary<string, string> sendHTTPtoREST(string json, string restURL)
{
HttpWebRequest request = WebRequest.Create(restURL) as HttpWebRequest;
request.Method = "POST";
request.Accept = "application/json";
request.ContentType = "application/json";
string mergedCreds = string.Format("{0}:{1}", username, password);
byte[] byteCreds = UTF8Encoding.UTF8.GetBytes(mergedCreds);
request.Headers.Add("Authorization", "Basic " + byteCreds);
byte[] data = Encoding.UTF8.GetBytes(json);
try
{
using (var requestStream = request.GetRequestStream())
{
requestStream.Write(data, 0, data.Length);
requestStream.Close();
}
}
catch(Exception ex)
{
displayMessages(string.Format("Error creating Jira: {0}",ex.Message.ToString()), "red", "white");
Dictionary<string, string> excepHTTP = new Dictionary<string, string>();
excepHTTP.Add("error", ex.Message.ToString());
return excepHTTP;
}
response = (HttpWebResponse)request.GetResponse();
var reader = new StreamReader(response.GetResponseStream());
string str = reader.ReadToEnd();
var jss = new System.Web.Script.Serialization.JavaScriptSerializer();
var sData = jss.Deserialize<Dictionary<string, string>>(str);
if(response.StatusCode.ToString()=="NoContent")
{
sData.Add("code", "NoContent");
request.Abort();
return sData;
}
else
{
sData.Add("code", response.StatusCode.ToString());
request.Abort();
return sData;
}
}
Thanks!
How about attempting to access the root page of JIRA and see if you get an HTTP 403 error?
try
{
// access JIRA using (parts of) your existing code
}
catch (WebException we)
{
var response = we.Response as HttpWebResponse;
if (response != null && response.StatusCode == HttpStatusCode.Forbidden)
{
// JIRA doesn't like your credentials
}
}
The HttpClient would be simple and best to use check credentials with GetAsync.
The sample code is below
using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri(JiraPath);
// Add an Accept header for JSON format.
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
string mergedCreds = string.Format("{0}:{1}", username, password);
byte[] byteCreds = UTF8Encoding.UTF8.GetBytes(mergedCreds);
var authHeader = new AuthenticationHeaderValue("Basic", byteCreds);
client.DefaultRequestHeaders.Authorization = authHeader;
HttpResponseMessage response = client.GetAsync(restURL).Result; // Blocking call!
if (response.IsSuccessStatusCode)
{
strJSON = response.Content.ReadAsStringAsync().Result;
if (!string.IsNullOrEmpty(strJSON))
return strJSON;
}
else
{
exceptionOccured = true;
// Use "response.ReasonPhrase" to return error message
}
}

JIRA error: Remote server returned error 407 proxy authentication required

I'm trying to access JIRA data (get project) using JIRA REST API. Here is how I'm making it:
public enum JiraResource
{
project
}
public class JiraService : IJiraService
{
#region JiraService member functions
/* public void DoWork()
{
}*/
private const string str_baseURL = "https://jira.atlassian.com/rest/api/latest/issue/JRA-9";
private string str_Username;
private string str_Password;
public JiraService(string username,string password)
{
str_Username = username;
str_Password = password;
}
public string runQuery(
JiraResource resource,
string argument = null,
string data = null,
string method = "GET")
{
string url = string.Format("{0}{1}/",str_baseURL,resource.ToString());
// string url = string.Format("{0}{1}/", str_baseURL);
if(argument != null)
{
url = string.Format("{0}{1}/",url,argument);
}
HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
request.ContentType = "application/json";
request.Method = method;
if (data != null)
{
using (StreamWriter writer = new StreamWriter(request.GetRequestStream()))
{
writer.Write(data);
}
}
string base64Credentials = GetEncodedCredentials();
request.Headers.Add("Authorization", "Basic " + base64Credentials);
HttpWebResponse response = request.GetResponse() as HttpWebResponse; //Getting error here.
string result = string.Empty;
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{ result = reader.ReadToEnd(); }
return result;
}
private string GetEncodedCredentials()
{
string mergedCredentials = string.Format("{0}:{1}", str_Username, str_Password);
byte[] byteCredentials = UTF8Encoding.UTF8.GetBytes(mergedCredentials);
return Convert.ToBase64String(byteCredentials);
}
I'm using username and password as admin and admin (I think this is default). Please note that, this url ("https://jira.atlassian.com/rest/api/latest/issue/JRA-9") showing me json objects from the browser. However, from my code, I'm getting exception "Remote server returned error (407) proxy authentication required ".
As per this post(The remote server returned an error: (407) Proxy Authentication Required) I used useDefaultCredentials="true". But this is not working.
Is there anything that I'm missing? Please guide me.

How can I do digest authentication with HttpWebRequest?

Various articles (1, 2) I discovered make this look easy enough:
WebRequest request = HttpWebRequest.Create(url);
var credentialCache = new CredentialCache();
credentialCache.Add(
new Uri(url), // request url
"Digest", // authentication type
new NetworkCredential("user", "password") // credentials
);
request.Credentials = credentialCache;
However, this only works for URLs without URL parameters. For example, I can download http://example.com/test/xyz.html just fine, but when I attempt to download http://example.com/test?page=xyz, the result is a 400 Bad Request message with the following in the server's logs (running Apache 2.2):
Digest: uri mismatch - </test> does not match request-uri </test?page=xyz>
My first idea was that the digest specification requires URL parameters to be removed from the digest hash -- but removing the parameter from the URL passed to credentialCache.Add() didn't change a thing. So it must be the other way around and somewhere in the .NET framework is wrongly removing the parameter from the URL.
You said you removed the querystring paramters, but did you try going all the way back to just the host? Every single example of CredentialsCache.Add() I've seen seems to use only the host, and the docs for CredentialsCache.Add() list the Uri parameter as "uriPrefix", which seems telling.
In other words, try this out:
Uri uri = new Uri(url);
WebRequest request = WebRequest.Create(uri);
var credentialCache = new CredentialCache();
credentialCache.Add(
new Uri(uri.GetLeftPart(UriPartial.Authority)), // request url's host
"Digest", // authentication type
new NetworkCredential("user", "password") // credentials
);
request.Credentials = credentialCache;
If this works, you will also have to make sure that you don't add the same "authority" to the cache more than once... all requests to the same host should be able to make use of the same credential cache entry.
Code taken from this post has worked perfectly for me Implement Digest authentication via HttpWebRequest in C#
I had following issue, when ever I browser the feed url in a browser it asked for username and password and worked fine, however any of the above code samples were not working, on inspecting Request/Response Header (in web developer tools in firefox) i could see header having Authorization of type digest.
Step 1 Add:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.Text.RegularExpressions;
using System.Net;
using System.IO;
namespace NUI
{
public class DigestAuthFixer
{
private static string _host;
private static string _user;
private static string _password;
private static string _realm;
private static string _nonce;
private static string _qop;
private static string _cnonce;
private static DateTime _cnonceDate;
private static int _nc;
public DigestAuthFixer(string host, string user, string password)
{
// TODO: Complete member initialization
_host = host;
_user = user;
_password = password;
}
private string CalculateMd5Hash(
string input)
{
var inputBytes = Encoding.ASCII.GetBytes(input);
var hash = MD5.Create().ComputeHash(inputBytes);
var sb = new StringBuilder();
foreach (var b in hash)
sb.Append(b.ToString("x2"));
return sb.ToString();
}
private string GrabHeaderVar(
string varName,
string header)
{
var regHeader = new Regex(string.Format(#"{0}=""([^""]*)""", varName));
var matchHeader = regHeader.Match(header);
if (matchHeader.Success)
return matchHeader.Groups[1].Value;
throw new ApplicationException(string.Format("Header {0} not found", varName));
}
private string GetDigestHeader(
string dir)
{
_nc = _nc + 1;
var ha1 = CalculateMd5Hash(string.Format("{0}:{1}:{2}", _user, _realm, _password));
var ha2 = CalculateMd5Hash(string.Format("{0}:{1}", "GET", dir));
var digestResponse =
CalculateMd5Hash(string.Format("{0}:{1}:{2:00000000}:{3}:{4}:{5}", ha1, _nonce, _nc, _cnonce, _qop, ha2));
return string.Format("Digest username=\"{0}\", realm=\"{1}\", nonce=\"{2}\", uri=\"{3}\", " +
"algorithm=MD5, response=\"{4}\", qop={5}, nc={6:00000000}, cnonce=\"{7}\"",
_user, _realm, _nonce, dir, digestResponse, _qop, _nc, _cnonce);
}
public string GrabResponse(
string dir)
{
var url = _host + dir;
var uri = new Uri(url);
var request = (HttpWebRequest)WebRequest.Create(uri);
// If we've got a recent Auth header, re-use it!
if (!string.IsNullOrEmpty(_cnonce) &&
DateTime.Now.Subtract(_cnonceDate).TotalHours < 1.0)
{
request.Headers.Add("Authorization", GetDigestHeader(dir));
}
HttpWebResponse response;
try
{
response = (HttpWebResponse)request.GetResponse();
}
catch (WebException ex)
{
// Try to fix a 401 exception by adding a Authorization header
if (ex.Response == null || ((HttpWebResponse)ex.Response).StatusCode != HttpStatusCode.Unauthorized)
throw;
var wwwAuthenticateHeader = ex.Response.Headers["WWW-Authenticate"];
_realm = GrabHeaderVar("realm", wwwAuthenticateHeader);
_nonce = GrabHeaderVar("nonce", wwwAuthenticateHeader);
_qop = GrabHeaderVar("qop", wwwAuthenticateHeader);
_nc = 0;
_cnonce = new Random().Next(123400, 9999999).ToString();
_cnonceDate = DateTime.Now;
var request2 = (HttpWebRequest)WebRequest.Create(uri);
request2.Headers.Add("Authorization", GetDigestHeader(dir));
response = (HttpWebResponse)request2.GetResponse();
}
var reader = new StreamReader(response.GetResponseStream());
return reader.ReadToEnd();
}
}
}
Step 2: Call new method
DigestAuthFixer digest = new DigestAuthFixer(domain, username, password);
string strReturn = digest.GrabResponse(dir);
if Url is: http://xyz.rss.com/folder/rss
then
domain: http://xyz.rss.com (domain part)
dir: /folder/rss (rest of the url)
you could also return it as stream and use XmlDocument Load() method.
The solution is to activate this parameter in apache:
BrowserMatch "MSIE" AuthDigestEnableQueryStringHack=On
More info : http://httpd.apache.org/docs/2.0/mod/mod_auth_digest.html#msie
Then add this property in your code for the webrequest object:
request.UserAgent = "MSIE"
it work very well for me
I think the second URL points to dynamic page and you should first call it using GET to get the HTML and then to download it. No experience in this field though.
In earlier answers everybody use the obsolete WEbREquest.Create method.
So here is my async solution what up to date for recently trending's:
public async Task<string> LoadHttpPageWithDigestAuthentication(string url, string username, string password)
{
Uri myUri = new Uri(url);
NetworkCredential myNetworkCredential = new NetworkCredential(username, password);
CredentialCache myCredentialCache = new CredentialCache { { myUri, "Digest", myNetworkCredential } };
var request = new HttpClient(new HttpClientHandler() { Credentials = myCredentialCache, PreAuthenticate = true});
var response = await request.GetAsync(url);
var responseStream = await response.Content.ReadAsStreamAsync();
StreamReader responseStreamReader = new StreamReader(responseStream, Encoding.Default);
string answer = await responseStreamReader.ReadToEndAsync();
return answer;
}

Categories

Resources