Giving users access to only stored procedure execute permissions is a common issue. It is a good security practice. In 2005, we can grant excecute permissions at the database level instead of having to do it on each and every stored procedure, which was a pain. So now people do this:
CREATE ROLE db_executor
GRANT EXECUTE TO db_executor
But I would definitely say that granting at the database level is not a best practice. You can just as easily grant at the schema level, and have better control.
GRANT EXECUTE on schema::dbo TO db_executor