MVC6 and Web Api Authentication - asp.net

Well, this was a mistake.
I decided to migrate my MVC5 application to MVC6 and things were going fine until I needed to migrate my authentication.
My MVC application was logging in using an external Web Api 2 application which returns a token.
I built a filter to handle that very simply like this:
/// <summary>
/// Uses the session to authorize a user
/// </summary>
public class SimpleAuthorize : AuthorizeAttribute
{
/// <summary>
/// Authorizes the user
/// </summary>
/// <param name="httpContext">The HTTP Context</param>
/// <returns></returns>
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var accessToken = httpContext.Session["AccessToken"];
if (accessToken == null)
return false;
return true;
}
}
which was applied to all controllers.
Now, it appears that you can't do that anymore as mentioned here.
So, how can I get my application to work with the API?
I have tried searching and found nothing that helps me with my situation. Does anyone know how I can solve this or could point me in the direction of some decent documentation?

You'd approach it by writing Authorization middleware which creates an identity out of the access token. Having a valid identity is enough for authorization to succeed, and then you can delve into policy should you need something more detailed. Something like
public class SessionAuthenticationHandler :
AuthenticationHandler<SessionAuthenticationOptions>
{
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
var sessionToken = Request.HttpContext.Session.GetString("Token");
if (sessionToken == null)
{
return AuthenticateResult.Failed("No session token");
}
// Construct principal here
var principal =
new ClaimsPrincipal(new ClaimsIdentity(new[] {
new Claim("SessionToken", sessionToken) }, Options.AuthenticationScheme));
var ticket = new AuthenticationTicket(principal, new AuthenticationProperties(),
Options.AuthenticationScheme);
return AuthenticateResult.Success(ticket);
}
}
Having said that the reason ASP.NET has never used session to hold authentication details is due to session fixation attacks.

Related

IDX21323: RequireNonce is '[PII is hidden]'. OpenIdConnectProtocolValidationContext.Nonce was null

I know there are several threads on the same but none of the solution works for me, is there any solution. The strange thing is when I run the application in IE it works where as in Edge I got into this issue
IDX21323: RequireNonce is '[PII is hidden]'.
OpenIdConnectProtocolValidationContext.Nonce was null,
OpenIdConnectProtocol.ValidatedIdToken.Payload.Nonce was not null. The
nonce cannot be validated. If you don't need to check the nonce, set
OpenIdConnectProtocolValidator.RequireNonce to 'false'. Note if a
'nonce' is found it will be evaluated.
Here is the code
public class Startup
{
// The Client ID is used by the application to uniquely identify itself to Azure AD.
string clientId = System.Configuration.ConfigurationManager.AppSettings["ClientId"];
// RedirectUri is the URL where the user will be redirected to after they sign in.
string redirectUri = System.Configuration.ConfigurationManager.AppSettings["RedirectUri"];
// Tenant is the tenant ID (e.g. contoso.onmicrosoft.com, or 'common' for multi-tenant)
static string tenant = System.Configuration.ConfigurationManager.AppSettings["Tenant"];
// Authority is the URL for authority, composed by Microsoft identity platform endpoint and the tenant name (e.g. https://login.microsoftonline.com/contoso.onmicrosoft.com/v2.0)
string authority = String.Format(System.Globalization.CultureInfo.InvariantCulture, System.Configuration.ConfigurationManager.AppSettings["Authority"], tenant);
/// <summary>
/// Configure OWIN to use OpenIdConnect
/// </summary>
/// <param name="app"></param>
public void Configuration(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
// Sets the ClientId, authority, RedirectUri as obtained from web.config
ClientId = clientId,
Authority = authority,
RedirectUri = redirectUri,
// PostLogoutRedirectUri is the page that users will be redirected to after sign-out. In this case, it is using the home page
PostLogoutRedirectUri = redirectUri,
Scope = OpenIdConnectScope.OpenIdProfile,
// ResponseType is set to request the code id_token - which contains basic information about the signed-in user
ResponseType = OpenIdConnectResponseType.CodeIdToken,
// OpenIdConnectAuthenticationNotifications configures OWIN to send notification of failed authentications to OnAuthenticationFailed method
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailed
}
}
);
}
/// <summary>
/// Handle failed authentication requests by redirecting the user to the home page with an error in the query string
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
private Task OnAuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context)
{
context.HandleResponse();
context.Response.Redirect("/?errormessage=" + context.Exception.Message);
return Task.FromResult(0);
}
}
I tired the options available but nothing works, is there any possible solution for this
Referred to this OpenIdConnectProtocolValidationContext.Nonce was null
Please try updating your Microsoft.Owin.Security.OpenIdConnect package to match the version numbers on your other Owin packages, as discussed in this related thread.
Microsoft.Owin [4.1.1]
Microsoft.Owin.Security [4.1.1]
Microsoft.Owin.Security.Cookies [4.1.1]
Make sure you have also updated to the latest version of Chrome if you are using Google Chrome, or test in other browsers. (This guide discusses an issue with Chrome that was causing this error.)

