Keep users in Config.groovy list in Grails - grails

Is there any way to define the users that can use my application in a list in Config.groovy? This will be using Grails 2.2.3 and the latest versions of Spring Security Core and Spring Security LDAP.
We use Active Directory for authentication, and only 2 or 3 people will use this little application, so it doesn't seem worthy of making an AD Group for just this app. It would be simpler to define a list, and any time there is a new hire instead of adding them to the AD group all I have to do is add their name to the external Grails config.
I would like to do something like the following:
SomeController.groovy
#Secured("authentication.name in grailsApplication.config.my.app.usersList")
class SomeController {
}
Then in Config.groovy put this code:
my.app.usersList = ['Bill', 'Tom', 'Rick']
Is this possible? If so, is this a terrible idea? Thanks a lot.

That seems really silly. Why not have the list of users in a table? Then you can add/remove from that table without have to modify the application.
I currently do this and in my UserDetailsContextMapper I make sure the username already exists in the Users table.

You need a custom authenticator that will try to access your Active Directory and if authenticated, will look into Grails properties to check if the username is allowed to login.
This is the class that I use. I changed the code to validate the config:
class ActiveDirectoryAuthenticator {
private DefaultSpringSecurityContextSource contextFactory
private String principalSuffix = ""
def grailsApplication
public DirContextOperations authenticate(Authentication authentication) {
// Grab the username and password out of the authentication object.
String principal = authentication.getName() + "#" + principalSuffix
String password = ""
if (authentication.getCredentials() != null) {
password = authentication.getCredentials().toString()
}
// If we have a valid username and password, try to authenticate.
if (!("".equals(principal.trim())) && !("".equals(password.trim()))) {
try {
String provider = contextFactory.getUrls()[0]
Hashtable authEnv = new Hashtable(11)
authEnv.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory")
authEnv.put(Context.PROVIDER_URL, provider)
authEnv.put(Context.SECURITY_AUTHENTICATION, "simple")
authEnv.put(Context.SECURITY_PRINCIPAL, principal)
authEnv.put(Context.SECURITY_CREDENTIALS, password)
javax.naming.directory.DirContext authContext = new InitialDirContext(authEnv)
//here validate the user against your config.
if(!authentication.getName() in grailsApplication.config.adUsersAllowed) {
throw new BadCredentialsException("User not allowed.")
}
DirContextOperations authAdapter = new DirContextAdapter()
authAdapter.addAttributeValue("ldapContext", authContext)
return authAdapter
} catch ( NamingException ex ) {
throw new BadCredentialsException(ex.message)
}
} else {
throw new BadCredentialsException("Incorrect username or password")
}
}
public DefaultSpringSecurityContextSource getContextFactory() {
return contextFactory
}
/**
* Set the context factory to use for generating a new LDAP context.
*
* #param contextFactory
*/
public void setContextFactory(DefaultSpringSecurityContextSource contextFactory) {
this.contextFactory = contextFactory
}
public String getPrincipalSuffix() {
return principalSuffix
}
/**
* Set the string to be prepended to all principal names prior to attempting authentication
* against the LDAP server. (For example, if the Active Directory wants the domain-name-plus
* backslash prepended, use this.)
*
* #param principalPrefix
*/
public void setPrincipalSuffix(String principalSuffix) {
if (principalSuffix != null) {
this.principalSuffix = principalSuffix
} else {
this.principalSuffix = ""
}
}
}
Declare it as your ldapAuthenticator in resources.groovy:
ldapAuthenticator(ActiveDirectoryAuthenticator) {
contextFactory = ref('contextSource')
principalSuffix = 'domain.local' //your domain suffix
grailsApplication = ref('grailsApplication')
}
The downside is that you need to restart your context when you change config.groovy
In your controllers just use #Secured('IS_AUTHENTICATED_FULLY')

I do not think you can do that because annotations are resolved at compile time and not in runtime. Config properties will be read during the application runtime so you I fear you have to end up doing:
#Secured(["authentication.name in ['Bill', 'Tom', 'Rick']"])
class SomeController {
}

