SQL Server / Intermediate / Lesson 12
Identities, permissions, and least privilege
Design application access using the correct SQL Server security boundaries. Separate authentication, database authorization, parameter safety, encryption, and audit responsibilities.
On this page
Lesson goals
- Distinguish a login, user, and role.
- Grant narrowly scoped permissions.
- Explain why parameterization and authorization are complementary.
Separate server identity from database access
A login authenticates at the instance level in the traditional SQL Server model, while a database user represents access inside a database. Roles collect permissions for users. Contained-database models have additional identity options, so diagnose the actual configuration instead of assuming every user maps to the same login pattern. A successful connection does not prove permission to read a business table.
Use distinct service identities for separate applications and environments. Humans should not share the application's credential for convenience. Prefer the organization's managed authentication mechanism where supported, and keep secrets in an appropriate secret store. The course never needs a real credential: security design can be reasoned about using synthetic principal names and reviewed scripts.
Grant the smallest useful capability
An application that only executes an order procedure need not become db_owner. Grant a database role EXECUTE on the required object, then add the service user to that role through an authorized deployment process. The conceptual example assumes the procedure and user already exist in a training database. It intentionally contains no password or login creation command.
Schema-level grants can simplify a deliberately bounded module surface, but future objects in that schema inherit the broader exposure. Object-level grants are narrower and require more maintenance. Choose the boundary from the application's responsibilities. Review effective permissions, ownership chains, and any dynamic SQL separately; a static module permission story does not automatically extend to every dynamic statement.
-- Conceptual authorization migration for a training database.
CREATE ROLE OrderExecutor;
GRANT EXECUTE ON OBJECT::sales.PlaceOrder TO OrderExecutor;
ALTER ROLE OrderExecutor ADD MEMBER TrainingAppUser;
The role can execute this module; table access and dynamic SQL behavior still require review. This script is not executed.
Inspect your effective context
Read-only metadata can identify current database identity and permission checks without exposing secrets. HAS_PERMS_BY_NAME returns whether the current context has the named permission, subject to valid object and permission inputs. Metadata visibility itself follows security rules; an absent catalog row may mean insufficient visibility rather than proof that an object does not exist.
Test the actual service identity, not only an administrator's session. Positive checks show what should work, while negative checks confirm forbidden operations fail. A role intended for reporting should not modify orders, and a write module should not grant arbitrary table alteration. Keep permission tests tied to the application's supported capabilities rather than granting broad roles until an error disappears.
SELECT ORIGINAL_LOGIN() AS original_login,USER_NAME() AS database_user;
SELECT HAS_PERMS_BY_NAME(N'sales.PlaceOrder',N'OBJECT',N'EXECUTE')
AS can_execute;
Identity strings and permission results depend on the session. Do not publish real principal names in public incident reports.
Keep security controls distinct
Parameterization prevents supplied values from becoming SQL command structure. It does not decide whether a user is allowed to access customer 42. Tenant and object authorization must be applied independently, with a trusted identity-to-tenant mapping. A bound CustomerId is still unsafe if any caller can choose another tenant's identifier.
Transport encryption protects data in transit; storage encryption protects a different boundary; backups and logs need their own access controls. None replaces authorization or parameter binding. Audit records should capture useful identity, action, target, and outcome without storing passwords, complete connection strings, or unnecessary personal data. Security review follows the whole data path rather than a single SQL keyword.
Review access over its lifecycle
Permissions accumulate unless ownership and review are explicit. Record why a grant exists, which deployment owns it, and how it will be removed when a feature is retired. Separate emergency administration from routine service access and review temporary grants after an incident. A permanent sysadmin workaround is a security defect even if it resolves an immediate permission error.
When debugging a failure, first identify whether authentication, database mapping, object permission, or module behavior failed. Preserve the exact error number in a restricted diagnostic record. Do not request a secret from a learner or copy production configuration into a training script. A useful fix narrows the failing capability and verifies the intended access boundary.
Common errors and fixes
The SELECT permission was denied.
- Why it happens
- The current database user lacks the required capability or an effective restriction applies.
- How to fix it
- Grant the approved role only the intended object capability.
- Prevent it next time
- Test with the real service identity.
Login succeeded but database access failed.
- Why it happens
- Authentication and database authorization were conflated.
- How to fix it
- Verify the target database, user mapping, and availability with an administrator.
- Prevent it next time
- Document identity boundaries per environment.
A bound identifier exposed another tenant.
- Why it happens
- Authorization trusted caller-selected ownership.
- How to fix it
- Enforce trusted tenant scope in every applicable operation.
- Prevent it next time
- Test cross-tenant denial explicitly.
Check your understanding
1. A reporting service needs one view. Should it receive db_owner?
Show worked solution
No. Grant SELECT on the approved view through a dedicated role, review its underlying permission behavior, and confirm that direct table writes and schema changes are denied.
2. Does binding @TenantId prove tenant authorization?
Show worked solution
No. Binding keeps the value as data. The application must derive or validate tenant membership from trusted identity, and the database operation must consistently enforce that scope.
Key takeaways
- Authentication, authorization, and injection prevention are separate controls.
- Roles should represent bounded capabilities.
- Verify both permitted and forbidden operations.