Thursday, July 14, 2016

Encrypt / Decrypt data at db context

Override savechanges method and object materialized methods to automatically encrypt/decrypt data without manually doing it.
Here, the custom attribute [Encrypted] is used to mark a property as encrypted or secured data.
Use your way of security for encryption and decryption.

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.Objects;
using System.Linq;
using System.Threading.Tasks;
using System.Web;

public class BaseDbContext : DbContext
    {
        public BaseDbContext() : base("mydbcontext")
        {
            ((IObjectContextAdapter)this).ObjectContext.ObjectMaterialized += new ObjectMaterializedEventHandler(ObjectMaterialized);
        }

        public override int SaveChanges()
        {
            var contextAdapter = ((IObjectContextAdapter)this);

            contextAdapter.ObjectContext.DetectChanges();

            var pendingEntities = contextAdapter.ObjectContext.ObjectStateManager
                .GetObjectStateEntries(EntityState.Added | EntityState.Modified)
                .Where(en => !en.IsRelationship).ToList();

            foreach (var entry in pendingEntities) //Encrypt all pending changes
                EncryptEntity(entry.Entity);

            int result = base.SaveChanges();

            foreach (var entry in pendingEntities) //Decrypt updated entities for continued use
                DecryptEntity(entry.Entity);

            return result;
        }

        void ObjectMaterialized(object sender, ObjectMaterializedEventArgs e)
        {
            DecryptEntity(e.Entity);
        }

        private void EncryptEntity(object entity)
        {
            //Get all the properties that are encryptable and encrypt them
            var encryptedProperties = entity.GetType().GetProperties()
                .Where(p => p.GetCustomAttributes(typeof(Encrypted), true).Any(a => p.PropertyType == typeof(String)));
            foreach (var property in encryptedProperties)
            {
                string value = property.GetValue(entity) as string;
                if (!String.IsNullOrEmpty(value))
                {
                    property.SetValue(entity, SimpleEncrypt(value));
                }
            }
        }

        private void DecryptEntity(object entity)
        {
            //Get all the properties that are encryptable and decyrpt them
            var encryptedProperties = entity.GetType().GetProperties()
                .Where(p => p.GetCustomAttributes(typeof(Encrypted), true).Any(a => p.PropertyType == typeof(String)));

            foreach (var property in encryptedProperties)
            {
                string encryptedValue = property.GetValue(entity) as string;
                if (!String.IsNullOrEmpty(encryptedValue))
                {
                    Entry(entity).Property(property.Name).OriginalValue = SimpleDecrypt(encryptedValue);
                    Entry(entity).Property(property.Name).IsModified = false;
                }
            }
        }

        public string SimpleEncrypt(string value)
        {
            // your encryption logic
            return value;
        }

        public string SimpleDecrypt(string value)
        {
            // your decryption logic
            return value;

        }
    }

    public class Encrypted : Attribute { }

Enable simple Token based authorization in Web API

Set /token endpoint for request token

using Microsoft.Owin;
using Microsoft.Owin.Cors;
using Microsoft.Owin.Security.OAuth;
using Owin;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IdentityModel.Tokens;
using System.Linq;
using System.Web;
using System.Web.Http;
using MyApp.Providers;

[assembly: OwinStartup(typeof(MyApp.Startup))]
namespace MyApp
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            // Allow cross domain access
            app.UseCors(CorsOptions.AllowAll);

            OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
            {
                AllowInsecureHttp = true,
                TokenEndpointPath = new PathString("/token"),
                AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
                Provider = new MyAuthServerProvider(),
            };

            // Token Generation
            app.UseOAuthAuthorizationServer(OAuthServerOptions);
            app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());

            // Important: Enable Suppress redirect to login page if token is invalid
            app.Use((context, next) =>
            {
                HttpContext.Current.Response.SuppressFormsAuthenticationRedirect = true;
                return next.Invoke();
            });

            var config = new HttpConfiguration();
            WebApiConfig.Register(config);
            app.UseWebApi(config);
        }
    }
}

Define MyAuthServerProvider()

using Microsoft.Owin.Security.OAuth;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using System.Security.Claims;
using MyApp.Models;
using Newtonsoft.Json.Linq;

namespace MyApp.Providers
{
    public class MyAuthServerProvider : OAuthAuthorizationServerProvider
    {
        public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
        {
            // validate username by username/password using your custom logic
            bool isValid = ValidateUser(context.UserNamecontext.Password);

            if (isValid)
            {
                var identity = new ClaimsIdentity(context.Options.AuthenticationType);
            identity.AddClaim(new Claim("sub", context.UserName)); 
            identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
            identity.AddClaim(new Claim(ClaimTypes.Role, "user"));

            context.Validated(identity);
            }
            else
            {
                context.SetError("invalid_grant", "The user name or password is incorrect.");
                return;
            }
            
        }
    }
}