If I remember correctly the #Secured annotation cannot be used for other things than comparing roles. But you should be able to do this with spring securities #PreAuthorize and #PostAuthorize annotations. When using grails the easiest way to setup these annotations is installing the spring security ACL plugin.
Within #PreAuthorize and #PostAuthorize you can use SPEL expressions which are more flexible. Unfortunatelly SPEL does not provide an in operator. However you can delegate the security check to a service:
#PreAuthorize('#securityService.canAccess(authentication)')
public void test() {
println "test?"
}
With the # symbol you can reference other beans like services within expression. Here the method securityService.canAccess() is called to evaluate if the logged in user can access this method.
To use this you have to configure a BeanResolver. I wrote some more details about configuring a BeanResolver here.
Within securityService you can now do:
class SecurityService {
def grailsApplication
public boolean canAccess(Authentication auth) {
return grailsApplication.config.myList.contains(auth.name)
}
}
In general I would not recommend to use a configuration value for validating the user in security checks. The groovy configuration will be compiled so you cannot easily add a new user without redeploying your application.

Related

Grails - Spring Security UI Email for Username

I'm using Grails 3.2.4 and am attempting to use the email property of my User class as the username for registration.
So far, I've managed to get Spring Security Core to use the email as the username using the configuration setting below:
grails.plugin.springsecurity.userLookup.usernamePropertyName='email'
However, the registration functionality doesn't seem to take this into account and won't let me register a new user using only an email and password.
I've made a few attempts at overriding the RegisterController but I continue to experience different errors regarding null usernames.
It seems like I must be missing something very simple. Any help / direction is greatly appreciated.
It appears that in version spring-security-ui-3.0.0.M2 the username property might not be override-able.
String paramNameToPropertyName(String paramName, String controllerName) {
// Properties in the ACL classes and RegistrationCode aren't currently
// configurable, and the PersistentLogin class is generated but its
// properties also aren't configurable. Since this method is only by
// the controllers to be able to hard-code param names and lookup the
// actual domain class property name we can short-circuit the logic here.
// Additionally there's no need to support nested properties (e.g.
// 'aclClass.className' for AclObjectIdentity search) since those are
// not used in GSP for classes with configurable properties.
if (!paramName) {
return paramName
}
Class<?> clazz = domainClassesByControllerName[controllerName]
String name
if (clazz) {
String key = paramName
if (paramName.endsWith('.id')) {
key = paramName[0..-4]
}
name = classMappings[clazz][key]
}
name ?: paramName
}
PS I'm getting around this now by doing something like this in my user domain class:
static transients = ["migrating"]]
String username = null
public void setEmail(String email) {
this.email = email
this.username = email
}

How to ensure #PreAuthorize has been defined?

We're using Spring Security #PreAuthorize annotation to check proper roles and permissions of Jersey-based REST APIs. What we're worried about is that we forget the annotation from some method, leaving it unsecured.
Is there a way to ensure that all APIs have the annotation, even if it is just #PreAuthorize("permitAll") (i.e. explicitly permitting everything)?
We're envisioning that if an API does not use the annotation, either the request is blocked (so that the API cannot be used) an error is logged (so that we notice the mistake quickly).
One way would be to make a custom expression that adds a request attribute marking the request as "secured". Then a web filter can check that the request attribute is in place and log an error if it is not.
Is there any cleaner way to do this? For example set "denyAll" as the default policy for un-annotated methods, and require overriding it as appropriate?
I finally resolved this by adding a Jersey ResourceFilterFactory which checks that each method or class has the annotation:
public class RestFilterFactory implements ResourceFilterFactory {
private static final Logger log = LoggerFactory.getLogger(RestFilterFactory.class);
#Override
public List<ResourceFilter> create(AbstractMethod method) {
ensurePreAuthorizedAnnotation(method);
return Arrays.emptyList();
}
private void ensurePreAuthorizedAnnotation(AbstractMethod method) {
boolean found = false;
if (hasPreAuthenticatedAnnotation(method)) {
found = true;
}
if (hasPreAuthenticatedAnnotation(method.getResource().getResourceClass())) {
found = true;
}
if (!found) {
log.error("Resource is not protected with #PreAuthorize annotation: " + method);
}
}
private boolean hasPreAuthenticatedAnnotation(AnnotatedElement element) {
Annotation a = element.getAnnotation(PreAuthorize.class);
return a != null;
}
}

