3

I have model class:

 public class Person {
        public int Id { get; set; }
  ...
 }

and to see details about a person user can guess its' id

http://localhost:17697/Person/Details/2

they're just consecutive integers.

How can I tell Entity Framework to shuffle those ID to make them harder to guess?

ASP NET
  • 39
  • 1
  • 3
  • If you don't want predictable ids use a guid. This also raises the question why don't you want predictable ids. What are you trying to prevent as the only requirement for a primary is that it is unique. – Ben Robinson Sep 15 '14 at 17:58

4 Answers4

6

If you don't want predictable IDs then you could use a Guid instead of int. "Shuffling" would over-complicate the process and it's not going to give you any protection.

Remember that if you're trying to secure a url, write proper security using authorization and filters. Security through obscurity does not actually secure anything

jamesSampica
  • 12,230
  • 3
  • 63
  • 85
2

Personally, I utilize slugs in my URLs, rather than ids. Something like:

http://localhost:17697/Person/Details/john-doe

You then pull the object based on the slug:

db.People.SingleOrDefault(m => m.Slug == slug);

However, "security by obscurity" is not a good game plan. Making the ids "harder to guess", doesn't solve the problem of people accessing it who shouldn't. If the details should be protected, then implement authentication and specify an authorization policy for the action.

Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
  • Authorization is done, users will be able to get into details of person I just want make it harder to remember the ids. – ASP NET Sep 15 '14 at 18:01
1

Late to the party, but since there isn't much about using HashIds within ASP.NET MVC I'll share my solution using a custom ModelBinder and a BaseModel class. The end route looks something like /example/voQ/details.

First you need a model, that your existing models can extend from and generate a HashId;

public class abstract BaseModel
{
   private static readonly Hashids __hashId = new Hashids("seed", 2);

   public Id { get; set; }

   [NotMapped]
   public HashId
   {
     get { return BaseModel.__hashId.Encode(this.Id); }
   }
}

The binder needs registering in Global.asax for each model:

ModelBinders.Binders.Add(typeof(ExampleModel), new ControllerModelBinder<ExampleModel>()); 

Then the action can use the model directly without worrying about the hash id:

public ActionResult Details(ExampleModel model)
{
  return View(model);
}

Setting up a link is the same, but rather than passing the Id, you need to use the HashId property from the BaseModel.

@Url.Action("Details", new { id = item.HashId })

Finally the the model binder:

public class ControllerModelBinder<T> : DefaultModelBinder
  where T : BaseModel
{

  public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
  {
    if (bindingContext.ModelType == typeof(T))
    {
      string hash = bindingContext.ValueProvider.GetValue("id").RawValue.ToString();

      if (!string.IsNullOrWhiteSpace(hash))
      {
        int id = HashIdHelper.ToInt(hash);

        if (id > 0)
        {
          using (ApplicationContext context = new ApplicationContext())
          {
            DbRawSqlQuery<T> query = context.Database.SqlQuery<T>(string.Format("SELECT * FROM {0} WHERE id = @Id LIMIT 1", EntityHelper.GetTableName<T>(context)), new MySqlParameter("@Id", id));

            try
            {
              T model = query.Cast<T>().FirstOrDefault();

              if (model != null)
              {
                return model;
              }
            }
            catch (Exception ex)
            {
              if (ex is ArgumentNullException || ex is InvalidCastException)
              {
                return base.BindModel(controllerContext, bindingContext);
              }

              throw;
            }
          }
        }
      }
    }

    return base.BindModel(controllerContext, bindingContext);
  }
}
sixones
  • 1,953
  • 4
  • 21
  • 25
0

You can use HttpServerUtility.UrlTokenEncode and HttpServerUtility.UrlTokenDecode

Encode uses base64 encoding, but replaces URL unfriendly characters.

There's a similar answer in a previous SO question. See the accepted answer. MSDN Reference

Community
  • 1
  • 1
Tushar Gupta
  • 15,504
  • 1
  • 29
  • 47