Initial Setup
First, create a new project using the ASP.NET Web Application template. Choose the Web Api project template with No Authentication.
In the Web tab of the Project Properties, change it to Local IIS, and create a virtual directory. Then change the application pool of this virtual directory to a pool that uses your own more powerful account. While, you're in there, require SSL.
At this point, make sure you're everything is working by browsing to the api using this uri: NameOfSite/api/values/5. If you get json in return, you're good so far.
Authentication
Authentication and authorization can be configured automatically for you if you choose Local Accounts when you add the project. However, if you want to implement your own custom authentication and authorization code, you need to jump through some hoops.
Authentication of a Web Api is done through what is called an Http Module. This is a bit of code that you write that actually runs on IIS before the request hits your web service. It's pretty heavy stuff, but is the only way I know how to do this at the time of this writing. Add an Infrastructure folder to your project and add a class that implements the IHttpModule interface. Like this:
Imports System.Net.Http.Headers
Imports System.Security.Principal
Public Class BasicAuthHttpModule
Implements IHttpModule
Public Sub Dispose() Implements IHttpModule.Dispose
End Sub
Public Sub Init(context As HttpApplication) Implements IHttpModule.Init
AddHandler context.AuthenticateRequest, AddressOf OnAuthenticateRequest
AddHandler context.EndRequest, AddressOf OnEndRequest
End Sub
Private Sub SetPrincipal(principal As IPrincipal)
Threading.Thread.CurrentPrincipal = principal
If HttpContext.Current IsNot Nothing Then
HttpContext.Current.User = principal
End If
End Sub
Private Function CheckUserNameAndPassword(userName As String, password As String) As Boolean
If userName = "Steve" And password = "topsecret" Then
Return True
Else
Return False
End If
End Function
Private Function AuthenticateUser(userNamePasswordCombo As String) As Boolean
Dim Validated As Boolean = False
Try
Dim MyEncoding = Encoding.GetEncoding("iso-8859-1")
userNamePasswordCombo = MyEncoding.GetString(Convert.FromBase64String(userNamePasswordCombo))
Dim Separator As Integer = userNamePasswordCombo.IndexOf(":")
Dim UserName As String = userNamePasswordCombo.Substring(0, Separator)
Dim Password As String = userNamePasswordCombo.Substring(Separator + 1)
Validated = CheckUserNameAndPassword(UserName, Password)
If Validated Then
Dim MyIdentity = New GenericIdentity(UserName)
SetPrincipal(New GenericPrincipal(MyIdentity, Nothing))
End If
Catch ex As Exception
Validated = False
End Try
Return Validated
End Function
Private Sub OnAuthenticateRequest(sender As Object, e As EventArgs)
Dim MyRequest As HttpRequest = HttpContext.Current.Request
Dim AuthHeader As String = MyRequest.Headers("Authorization")
If AuthHeader IsNot Nothing Then
Dim AuthHeaderValue = AuthenticationHeaderValue.Parse(AuthHeader)
If AuthHeaderValue.Scheme.Equals("basic", StringComparison.OrdinalIgnoreCase) And AuthHeaderValue.Parameter IsNot Nothing Then
AuthenticateUser(AuthHeaderValue.Parameter)
End If
End If
End Sub
Private Sub OnEndRequest(sender As Object, e As EventArgs)
Dim MyResponse As HttpResponse = HttpContext.Current.Response
If MyResponse.StatusCode = 401 Then
MyResponse.Headers.Add("WWW-Authenticate", "Basic realm=""My Realm""")
End If
End Sub
End Class
To enable IIS to use this HTTP Module, add the following to your web.config:
<system.webServer>
<modules>
<add name="BasicAuthHttpModule"
type="NameOfWebApplication.BasicAuthHttpModule, NameOfWebApplication"/>
</modules>
</system.webServer>
In IIS, you also need to disable all authentication including anonymous on the site where the web api is hosted.
Do a quick test in the browser. You should find that a login window comes up (in IE anyway) with which to provide your credentials.
Next, you'll want to create a little client application to test the web service. A console application works great for this. However, you will need to install a NuGet package called Microsoft ASP.NET Web API Client Libraries. The id is Microsoft.AspNet.WebApi.Client. Here is the code to call the Get method from your console:
Async Function WebApiGetAsync() As Task
Using Client As New HttpClient()
Client.BaseAddress = New Uri("https://NameOfServer.Domain.com/NameOfSite/")
Client.DefaultRequestHeaders.Accept.Clear()
Client.DefaultRequestHeaders.Accept.Add(New MediaTypeWithQualityHeaderValue("application/json"))
Dim ByteArray = Encoding.ASCII.GetBytes("Steve:topsecret")
Client.DefaultRequestHeaders.Authorization = New AuthenticationHeaderValue("Basic", Convert.ToBase64String(ByteArray))
Dim Response As HttpResponseMessage = Await Client.GetAsync("api/values/5")
If Response.IsSuccessStatusCode Then
Dim Results As String = Await Response.Content.ReadAsStringAsync()
Console.WriteLine(Results)
Else
Console.WriteLine(Response.StatusCode.ToString)
Dim Results As String = Await Response.Content.ReadAsStringAsync()
Console.WriteLine(Results)
End If
End Using
End Function
Authorization
To create your own custom authorization, you just need to create your own Authorization Filter. Just add a class called CustomAuthorizeAttribute to your Infrastructure folder like this:
Public Class CustomAuthorizeAttribute
Inherits System.Web.Http.AuthorizeAttribute
Protected Overrides Function IsAuthorized(actionContext As Http.Controllers.HttpActionContext) As Boolean
Dim Authorized As Boolean = False
For Each ThisRole As String In Roles.Split(",").ToList
If SomeCustomLibrary.IsInRole(Threading.Thread.CurrentPrincipal.Identity.Name, ThisRole) Then
Authorized = True
Exit For
End If
Next
Return Authorized
End Function
End Class
There are a few things to note here.
- You must inherit System.Web.Http.AuthorizeAttribute and not System.Web.Mvc.AuthorizeAttribute.
- This is invoked by using <CustomAuthorize(Roles:="RoleName1, RoleName2")>
To call a POST method from your client, do the following:
Async Function WebApiPostAsync() As Task
Using Client As New HttpClient()
Client.BaseAddress = New Uri("https://NameOfServer.YourDomain.com/NameOfSite/")
Dim MyModel As New ParameterModel With {.Parameter1 = "From the Web Service.", .Parameter2 = 484507, .Parameter3 = Now.Date}
Dim ByteArray = Encoding.ASCII.GetBytes("Steve:topsecret")
Client.DefaultRequestHeaders.Authorization = New AuthenticationHeaderValue("Basic", Convert.ToBase64String(ByteArray))
Dim Response As HttpResponseMessage = Await Client.PostAsJsonAsync("api/NameOfController", MyModel)
If Response.IsSuccessStatusCode Then
Dim Results As String = Await Response.Content.ReadAsStringAsync()
Console.WriteLine(Results)
Else
Console.WriteLine(Response.StatusCode.ToString)
Dim Results As String = Await Response.Content.ReadAsStringAsync()
Console.WriteLine(Results)
End If
End Using
End Function
There are some key differences here between WCF and Web API. With WCF, you can just call a function and specify parameters just like you would if the method was local. With Web API, in cases of GETs the parameters are either provided via the URL with query parameters or the route. With POSTs you pass a JSON object as data. This JSON object has a "property" for each parameter that you need.