How to propagate spring security context to JMS?

I have a web application which sets a spring security context through a spring filter. Services are protected with spring annotations based on users roles. This works.
Asynchronous tasks are executed in JMS listeners (extend javax.jms.MessageListener). The setup of this listeners is done with Spring.
Messages are sent from the web application, at this time a user is authenticated. I need the same authentication in the JMS thread (user and roles) during message processing.
Today this is done by putting the spring authentication in the JMS ObjectMessage:
SecurityContext context = SecurityContextHolder.getContext();
Authentication auth = context.getAuthentication();
... put the auth object in jms message object
Then inside the JMS listener the authentication object is extracted and set in the context:
SecurityContext context = new SecurityContextImpl();
context.setAuthentication(auth);
SecurityContextHolder.setContext(context);
This works most of the time. But when there is a delay before the processing of a message, message will never be processed. I couldn't determine yet the cause of these messages loss, but I'm not sure the way we propagate authentication is good, even if it works in custer when the message is processed in another server.
Is this the right way to propagate a spring authentication ?
Regards,
Mickaël
I did not find better solution, but this one works for me just fine.
By sending of JMS Message I'am storing Authentication as Header and respectively by receiving recreating Security Context. In order to store Authentication as Header you have to serialise it as Base64:
class AuthenticationSerializer {
static String serialize(Authentication authentication) {
byte[] bytes = SerializationUtils.serialize(authentication);
return DatatypeConverter.printBase64Binary(bytes);
}
static Authentication deserialize(String authentication) {
byte[] decoded = DatatypeConverter.parseBase64Binary(authentication);
Authentication auth = (Authentication) SerializationUtils.deserialize(decoded);
return auth;
}
}
By sending just set Message header - you can create Decorator for Message Template, so that it will happen automatically. In you decorator just call such method:
private void attachAuthenticationContext(Message message){
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String serialized = AuthenticationSerializer.serialize(auth);
message.setStringProperty("authcontext", serialized);
}
Receiving gets more complicated, but it can be also done automatically. Instead of applying #EnableJMS use following Configuration:
#Configuration
class JmsBootstrapConfiguration {
#Bean(name = JmsListenerConfigUtils.JMS_LISTENER_ANNOTATION_PROCESSOR_BEAN_NAME)
#Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public JmsListenerAnnotationBeanPostProcessor jmsListenerAnnotationProcessor() {
return new JmsListenerPostProcessor();
}
#Bean(name = JmsListenerConfigUtils.JMS_LISTENER_ENDPOINT_REGISTRY_BEAN_NAME)
public JmsListenerEndpointRegistry defaultJmsListenerEndpointRegistry() {
return new JmsListenerEndpointRegistry();
}
}
class JmsListenerPostProcessor extends JmsListenerAnnotationBeanPostProcessor {
#Override
protected MethodJmsListenerEndpoint createMethodJmsListenerEndpoint() {
return new ListenerEndpoint();
}
}
class ListenerEndpoint extends MethodJmsListenerEndpoint {
#Override
protected MessagingMessageListenerAdapter createMessageListenerInstance() {
return new ListenerAdapter();
}
}
class ListenerAdapter extends MessagingMessageListenerAdapter {
#Override
public void onMessage(Message jmsMessage, Session session) throws JMSException {
propagateSecurityContext(jmsMessage);
super.onMessage(jmsMessage, session);
}
private void propagateSecurityContext(Message jmsMessage) throws JMSException {
String authStr = jmsMessage.getStringProperty("authcontext");
Authentication auth = AuthenticationSerializer.deserialize(authStr);
SecurityContextHolder.getContext().setAuthentication(auth);
}
}
I have implemented for myself a different solution, which seems easier for me.
Already I have a message converter, the standard JSON Jackson message converter, which I need to configure on the JMSTemplate and the listeners.
So I created a MessageConverter implementation which wraps around another message converter, and propagates the security context via the JMS message properties.
(In my case, the propagated context is a JWT token which I can extract from the current context and apply to the security context of the listening thread).
This way the entire responsibility for propagation of security context is elegantly implemented in a single class, and requires only a little bit of configuration.
Thanks great but I am handling this in easy way . put one util file and solved .
public class AuthenticationSerializerUtil {
public static final String AUTH_CONTEXT = "authContext";
public static String serialize(Authentication authentication) {
byte[] bytes = SerializationUtils.serialize(authentication);
return DatatypeConverter.printBase64Binary(bytes);
}
public static Authentication deserialize(String authentication) {
byte[] decoded = DatatypeConverter.parseBase64Binary(authentication);
Authentication auth = (Authentication) SerializationUtils.deserialize(decoded);
return auth;
}
/**
* taking message and return string json from message & set current context
* #param message
* #return
*/
public static String jsonAndSetContext(Message message){
LongString authContext = (LongString)message.getMessageProperties().getHeaders().get(AUTH_CONTEXT);
Authentication auth = deserialize(authContext.toString());
SecurityContextHolder.getContext().setAuthentication(auth);
byte json[] = message.getBody();
return new String(json);
}
}

