This post shows you how to handle encrypted user credentials in a Web Api application and offer further security
by enforcing https for all REST api calls.
Step 1: Create a new Web Api application:
This is our web service that will need to authenticate encrypted user credentials.
Step 2: Add a class for handling encryption and decryption
Crypto.cs
using System;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
namespace WebApiEncrypt.Models
{
public static class Crypto
{
private const string PrivateKey =
"<RSAKeyValue><Modulus>s6lpjspk+3o2GOK5TM7JySARhhxE5gB96e9XLSSRuWY2W9F951MfistKRzVtg0cjJTdSk5mnWAVHLfKOEqp8PszpJx9z4IaRCwQ937KJmn2/2VyjcUsCsor+fdbIHOiJpaxBlsuI9N++4MgF/jb0tOVudiUutDqqDut7rhrB/oc=</Modulus><Exponent>AQAB</Exponent><P>3J2+VWMVWcuLjjnLULe5TmSN7ts0n/TPJqe+bg9avuewu1rDsz+OBfP66/+rpYMs5+JolDceZSiOT+ACW2Neuw==</P><Q>0HogL5BnWjj9BlfpILQt8ajJnBHYrCiPaJ4npghdD5n/JYV8BNOiOP1T7u1xmvtr2U4mMObE17rZjNOTa1rQpQ==</Q><DP>jbXh2dVQlKJznUMwf0PUiy96IDC8R/cnzQu4/ddtEe2fj2lJBe3QG7DRwCA1sJZnFPhQ9svFAXOgnlwlB3D4Gw==</DP><DQ>evrP6b8BeNONTySkvUoMoDW1WH+elVAH6OsC8IqWexGY1YV8t0wwsfWegZ9IGOifojzbgpVfIPN0SgK1P+r+kQ==</DQ><InverseQ>LeEoFGI+IOY/J+9SjCPKAKduP280epOTeSKxs115gW1b9CP4glavkUcfQTzkTPe2t21kl1OrnvXEe5Wrzkk8rA==</InverseQ><D>HD0rn0sGtlROPnkcgQsbwmYs+vRki/ZV1DhPboQJ96cuMh5qeLqjAZDUev7V2MWMq6PXceW73OTvfDRcymhLoNvobE4Ekiwc87+TwzS3811mOmt5DJya9SliqU/ro+iEicjO4v3nC+HujdpDh9CVXfUAWebKnd7Vo5p6LwC9nIk=</D></RSAKeyValue>"
;
private const string PublicKey =
"<RSAKeyValue><Modulus>s6lpjspk+3o2GOK5TM7JySARhhxE5gB96e9XLSSRuWY2W9F951MfistKRzVtg0cjJTdSk5mnWAVHLfKOEqp8PszpJx9z4IaRCwQ937KJmn2/2VyjcUsCsor+fdbIHOiJpaxBlsuI9N++4MgF/jb0tOVudiUutDqqDut7rhrB/oc=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>"
;
private static readonly UnicodeEncoding Encoder = new UnicodeEncoding();
public static string Decrypt(string data)
{
var rsa = new RSACryptoServiceProvider();
var dataArray = data.Split(',');
var dataByte = new byte[dataArray.Length];
for (var i = 0; i < dataArray.Length; i++)
dataByte[i] = Convert.ToByte(dataArray[i]);
rsa.FromXmlString(PrivateKey);
var decryptedByte = rsa.Decrypt(dataByte, false);
return Encoder.GetString(decryptedByte);
}
public static string Encrypt(string data)
{
var rsa = new RSACryptoServiceProvider();
rsa.FromXmlString(PublicKey);
var dataToEncrypt = Encoder.GetBytes(data);
var encryptedByteArray = rsa.Encrypt(dataToEncrypt, false).ToArray();
var length = encryptedByteArray.Length;
var item = 0;
var sb = new StringBuilder();
foreach (var x in encryptedByteArray)
{
item++;
sb.Append(x);
if (item < length)
sb.Append(",");
}
return sb.ToString();
}
}
}
Step 3: Create a new Authentication filter
I have created a new folder with which to put any new filter classes:
Into this new Filters folder create a new class called BasicAuthenticationAttribute. This needs to inherit from AuthorizationFilterAttribute.
BasicAuthenticationAttribute.cs
using System;
using System.Net;
using System.Net.Http;
using System.Security.Principal;
using System.Text;
using System.Threading;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
namespace WebApiEncrypt.Filters
{
public class BasicAuthenticationAttribute : AuthorizationFilterAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
var authHeader = actionContext.Request.Headers.Authorization;
if (authHeader != null)
{
var authenticationToken = actionContext.Request.Headers.Authorization.Parameter;
var decodedAuthenticationToken = Encoding.UTF8.GetString(Convert.FromBase64String(authenticationToken));
var decryptedAuthenticationToken = Crypto.Crypto.Decrypt(decodedAuthenticationToken);
var usernamePasswordArray = decryptedAuthenticationToken.Split(':');
var userName = usernamePasswordArray[0];
var password = usernamePasswordArray[1];
// Replace this with your own system of security / means of validating credentials
var isValid = userName == "andy" && password == "password";
if (isValid)
{
var principal = new GenericPrincipal(new GenericIdentity(userName), null);
Thread.CurrentPrincipal = principal;
//actionContext.Response =
// actionContext.Request.CreateResponse(HttpStatusCode.OK,
// "User " + userName + " successfully authenticated");
return;
}
}
HandleUnathorized(actionContext);
}
private static void HandleUnathorized(HttpActionContext actionContext)
{
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
actionContext.Response.Headers.Add("WWW-Authenticate", "Basic Scheme='Data' location = 'http://localhost:");
}
}
}
Step 4: Ensure basic authentication filter is applied in Values controller
Insert wherever you need this authentication to be enforced – globally or per function.
ValuesController.cs
using System.Collections.Generic;
using System.Web.Http;
using WebApiEncrypt.Filters;
namespace WebApiEncrypt.Controllers
{
[BasicAuthentication]
public class ValuesController : ApiController
{
// GET api/values
public IEnumerable<string> Get()
{
return new[] {"value1", "value2"};
}
// GET api/values/5
public string Get(int id)
{
return "value";
}
// POST api/values
public void Post([FromBody] string value)
{
}
// PUT api/values/5
public void Put(int id, [FromBody] string value)
{
}
// DELETE api/values/5
public void Delete(int id)
{
}
}
}
Step 5: Add a require https authorization filter attribute
First add a new Add a require https authorization filter attribute class:
RequireHttpsAttribute.cs
using System;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
namespace WebApiEncrypt.Filters
{
public class RequireHttpsAttribute : AuthorizationFilterAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
var req = actionContext.Request;
if (req.RequestUri.Scheme == Uri.UriSchemeHttps) return;
var html = "<p>Https required.</p>";
if (req.Method.Method == "GET")
{
actionContext.Response = req.CreateResponse(HttpStatusCode.Found);
actionContext.Response.Content = new StringContent(html, Encoding.UTF8, "text/html");
var uriBuilder = new UriBuilder(req.RequestUri)
{
Scheme = Uri.UriSchemeHttps,
Port = 443
};
actionContext.Response.Headers.Location = uriBuilder.Uri;
}
else
{
actionContext.Response = req.CreateResponse(HttpStatusCode.NotFound);
actionContext.Response.Content = new StringContent(html, Encoding.UTF8, "text/html");
}
}
}
}
Then update WebApiConfig.cs to ensure the require https is enforced globally:
WebApiConfig.cs
using System.Web.Http;
using WebApiEncrypt.Filters;
namespace WebApiEncrypt
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Filters.Add(new RequireHttpsAttribute());
}
}
}
Step 6: Set the project properties
Step 7: Test
So in this example, the username:password combination I want to test is “andy:password”
Using the ‘Crypto’ facility, this would encrypt to:
63,127,47,68,12,186,31,83,59,235,103,58,43,65,231,137,16,139,161,134,225,181,241,74,187,20,159,69,37,132,133,181,162,61,251,86,107,5,133,121,228,3,226,112,249,134,85,253,131,103,218,145,237,174,238,89,175,95,220,36,177,7,173,170,208,199,43,243,125,58,164,235,143,139,180,42,142,240,36,167,164,164,191,147,242,145,234,120,159,66,61,76,40,21,88,68,167,42,24,4,20,115,130,141,63,145,82,154,171,193,247,214,183,2,147,236,25,123,6,71,96,112,207,91,89,152,29,168
And the Base 64 encoding of the encrypted text is :
MTEyLDUsNjYsOTYsMTMsNjksMjI4LDEzLDE2OSwyNDIsMjIyLDQ0LDIwNywyNDcsMTk4LDY1LDEyMiwxNTMsMjM4LDE1NSw2MCwxMiw1MCwxMzcsMTM3LDQ1LDE2MSwxODQsMzIsNzIsMjAsMjMxLDIxMSw5OSwxMzMsMTYxLDE2LDE0Myw1MCwxMjgsNjQsMTgzLDEzMyw2NCw4NiwyMDEsMjUwLDIzOCw0MywxNTAsNzYsOTIsMTIzLDAsMjA5LDE1MiwzNywxNywyMTIsMTE0LDExNiw1OSwxNCwxMTQsMCwyMzgsNzAsMTcyLDI0NiwxODcsMTU3LDEzMiw2MywyNDksNjEsMjAxLDg3LDI1MSwxOTMsMjM2LDIyNCwxOTUsMjI2LDEyLDExNSwxOTEsMzAsODEsMjM0LDIyMiwxMjIsMTI1LDYsNCwyMTUsMTg1LDYsMyw5OCwxODYsMTM3LDEzNywxNjUsMjUsMTIyLDg4LDI0NCw2NywxMjksMjEzLDgsMTQ5LDcyLDMsMTA3LDI0LDIxNCwyNywyMTIsMjEyLDc3LDIxNCwyOSwxNTksMjksMjExLDQ4LDcw
Example link for doing Base 64 encoding: https://www.base64encode.org/
I use this encoding as part of the basic authentication header used in the example GET command in Fiddler:
On inspecting the raw output in Fiddler see that the command has satisfied both the authentication and https requirements and the GET command has returned the values as shown:
Also shown is an example console app with which to test the web service:
using System;
using System.Net;
using System.Text;
namespace WebAppConsumer
{
internal static class Program
{
private static void Main()
{
var path = "https://localhost/api/values";
// Update your local service port no. / service APIs etc in the following line
var encrypted = Crypto.Encrypt("andy:password");
var encoded = Convert.ToBase64String(Encoding.UTF8.GetBytes(encrypted));
try
{
var webClient = new WebClient();
webClient.Headers.Add(HttpRequestHeader.Authorization,
"Basic " + encoded);
// Sometimes when you get security certificate trouble but you still need to debug
//ServicePointManager.ServerCertificateValidationCallback +=
// (sender, certificate, chain, sslPolicyErrors) => true;
var result = webClient.DownloadString(path);
Console.WriteLine(result);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
Console.ReadLine();
}
}
}