Google OAuth2 from .NET Web API

First of all I would like to say that there is a chance that this has already been answered some place else, but please read my question carefully before coming to that conclusion, because I have seen so many samples now that do things in a different way or is in another language using components I can't easily interpret into something I know.
What I'm trying to accomplish is to make an ASP.NET Web API, that can authorize with Google aka get an access_token for further communication with Google REST APIs.
That is all I want it to be able to do - the later comminucation with the Google APIs is out of the scope of my Web API - it's just the access_token (either through user consent or by refresh token if user has already previously consented).
I have tried using several different approaches including using the Google API Client Library for .NET.
I'm not going to post code at this point since I'm pretty much confused by now as to which approach should be used in this scenario.
My latest read was this: https://developers.google.com/identity/protocols/OAuth2WebServer and I really wish they had added some C# samples here and not only PHP, Python and Ruby.
For what I'm trying to do, I'm not sure which type of credentials I should be using Oauth or Service Account and if OAuth should it then be for application type Web Application or Other?
It seems to make a great difference which I pick, but I haven't been able to find a guide that explains this so that there isn't any doubt which to use.
What I do know though is, that some of the things I have tried worked from my local computer, but didn't as soon as it was published to an IIS.
So if you can explain to me which of the many approaches is the right one for my scenario, it would be much appreciated - any code samples are also much welcome.
Summing Up:
I want to make an ASP.NET Web API, that can get an access token from Google (an access token to communicate with Google APIs) nothing else.
It has to me able to get an access token via the refresh token, so that the owner of the Google account, only has to grant access once.
Any further communication with Google APIs will happen through normal REST calls and is outside scope of my Web API.
I had the same problem and went down most of the same roads you went down, but having some experience writing OAuth implementations it wasn't too terribly difficult. Maybe you can use what I did, which seems to work for me. You will need to install RestSharp or use some other HttpClient.
First I wrote a GoogleAuthService that handles a few basic issues. Gets the authorization url, exchanges an authorization_code for an access_token and will refresh an access_token with a refresh_token.
GoogleAuthService
public class GoogleAuthService : IGoogleAuthService
{
/// <summary>
/// Step 1 in authorization process
/// </summary>
/// <param name="appId"></param>
/// <returns></returns>
public dynamic AuthorizationUrl(string appId)
{
var qs = HttpUtility.ParseQueryString("");
qs.Add("client_id", CloudConfigurationManager.GetSetting("ga:clientId"));
qs.Add("redirect_uri", CloudConfigurationManager.GetSetting("ga:redirectUri"));
qs.Add("scope", CloudConfigurationManager.GetSetting("ga:scopes"));
qs.Add("access_type", "offline");
qs.Add("state", $"appid={appId}");
qs.Add("response_type", "code");
return new { Url = $"{CloudConfigurationManager.GetSetting("ga:authUrl")}?{qs.ToString()}" };
}
/// <summary>
/// Take the code that came back from Google and exchange it for an access_token
/// </summary>
/// <param name="code"></param>
/// <returns></returns>
public async Task<GoogleAccessTokenResponse> AccessToken(string code)
{
var client = new RestClient(CloudConfigurationManager.GetSetting("ga:tokenUrl"));
var request = new RestRequest();
request.AddParameter("code", code, ParameterType.GetOrPost);
request.AddParameter("client_id", CloudConfigurationManager.GetSetting("ga:clientId"), ParameterType.GetOrPost);
request.AddParameter("client_secret", CloudConfigurationManager.GetSetting("ga:clientSecret"), ParameterType.GetOrPost);
request.AddParameter("redirect_uri", CloudConfigurationManager.GetSetting("ga:redirectUri"), ParameterType.GetOrPost);
request.AddParameter("grant_type", "authorization_code", ParameterType.GetOrPost);
var response = await client.ExecuteTaskAsync<GoogleAccessTokenResponse>(request, Method.POST);
return response.Data;
}
/// <summary>
/// Take an offline refresh_token and get a new acceses_token
/// </summary>
/// <param name="refreshToken"></param>
/// <returns></returns>
public async Task<GoogleRefreshTokenResponse> RefreshToken(string refreshToken)
{
var client = new RestClient(CloudConfigurationManager.GetSetting("ga:tokenUrl"));
var request = new RestRequest();
request.AddParameter("refresh_token", refreshToken, ParameterType.GetOrPost);
request.AddParameter("client_id", CloudConfigurationManager.GetSetting("ga:clientId"), ParameterType.GetOrPost);
request.AddParameter("client_secret", CloudConfigurationManager.GetSetting("ga:clientSecret"), ParameterType.GetOrPost);
request.AddParameter("grant_type", "refresh_token", ParameterType.GetOrPost);
var response = await client.ExecuteTaskAsync<GoogleRefreshTokenResponse>(request, Method.POST);
return response.Data;
}
}
GoogleAccessTokenResponse
public class GoogleAccessTokenResponse
{
/// <summary>
/// Initial token used to gain access
/// </summary>
[JsonProperty("access_token")]
public string AccessToken { get; set; }
/// <summary>
/// Use to get new token
/// </summary>
[JsonProperty("refresh_token")]
public string RefreshToken { get; set; }
/// <summary>
/// Measured in seconds
/// </summary>
[JsonProperty("expires_in")]
public int ExpiresIn { get; set; }
/// <summary>
/// Should always be "Bearer"
/// </summary>
[JsonProperty("token_type")]
public string TokenType { get; set; }
}
GoogleRefreshTokenResponse
public class GoogleRefreshTokenResponse
{
[JsonProperty("access_token")]
public string AccessToken { get; set; }
[JsonProperty("expires_in")]
public int ExpiresIn { get; set; }
[JsonProperty("token_type")]
public string TokenType { get; set; }
}
Lastly you will need a Callback handler to accept the authorization_code.
GoogleOAuthController
public class GoogleOAuthController : Controller
{
private readonly ITenantGoogleAuthenticationService service;
private readonly ITenantService tenantService;
private readonly IGoogleAuthService googleAuthService;
public GoogleOAuthController(ITenantGoogleAuthenticationService service, ITenantService tenantService, IGoogleAuthService googleAuthService)
{
this.service = service;
this.tenantService = tenantService;
this.googleAuthService = googleAuthService;
}
public async Task<ActionResult> Callback(GoogleAuthResponse model)
{
try
{
var response = await this.googleAuthService.AccessToken(model.Code);
var qs = HttpUtility.ParseQueryString(model.State);
var appid = qs["appid"];
var tenant = await this.tenantService.TenantByAppId(appid);
var webTenant = await this.tenantService.GetWebTenant(appid);
var result = await this.service.GoogleAuthenticationSave(new TenantGoogleAuthenticationViewModel
{
AccessToken = response.AccessToken,
Expires = DateTime.Now.AddSeconds(response.ExpiresIn),
RefreshToken = response.RefreshToken,
TenantId = tenant.Id
}, webTenant);
return new RedirectResult("/");
}
catch (Exception ex)
{
return Content(ex.Message);
}
}
}
Model for what is sent to you in the Callback
GoogleAuthResponse
public class GoogleAuthResponse
{
public string State { get; set; }
public string Code { get; set; }
public string Scope { get; set; }
}
Don't worry about the Tenant code as that is specific to my system and shouldn't have any bearing on this implementation. I use the "appid" to identify the users of my application and google is nice enough to allow me to pass that to them in the AuthorizationUrl and they nicely pass it back to me.
So basically you make a call to the GoogleAuthService.AuthorizationUrl() to get the URL. Redirect the User to that URL. Make sure you setup a ga:scopes in your Web.config. When the user agrees to all your security requests they will be forwarded back to the GoogleOAuthController and hit the Callback action, where you will take the code and exchange it for an access_token. At this point you can do like I do and just save it to your database so you can use it later. Looks like by default it expires in like an hour, so you will most likely be calling a RefreshToken before every use but that is up to you.