Multitenancy with Fluent nHibernate and Ninject. One Database per Tenant

I'm building a multi-tenant web application where for security concerns, we need to have one instance of the database per tenant. So I have a MainDB for authentication and many ClientDB for application data.
I am using Asp.net MVC with Ninject and Fluent nHibernate. I have already setup my SessionFactory/Session/Repositories using Ninject and Fluent nHibernate in a Ninject Module at the start of the application. My sessions are PerRequestScope, as are repositories.
My problem is now I need to instanciate a SessionFactory (SingletonScope) instance for each of my tenants whenever one of them connects to the application and create a new session and necessary repositories for each webrequest. I'm puzzled as to how to do this and would need a concrete example.
Here's the situation.
Application starts : The user of TenantX enters his login info. SessionFactory of MainDB gets created and opens a session to the MainDB to authenticate the user. Then the application creates the auth cookie.
Tenant accesses the application : The Tenant Name + ConnectionString are extracted from MainDB and Ninject must construct a tenant specific SessionFactory (SingletonScope) for that tenant. The rest of the web request, all controllers requiring a repository will be inject with a Tenant specific session/repository based on that tenant's SessionFactory.
How do I setup that dynamic with Ninject? I was originally using Named instance when I had multiple databases but now that the databases are tenant specific, I'm lost...
After further research I can give you a better answer.
Whilst it's possible to pass a connection string to ISession.OpenSession a better approach is to create a custom ConnectionProvider. The simplest approach is to derive from DriverConnectionProvider and override the ConnectionString property:
public class TenantConnectionProvider : DriverConnectionProvider
{
protected override string ConnectionString
{
get
{
// load the tenant connection string
return "";
}
}
public override void Configure(IDictionary<string, string> settings)
{
ConfigureDriver(settings);
}
}
Using FluentNHibernate you set the provider like so:
var config = Fluently.Configure()
.Database(
MsSqlConfiguration.MsSql2008
.Provider<TenantConnectionProvider>()
)
The ConnectionProvider is evaluated each time you open a session allowing you to connect to tenant specific databases in your application.
An issue with the above approach is that the SessionFactory is shared. This is not really a problem if you are only using the first level cache (since this is tied to the session) but is if you decide to enable the second level cache (tied to the SessionFactory).
The recommended approach therefore is to have a SessionFactory-per-tenant (this would apply to schema-per-tenant and database-per-tenant strategies).
Another issue often overlooked is that although the second level cache is tied to the SessionFactory, in some cases the cache space itself is shared (reference). This can be resolved by setting the "regionName" property of the provider.
Below is a working implementation of SessionFactory-per-tenant based on your requirements.
The Tenant class contains the information we need to set up NHibernate for the tenant:
public class Tenant : IEquatable<Tenant>
{
public string Name { get; set; }
public string ConnectionString { get; set; }
public bool Equals(Tenant other)
{
if (other == null)
return false;
return other.Name.Equals(Name) && other.ConnectionString.Equals(ConnectionString);
}
public override bool Equals(object obj)
{
return Equals(obj as Tenant);
}
public override int GetHashCode()
{
return string.Concat(Name, ConnectionString).GetHashCode();
}
}
Since we'll be storing a Dictionary<Tenant, ISessionFactory> we implement the IEquatable interface so we can evaluate the Tenant keys.
The process of getting the current tenant is abstracted like so:
public interface ITenantAccessor
{
Tenant GetCurrentTenant();
}
public class DefaultTenantAccessor : ITenantAccessor
{
public Tenant GetCurrentTenant()
{
// your implementation here
return null;
}
}
Finally the NHibernateSessionSource which manages the sessions:
public interface ISessionSource
{
ISession CreateSession();
}
public class NHibernateSessionSource : ISessionSource
{
private Dictionary<Tenant, ISessionFactory> sessionFactories =
new Dictionary<Tenant, ISessionFactory>();
private static readonly object factorySyncRoot = new object();
private string defaultConnectionString =
#"Server=(local)\sqlexpress;Database=NHibernateMultiTenancy;integrated security=true;";
private readonly ISessionFactory defaultSessionFactory;
private readonly ITenantAccessor tenantAccessor;
public NHibernateSessionSource(ITenantAccessor tenantAccessor)
{
if (tenantAccessor == null)
throw new ArgumentNullException("tenantAccessor");
this.tenantAccessor = tenantAccessor;
lock (factorySyncRoot)
{
if (defaultSessionFactory != null) return;
var configuration = AssembleConfiguration("default", defaultConnectionString);
defaultSessionFactory = configuration.BuildSessionFactory();
}
}
private Configuration AssembleConfiguration(string name, string connectionString)
{
return Fluently.Configure()
.Database(
MsSqlConfiguration.MsSql2008.ConnectionString(connectionString)
)
.Mappings(cfg =>
{
cfg.FluentMappings.AddFromAssemblyOf<NHibernateSessionSource>();
})
.Cache(c =>
c.UseSecondLevelCache()
.ProviderClass<HashtableCacheProvider>()
.RegionPrefix(name)
)
.ExposeConfiguration(
c => c.SetProperty(NHibernate.Cfg.Environment.SessionFactoryName, name)
)
.BuildConfiguration();
}
private ISessionFactory GetSessionFactory(Tenant currentTenant)
{
ISessionFactory tenantSessionFactory;
sessionFactories.TryGetValue(currentTenant, out tenantSessionFactory);
if (tenantSessionFactory == null)
{
var configuration = AssembleConfiguration(currentTenant.Name, currentTenant.ConnectionString);
tenantSessionFactory = configuration.BuildSessionFactory();
lock (factorySyncRoot)
{
sessionFactories.Add(currentTenant, tenantSessionFactory);
}
}
return tenantSessionFactory;
}
public ISession CreateSession()
{
var tenant = tenantAccessor.GetCurrentTenant();
if (tenant == null)
{
return defaultSessionFactory.OpenSession();
}
return GetSessionFactory(tenant).OpenSession();
}
}
When we create an instance of NHibernateSessionSource we set up a default SessionFactory to our "default" database.
When CreateSession() is called we get a ISessionFactory instance. This will either be the default session factory (if the current tenant is null) or a tenant specific session factory. The task of locating the tenant specific session factory is performed by the GetSessionFactory method.
Finally we call OpenSession on the ISessionFactory instance we have obtained.
Note that when we create a session factory we set the SessionFactory name (for debugging/profiling purposes) and cache region prefix (for the reasons mentioned above).
Our IoC tool (in my case StructureMap) wires everything up:
x.For<ISessionSource>().Singleton().Use<NHibernateSessionSource>();
x.For<ISession>().HttpContextScoped().Use(ctx =>
ctx.GetInstance<ISessionSource>().CreateSession());
x.For<ITenantAccessor>().Use<DefaultTenantAccessor>();
Here NHibernateSessionSource is scoped as a singleton and ISession per request.
Hope this helps.
If all the databases are on the same machine, maybe the schema property of class mappings could be used to set the database on a pre-tenant basis.