Tuesday, April 26, 2016

Prevent Redirect to Login page for Unauthorized Calls to Web API

If you have implemented token based authorization to your Web API which resides in an ASP.NET MVC application, you would probably come across this situation. Because, you have login page to MVC application which will be shown if you are not logged in. In case of Web API calls, you are obviously not logged in, but credentials or access token is passed in the request header.

Just include the following code fragment in the startup class just before the app.UseWebApi(config); line, to prevent this redirect for unauthorized Web API calls such the access token is not provided or got expired.

            // Important: Enable Suppress redirect to login page if token is invalid
            app.Use((context, next) =>
            {
                HttpContext.Current.Response.SuppressFormsAuthenticationRedirect = true;
                return next.Invoke();
            });

            var config = new HttpConfiguration();
            WebApiConfig.Register(config);
            app.UseWebApi(config);

System.Web.Http.Authorize vs System.Web.Mvc.Authorize

You must use System.Web.Http.Authorize against an ApiController (Web API controller) and System.Web.Mvc.Authorize against a Controller (MVC controller).

Thanks to: Badri
Source: http://stackoverflow.com/a/19156530/1931848

Friday, April 22, 2016

Bug in MySql .NET Connector 6.9.8: Nested transactions are not supported

There is bug in MySQL connector for .NET version 6.9.8, that it will throw an error: “Nested transactions are not supported”


You have likely run into this bug in MySQL Connector/NET.
What triggers this bug:
  1. Code calls for execution of query A
  2. Transaction 1 for query A is started
  3. Query A is executed and causes an error in MySQL
  4. Transaction 1 is NOT rolled back
  5. Code calls for execution of query B
  6. Transaction 2 for query B is started
  7. MySQL Connector/NET throws the exception
The bug is point 4: transaction 1 is left open after an error (or at least the connector is still convinced it's left open). Because of connection pooling, the code calling for query A and query B can be completely unrelated. Also, if the time between point 4 and 5 is long enough, the transaction is rolled back, hence the rareness and randomness.
Unfortunately there's no fix by MySQL yet. The only workaround that I know of is downloading the source code of Connector/NET and fixing/building it yourself.
Otherwise, stick to the older stable version 6.8.7
Thanks: AronVanAmmers
Reference: http://stackoverflow.com/a/29265962/1931848

Tuesday, April 19, 2016

Single Sign On (SSO) with ASP.NET Web Application

If you want to have single sign in for multiple web applications, you have to share authentication data between.
You can achieve by using Forms Authentication with Same Machine key in all applications.
Machines keys are used to encrypt and decrypt cookies. Therefore, you have to set same machine key deliberately on each application's web.config to ensure single sign on works as expected.

Machine key contains 3 elements namely, validationkey, decryptionkey and validation (algorithm, eg. SHA1).

References:
http://www.developer-corner.com/blog/2006/10/01/aspnet-single-sign-on/
http://www.dotnetfunda.com/articles/show/979/aspnet-authentication-and-authorization
http://www.codeproject.com/Articles/6586/Single-sign-on-across-multiple-applications-in-ASP

Tuesday, April 12, 2016

sql_mode "only_full_group_by" in MySql

If you are getting one of the following errors, you have this problem;

  1. Error Code: 1055. 'test.somefield' isn't in GROUP BY
  2. SELECT list is not in GROUP BY clause and contains nonaggregated column 'test.somefield' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by
Check the current sql_mode in your mysql server with the following command to see if the only_full_group_by is enabled;

SELECT @@sql_mode;

This will give a list of sql_modes enabled in the server. Example as follows,

ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION

As you can see from the above result, you have a value called "only_full_group_by". That means if you have a group by clause in your sql statement, only the fields which are listed in a group by clause can be used in the select clause or order by clause, unless you aggregate the fields. i.e, using aggregate functions such as min(), max() etc

NOTE: As of MySQL Server version 5.7.5, the only_full_group_by is enabled in the sql_mode by default.

Temporary Fix:
Temporarily you can fix this issue by disabling this check in the server by globally and/or only in the active session by issuing the following command respectively.

SET GLOBAL sql_mode="STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION";

SET SESSION sql_mode="STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION";

Permanent Fix:
- Make sure nonagregated field list in select or order by clause are in group by
- Restructure the sql statement