OWIN is a standard interface between .NET servers and web applications. It provides a middleware for decoupling a web server from a web application. The biggest advantage of OWIN is that we are able to host the web application anywhere, and keep the server and application completely separated.
So what does OWIN have to do with our project?
If you notice the code above we see all references to OWIN namespaces, and we register in the assembly the OwinStartup
object to our Startup
class. We must have at least one Startup
class registered in the OwinStartup
attribute. The Startup
class has one function called Configuration
. All Startup
classes must include this function, and it must accept IAppBuilder
. Additional services, such as IHostingEnvironment
and ILoggerFactory
may also be specified, in which case these services will be injected by the server if they are available. The Configuration
specifies how the application will respond to individual HTTP requests. Finally, in our Configuration
method, we will be calling the MapSignalR
(an extension to the IAppBuilder
object). This will define the route for clients to use to connect to your Hub/s.
Our next step is to bring in some security.
The OAuth 2.0 framework enables a server to provide clients with limited access for HTTP services. Protected server resources can only be accessed via access tokens that expire after certain periods of time. Clients will shoot a HTTP request at a domain endpoint URL (normally /token
), the server will send a response with token details (such as expiration, access token, time/date issued), and the access token will be used for a period of time with other HTTP request headers to authorize access to protected resources.
So where do we begin to set up server authorization?
Our first step is to build the logic behind granting clients access from username and password credentials.
An OAuthAuthorizationServerProvider
determines how we validate user credentials using OAuthGrantResourceOwnerCredentialsContext
. Its job is to simply handle the authentication of users. This item provides the context in which we handle resource grants.
Let's add a new folder called Providers
, and add a new file in this folder called AuthorizationServerProvider.cs
. Implement the following:
public class AuthorizationServerProvider : OAuthAuthorizationServerProvider { public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context) { context.Validated(); } public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context) { context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" }); string userName = null; using (AuthenticationRepository authenticationRepository = new AuthenticationRepository()) { IdentityUser user = await authenticationRepository.FindUser(context.UserName, context.Password); if (user == null) { context.SetError("invalid_grant", "Incorrect user name or password"); return; } userName = user.UserName; } var identity = new ClaimsIdentity(context.Options.AuthenticationType); identity.AddClaim(new Claim("Role", "User")); identity.AddClaim(new Claim("UserName", userName)); context.Validated(identity); } }
Our implementation of the OAuthAuthorizationServerProvider
will override the ValidateClientAuthentication
function, which simply returns whether the usercontext
has been validated. We then override the GrantResourceOwnerCredentials()
function, which is called when a request to the token endpoint (/token
) arrives with a grant_type
of password
(this key is set in the request header along with the username and password). The function will simply initialize a new AuthenticationRepository
to access the UserManager
framework and check if the user exists; if it doesn't we return, and the context will still be invalid. If the user exists, we create a new ClaimsIdentity
object with two claims, one for the role and username principles of there source owner (the user who sent the HTTP request). Finally, we then place the ClaimsIdentity
object into the context.Validated()
function in order to issue the access token. This ClaimsIdentity
object is now the ticket that contains the claims about the resource owner (the user) associated with the access token.
Our next step is to add the logic behind handling bearer tokens (these are the access tokens granted by the authorization server provider). UseOAuthBearerAuthentication
has the job of ensuring that only authenticated users can access your protected server resources (in our example the ChatHub
). Add a new file called OAuthBearerTokenAuthenticationProvider.cs
and implement the following:
public class OAuthBearerTokenAuthenticationProvider : OAuthBearerAuthenticationProvider { public override Task RequestToken(OAuthRequestTokenContext context) { string cookieToken = null; string queryStringToken = null; string headerToken = null; try { cookieToken = context.OwinContext.Request.Cookies["BearerToken"]; } catch (NullReferenceException) { System.Diagnostics.Debug.WriteLine("The cookie does not contain the bearer token"); } try { queryStringToken = context.OwinContext.Request.Query["BearerToken"].ToString(); } catch (NullReferenceException) { System.Diagnostics.Debug.WriteLine("The query string does not contain the bearer token"); } try { headerToken = context.OwinContext.Request.Headers["BearerToken"]; } catch (NullReferenceException) { System.Diagnostics.Debug.WriteLine("The connection header does not contain the bearer token"); } if (!String.IsNullOrEmpty(cookieToken)) context.Token = cookieToken; else if (!String.IsNullOrEmpty(queryStringToken)) context.Token = queryStringToken; else if (!String.IsNullOrEmpty(headerToken)) context.Token = headerToken; return Task.FromResult<object>(null); } }
Let's look at this item more closely. We are overriding the RequestToken()
function to access the OAuthRequestTokenContext
from every HTTP request that hits the server. Inside the OwinContext
object, we can access the HTTP request that just hit the server, check through the dictionary of headers for our BearerToken
, and then extract this access token and assign it to the OAuthRequestTokenContext.Token
property.
18.225.35.81