ASP.NET MVC - HTTP Authentication Prompt

Is it possible to make my application ask for username and password prompting for it before render a view?
Just like on twitter API to get information about your account:
http://twitter.com/account/verify_credentials.xml
So before render the view || file it asks you to insert you username and password, I think this is made directly on the server since the curl request is based on username:password as well like this:
curl -u user:password http://twitter.com/account/verify_credentials.xml
As I'm trying to build an API following the same structure I would like to know how I can do this on ASP.NET MVC C#. I've already used this on ruby rails and its pretty simple like:
before_filter :authenticate
def authenticate
authenticate_or_request_with_http_basic do |username, password|
username == "foo" && password == "bar"
end
I don't think that [Authorize] filter is the same since I believe it's just a redirection,
and it redirects you to the Accounts Internal Controller that is based on the accounts database, in this case I will use another database, specifically from a webservice and do the validation after the information is submitted.
But I need the action to require the user and pass credentials on its request.
Thanks in advance
UPDATE:
Actually to request a page that requires this authentication (i.e. Twitter)
I would have to declare this on its request
request.Credentials = new NetworkCredential("username", "password");
And this would reflect that prompted username and password.
So, it's exactly the same thing but from the other side, if it's possible to provide information to the authentication prompt on request, how could I require this authentication on the request instead?
So everytime somebody tries to make a request to my application on example:
http://myapplication/clients/verify_credentials
it should ask for a username and password with that server prompt
so to retrive information on curl for example it would be like this
curl -u user:password http://myapplication/clients/verify_credentials
Well, to require basic authentication you need to return 401 status code. But doing that will cause the current authentication module to execute its default unauthorized handler (for forms authentication, this means redirecting to login page).
I wrote an ActionFilterAttribte to see if I can get the behaviour you want when there's no authentication module installed in web.config.
public class RequireBasicAuthentication : ActionFilterAttribute {
public override void OnActionExecuting(ActionExecutingContext filterContext) {
var req = filterContext.HttpContext.Request;
if (String.IsNullOrEmpty(req.Headers["Authorization"])) {
var res = filterContext.HttpContext.Response;
res.StatusCode = 401;
res.AddHeader("WWW-Authenticate", "Basic realm=\"Twitter\"");
res.End();
}
}
}
And the controller action :
[RequireBasicAuthentication]
public ActionResult Index() {
var cred = System.Text.ASCIIEncoding.ASCII
.GetString(Convert.FromBase64String(
Request.Headers["Authorization"].Substring(6)))
.Split(':');
var user = new { Name = cred[0], Pass = cred[1] };
return Content(String.Format("user:{0}, password:{1}",
user.Name, user.Pass));
}
That action successfully prints the username and password I enter. But I really doubt that's the best way to do this. Do you have no choice except asking for username and password this way?
You really want to create a service and not a web application, based on what I have read. I am guessing here, but I think you picked ASP.NET MVC to take advantage of the routing and building the URL's the way you want? Correct me if I am wrong.
In my opinion the best way to solve the problem you are having is to build RESTful web services with WCF if you are returning data. This article should help you get started if you want to go this route.
Otherwise, you will need to go further up the stack for handling the request and authenticating it. If this is the case, I can help with providing more info and code.
I modified the çağdaş answer to put the whole logic inside my custom ActionFilter attribute.
public class BasicAuthenticationAttribute : ActionFilterAttribute
{
public string BasicRealm { get; set; }
protected string Username { get; set; }
protected string Password { get; set; }
public BasicAuthenticationAttribute(string username, string password)
{
this.Username = username;
this.Password = password;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var req = filterContext.HttpContext.Request;
var auth = req.Headers["Authorization"];
if (!String.IsNullOrEmpty(auth))
{
var cred = System.Text.ASCIIEncoding.ASCII.GetString(Convert.FromBase64String(auth.Substring(6))).Split(':');
var user = new { Name = cred[0], Pass = cred[1] };
if (user.Name == Username && user.Pass == Password) return;
}
var res = filterContext.HttpContext.Response;
res.StatusCode = 401;
res.AddHeader("WWW-Authenticate", String.Format("Basic realm=\"{0}\"", BasicRealm ?? "Ryadel"));
res.End();
}
}
It can be used to put under Basic Authentication a whole controller:
[BasicAuthenticationAttribute("your-username", "your-password",
BasicRealm = "your-realm")]
public class HomeController : BaseController
{
...
}
or a specific ActionResult:
public class HomeController : BaseController
{
[BasicAuthenticationAttribute("your-username", "your-password",
BasicRealm = "your-realm")]
public ActionResult Index()
{
...
}
}
NOTE: The above implementation requires the developer to manually insert the username and password as ActionFilter required parameters but can be easily extended to make it support any authorization mechanism (MembershipProvider, ASP.NET Identity, custom userbase on an external DBMS or file, etc.) by removing the custom constructor and modifying the OnActionExecuting method IF block accordingly.
For additional info, you can also read this post I wrote on my blog.
Here's the way that has worked for me. It's a little foot work but it will make IIS and MVC3 behave a lot more like all the other Basic Http authentication systems, like Apache...
Step 1.
Make sure "Basic Authentication" is installed for IIS.
( Example: Control Panel -> Programs and Features -> Turn Windows features on or off )
*I'm using Windows 7 at the moment and am not sure the exact path. [GOOGLE: installing basic authentication in IIS] should get you close.
Step 2.
Make sure Basic Authentication is enabled under your site. If you had to install this in the previous step you need to make sure you reset the IIS service and that all the app pools actually went down.
Step 3.
(Note: I am using MVC3, and feel this should work in most models, including ASP.Net, without a lot of fuss.)
In your project you will need to add the following classes:
public class ServicePrincipal : IPrincipal { // This answers the "What am I allowed to do" question
// In real life, this guy will contain all your user info
// and you can put what ever you like and retrieve it
// later via the HttpContext, on your application side.
// Some fun with casting will be required.
public static IPrincipal Default {
get {
return new ServicePrincipal {
Identity = new ServiceIdentity {
AuthenticationType = "Test",
IsAuthenticated = true,
Name = "Basic"
}
};
}
}
public IIdentity Identity { get; set; }
public bool IsInRole(string role) {
// If you want to use role based authorization
// e.g. [Authorize(Roles = "CoolPeople")]
// This is the place to do it and you can do
// anything from load info from a db or flat file
// or simple case statement...though that would
// be silly.
return true;
}
}
public class ServiceIdentity : IIdentity { // This answers the "Who Am I" Question
public string AuthenticationType { get; set; }
public bool IsAuthenticated { get; set; }
public string Name { get; set; }
}
public class ServiceModule : IHttpModule { // This is the module for IIS
public void Init(HttpApplication context) {
context.AuthenticateRequest += this.BasicAuthenticationRequest;
}
public void BasicAuthenticationRequest(object sender, EventArgs e) {
HttpApplication app = sender as HttpApplication;
if( !ServiceProvider.Authenticate(app.Context) ) {
// Total FAIL!
}
}
public void Dispose() {
// Clean up the mess, if needed.
}
}
public class ServiceProvider {
public static bool Authenticate( HttpContext context ) {
// For the example we are going to create a nothing user
// say he is awesome, pass him along through and be done.
// The heavy lifting of the auth process will go here
// in the real world.
HttpContext.Current.User = ServicePrincipal.Default;
return true;
}
}
Step 3a. [edit]
Here's the different libs you'll be "using"
using System.Security.Principal;
using System.Web;
Just wanted to throw those in. I hate it when folks leave them out. :)
Step 4.
Add the following to your web config. Please note I am including the surrounding structure, for example the "configuration" tag... It's just a road map, if you already have a "configuration" tag don't add the other or IIS gets upset with you.
<configuration>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
<add name="ServiceCredentialModule" type="{Namespace}.ServiceModule"/>
</modules>
</system.webServer>
<configuration>
Please note that the Namespace in {Namespace}.ServiceModule is the Namespace you put the classes from Step 3 into.
...and that's pretty much it.

Resources