ASP.NET SignalR client-server authentication using (Azure active directory b2c) Json web token validation

I have two application...... one is JavaScript signalR client and the other one is asp.net web application used as signalR server to broadcast the updates to the client. And I was trying to use azure active directory b2c service to offer authentication and authorization for user through client application to access the resources in the server. So, that only the authenticated user of JavaScript client can initiate signalR connection with the asp.net web application hosting signalR server after the token validation.
As, signalR uses web-sockets we cannot supply the token in the HTTP connection request header. It seems that I should use query string to supply authentication token in the signalR connection request.
After receiving that token in the asp.net server application I need to validate that token and allow the JavaScript client application to have a signalR connection.
I want to implement exactly the same thing in this blog post https://kwilson.io/blog/authorize-your-azure-ad-users-with-signalr/ but using azure active directory b2c.
It seems like others might also have same problem using ASP.NET SignalR Client and server architecture.
Actually, with lots of efforts I was able to solve this issue by customizing the AuthorizeModule of signalR hubs. Actually I override AuthorizeHubConnection() and AuthorizeHubMethodInvocation() using AuthorizeAttribute inheritance in CustomAuthorization class.
First of all I added the GlobalHost.HubPipeline.AddModule(module) in app.Map("/signalr", map =>{ .... } in startup Configuration. You can see it in the following startup.cs.
using Microsoft.Owin;
using Microsoft.Owin.Cors;
using Owin;
using Microsoft.AspNet.SignalR;
using TestCarSurveillance.RealTimeCommunication.AuthorizationConfiguration;
using Microsoft.AspNet.SignalR.Hubs;
[assembly: OwinStartup(typeof(TestCarSurveillance.RealTimeCommunication.Startup))]
namespace TestCarSurveillance.RealTimeCommunication
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
//After adding Authorization module in GlobalHost.HubPipeline.AddModule(module)
//program was unable to create the log file so I have added it.
log4net.Config.XmlConfigurator.Configure();
// Branch the pipeline here for requests that start with "/signalr"
//app.UseWelcomePage("/");
app.Map("/signalr", map =>
{
// Setup the CORS middleware to run before SignalR.
// By default this will allow all origins. You can
// configure the set of origins and/or http verbs by
// providing a cors options with a different policy.
map.UseCors(CorsOptions.AllowAll);
var hubConfiguration = new HubConfiguration
{
EnableDetailedErrors = true,
// You can enable JSONP by uncommenting line below.
// JSONP requests are insecure but some older browsers (and some
// versions of IE) require JSONP to work cross domain
EnableJSONP = true
};
// Require authentication for all hubs
var authorizer = new CustomAuthorization();
var module = new AuthorizeModule(authorizer, authorizer);
GlobalHost.HubPipeline.AddModule(module);
map.RunSignalR(hubConfiguration);
});
}
}
}
This Authorize module calls CustomAuthorize.cs class in each signalR hub OnConnected(), OnDisconnected(), OnReconnected() and hub methods that the client can call.
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
using Microsoft.AspNet.SignalR.Owin;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Owin.Security.Jwt;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.Linq;
using System.Security.Claims;
namespace TestCarSurveillance.RealTimeCommunication.AuthorizationConfiguration
{
public class CustomAuthorization : AuthorizeAttribute
{
// These values are pulled from web.config for b2c authorization
public static string aadInstance = ConfigurationManager.AppSettings["ida:AadInstance"];
public static string tenant = ConfigurationManager.AppSettings["ida:Tenant"];
public static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
public static string signUpInPolicy = ConfigurationManager.AppSettings["ida:SignUpInPolicyId"];
static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
//This method is called multiple times before the connection with signalR is established.
public override bool AuthorizeHubConnection(HubDescriptor hubDescriptor, IRequest request)
{
var metadataEndpoint = string.Format(aadInstance, tenant, signUpInPolicy);
// Extract JWT token from query string.
var userJwtToken = request.QueryString.Get("Authorization");
if (string.IsNullOrEmpty(userJwtToken))
{
return false;
}
// Validate JWT token.
//var tokenValidationParameters = new TokenValidationParameters { ValidAudience = ClientId };
//Contains a set of parameters that are used by a SecurityTokenHandler when validating a SecurityToken.
TokenValidationParameters tvps = new TokenValidationParameters
{
// Accept only those tokens where the audience of the token is equal to the client ID of this app
// This is where you specify that your API only accepts tokens from its own clients
// here the valid audience is supplied to check against the token's audience
ValidAudience = clientId,
ValidateIssuer = false,
// It is the authentication scheme used for token validation
AuthenticationType = signUpInPolicy,
//SaveSigninToken = true,
//I’ve configured the “NameClaimType” of the “TokenValidationParameters” to use the claim named “objectidentifer” (“oid”)
//This will facilitate reading the unique user id for the authenticated user inside the controllers, all we need to call
//now inside the controller is: “User.Identity.Name” instead of querying the claims collection each time
//Gets or sets a String that defines the NameClaimType.
NameClaimType = "http://schemas.microsoft.com/identity/claims/objectidentifier"
};
try
{
var jwtFormat = new JwtFormat(tvps, new OpenIdConnectCachingSecurityTokenProvider(metadataEndpoint));
var authenticationTicket = jwtFormat.Unprotect(userJwtToken);
if(authenticationTicket != null && authenticationTicket.Identity !=null && authenticationTicket.Identity.IsAuthenticated)
{
var email = authenticationTicket.Identity.FindFirst(p => p.Type == "emails").Value;
// It is done to call the async method from sync method
//the ArgumentException will be caught as you’d expect, because .GetAwaiter().GetResult() unrolls the first exception the same way await does.
//This approach follows the principle of least surprise and is easier to understand.
// set the authenticated user principal into environment so that it can be used in the future
request.Environment["server.User"] = new ClaimsPrincipal(authenticationTicket.Identity);
return true;
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
log.Error(ex);
//throw ex;
}
return false;
}
public override bool AuthorizeHubMethodInvocation(IHubIncomingInvokerContext hubIncomingInvokerContext, bool appliesToMethod)
{
var connectionId = hubIncomingInvokerContext.Hub.Context.ConnectionId;
//Check the authenticated user principal from environment
var environment = hubIncomingInvokerContext.Hub.Context.Request.Environment;
//ClaimsPrincipal supports multiple claims based identities
var principal = environment["server.User"] as ClaimsPrincipal;
if(principal != null && principal.Identity != null && principal.Identity.IsAuthenticated)
{
// create a new HubCallerContext instance with the principal generated from token
// and replace the current context so that in hubs we can retrieve current user identity
hubIncomingInvokerContext.Hub.Context = new HubCallerContext(new ServerRequest(environment), connectionId);
return true;
}
return false;
}
}
}
After we receive the token from the query string we need to setup TokenValidationParameters use it in metadataEndpoint for token validation. The token validation is done in before having the hub connection so, that only the authorized user can have a connection and if the connection is not successful it returns 401 response. It is implemented in OpenIdConnectCachingSecurityTokenProvider.cs class. This class is being used by having following line of code in AuthorizeHubConnection() method.
var jwtFormat = new JwtFormat(tvps, new OpenIdConnectCachingSecurityTokenProvider(metadataEndpoint));
var authenticationTicket = jwtFormat.Unprotect(userJwtToken);
As, the last part of this authorization configuration I have inherited IIssureSecurityKeyProvider in OpenIdConnectCachingSecurityTokenProvider.cs class. The complete implementation of it can be seen in the following code.
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Owin.Security.Jwt;
//using System.IdentityModel.Tokens;
namespace TestCarSurveillance.RealTimeCommunication.AuthorizationConfiguration
{
//IIssuerSecurityKeyProvider Interface Provides security Key information to the implementing class.
// This class is necessary because the OAuthBearer Middleware does not leverage
// the OpenID Connect metadata endpoint exposed by the STS by default.
internal class OpenIdConnectCachingSecurityTokenProvider : IIssuerSecurityKeyProvider
{
//Manages the retrieval of Configuration data.
public ConfigurationManager<OpenIdConnectConfiguration> _configManager;
private string _issuer;
private IEnumerable<SecurityKey> _keys;
//this class will be responsible for communicating with the “Metadata Discovery Endpoint” and issue HTTP requests to get the signing keys
//that our API will use to validate signatures from our IdP, those keys exists in the jwks_uri which can read from the discovery endpoint
private readonly string _metadataEndpoint;
//Represents a lock that is used to manage access to a resource, allowing multiple threads for reading or exclusive access for writing.
private readonly ReaderWriterLockSlim _synclock = new ReaderWriterLockSlim();
public OpenIdConnectCachingSecurityTokenProvider(string metadataEndpoint)
{
_metadataEndpoint = metadataEndpoint;
//_configManager = new ConfigurationManager<OpenIdConnectConfiguration>(metadataEndpoint, new OpenIdConnectConfigurationRetriever());
_configManager = new ConfigurationManager<OpenIdConnectConfiguration>(metadataEndpoint, new OpenIdConnectConfigurationRetriever());
//_configManager = new ConfigurationManager<OpenIdConnectConfiguration>(metadataEndpoint);
RetrieveMetadata();
}
/// <summary>
/// Gets the issuer the credentials are for.
/// </summary>
/// <value>
/// The issuer the credentials are for.
/// </value>
public string Issuer
{
get
{
RetrieveMetadata();
_synclock.EnterReadLock();
try
{
return _issuer;
}
finally
{
_synclock.ExitReadLock();
}
}
}
/// <summary>
/// Gets all known security keys.
/// </summary>
/// <value>
/// All known security keys.
/// </value>
public IEnumerable<SecurityKey> SecurityKeys
{
get
{
RetrieveMetadata();
_synclock.EnterReadLock();
try
{
return _keys;
}
finally
{
_synclock.ExitReadLock();
}
}
}
private void RetrieveMetadata()
{
_synclock.EnterWriteLock();
try
{
//Task represents an asynchronous operation.
//Task.Run Method Queues the specified work to run on the ThreadPool and returns a task or Task<TResult> handle for that work.
OpenIdConnectConfiguration config = Task.Run(_configManager.GetConfigurationAsync).Result;
_issuer = config.Issuer;
_keys = config.SigningKeys;
}
finally
{
_synclock.ExitWriteLock();
}
}
}
}
After implementing this we do not need to have [Authorize] attribute in any hub method and this middle-ware will handle the request authorization and only authorized user will have a signalR connection and only authorized user can invoke the hub method.
At last I would like to mention that for this client server architecture to work we need to have separate b2c tenant client application and b2c tenant server application and b2c tenant client application should have API access to the b2c tenant server application. Azure b2c application should be configured as in this example https://learn.microsoft.com/en-us/aspnet/core/security/authentication/azure-ad-b2c-webapi?view=aspnetcore-2.1
Although, it is for .net core but it is also valid for asp.net and only difference is that b2c configuration should be at web.config

.Net MVC 4 CAS Authentication

I need to authenticate User from a central CAS. The assumption are these:
The UserId for authentication is in a Request Header
The roles for authorization are given by a web service.
The application must cache the authorization phase.
I've tried this:
In the Global.asax:
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
const string SiteMinderHeaderToken = "SM_USER";
if (HttpContext.Current.User == null || !HttpContext.Current.User.Identity.IsAuthenticated)
{
var userSSO = HttpContext.Current.Request.Headers[SiteMinderHeaderToken];
GenericIdentity webIdentity = new GenericIdentity(userSSO, "SiteMinder");
string[] roles = { "ROLE1", "ROLE2" };
GenericPrincipal principal = new GenericPrincipal(webIdentity, roles);
HttpContext.Current.User = principal;
// System.Web.Security.FormsAuthentication.SetAuthCookie(userSSO, true);
}
}
In the Web.config
<authentication mode="None" />
<authorization>
<deny users="?" />
</authorization>
The problem is that for every request, the HttpContext.Current.User is always null, and every time all the authentication and authorization phase are done.
If I uncomment
System.Web.Security.FormsAuthentication.SetAuthCookie(userSSO, true);
All is fine, after the first request the User is authenticated.
My questions are:
Is it correct to call System.Web.Security.FormsAuthentication.SetAuthCookie even if there isn't FormAuthentication?
Is there a way to do it better?
Are there some security issues doing this way?
Thanks
FormsAuthentication in this case is just storing the users session.
Here's some more info: Understanding the Forms Authentication Ticket and Cookie https://support.microsoft.com/en-us/kb/910443/
You can set the auth cookie this way, but if would like to store more of the users info within the session, I would recommend using claims.
FormsAuthentication cookie is secure. "The ticket is encrypted and signed using the configuration element of the server's Machine.config file. ASP.NET 2.0 uses the decryptionKey and the new decryption attribute of the element to encrypt forms authentication tickets." There's more information in the link I gave above. I guess the only security issue would be how the user is accessing your code "FormsAuthentication.SetAuthCookie(userSSO, true);"
Try looking into Owin Authentication to store the users session.
http://www.asp.net/aspnet/overview/owin-and-katana/owin-oauth-20-authorization-server
Here's an example of how I use it in my MVC project.
Installed nugget packages:
Microsoft.Owin.Host.SystemWeb
Microsoft.Owin.Security.OAuth
Microsoft.Owin.Security.Cookies
Created OwinConfig.cs file (in my App_Start folder):
using Microsoft.Owin;
[assembly: OwinStartup(typeof(MyProject.OwinConfig))]
namespace MyProject
{
using System.Web.Security;
using Microsoft.Owin.Security.Cookies;
using Owin;
/// <summary>
/// The startup.
/// </summary>
public class OwinConfig
{
/// <summary>
/// The configuration.
/// </summary>
/// <param name="app">
/// The app.
/// </param>
public void Configuration(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = FormsAuthentication.FormsCookieName,
Provider = new CookieAuthenticationProvider()
});
}
}
}
Code I use to log a user in:
var claims = new[]
{
new Claim(ClaimTypes.NameIdentifier, 1)),
new Claim("CustomClaim", "My bio or something"),
new Claim(ClaimTypes.Name, "set_username")
};
var userWithClaims = new ClaimsIdentity(claims, FormsAuthentication.FormsCookieName);
var owinContext = HttpContext.Current.Request.GetOwinContext();
var authenticationManager = owinContext.Authentication;
authenticationManager.SignIn(userWithClaims);
And then I access the stored claims by creating IdentityExtensions.cs class (also in my app_start folder):
public static class IdentityExtensions
{
public static string GetCustomClaim(this IIdentity identity)
{
if (identity == null)
{
throw new ArgumentNullException("identity");
}
var ci = identity as ClaimsIdentity;
return ci != null ? ci.FindFirstValue("CustomClaim") : null;
}
private static string FindFirstValue(this ClaimsIdentity identity, string claimType)
{
if (identity == null)
{
throw new ArgumentNullException("identity");
}
var claim = identity.FindFirst(claimType);
return claim != null ? claim.Value : null;
}
}
So in my controller I could just call:
string customClaim = this.HttpContext.User.Identity.GetCustomClaim();
Then finally, to logout I would use:
var owinContext = HttpContext.Current.Request.GetOwinContext();
var authenticationManager = owinContext.Authentication;
authenticationManager.SignOut();

SignalR ISAuthenticated using Headers

My goal is:
To use custom headers with my own token to authenticate a user or machine against my signalr service.
We've been using this methodology succesfully under ASP.net WEB API to perform our own custom claims based authentication and authorization.
Our Web Api was as follows:
protected void Application_Start()
{
GlobalConfiguration.Configuration.MessageHandlers.Add(new AuthorizationHeaderHandler());
}
Then we would have a AuthorizationHandler that would overwrite the Thread.CurrentPrincipal = principal; and we would be done.
Within SignalR I have tried to implement:
1. Mark our hub using Authorize
2. Implemented custom authorize atributes
3. Tried A Custom Module. But besides returning true if the correct headers we're send I still do not get the Context.User to change to the claims based principal that we generate.
But never can we get the Context.User to show the actual user that's being used to connect to the hub.
Any suggestions are Welcome.
Main reason why we want to achieve this is because we have a couple of different user/machine types that connect to our system.
Anybody any suggestions.
Finally found the solution.
I added my own owin security middleware allowing me to handle customer header based authentication.
This could be easily expanded allowing you to combine multiple authenitication scheme's within on service.
First Create Custom Authentication Middleware:
public class AuthenticationMiddleware : OwinMiddleware
{
public AuthenticationMiddleware(OwinMiddleware next) :
base(next) { }
public override async Task Invoke(IOwinContext context)
{
var request = context.Request;
var value = request.Headers["Phocabby-MachineKey"];
var username = value;
var usernameClaim = new Claim(ClaimTypes.Name, username);
var identity = new ClaimsIdentity(new[] { usernameClaim }, "ApiKey");
var principal = new ClaimsPrincipal(identity);
principal.Identities.First().AddClaim(new Claim("CanGetApiKey", "False"));
principal.Identities.First().AddClaim(new Claim("Cabinet", "True"));
request.User = principal;
await Next.Invoke(context);
}
}
Then register it in the startup class
public void Configuration(IAppBuilder app)
{
app.Use(typeof(AuthenticationMiddleware));
app.MapSignalR();
}

Resources