Skip to content
HomeWorkProcessAboutBlogContact[email protected]
← Back to blog

User Authentication and Authorization: Complete Implementation Guide

User Authentication and Authorization: Complete Implementation Guide. user authentication Insights from The Proper Motion Company.

Authentication and authorization are the gatekeepers of every application. Get them wrong, and you expose user data, enable account takeovers, and face regulatory consequences. Get them right, and your users barely notice they exist – which is exactly the point. Yet the implementation details are deceptively complex, with dozens of protocols, token formats, session strategies, and edge cases to navigate.

This guide covers the practical engineering of authentication and authorization systems, from protocol selection through production hardening.

Authentication Protocols: Choosing the Right Foundation

The choice of authentication protocol depends on your application architecture, user base, and security requirements. Here are the protocols that matter in 2023 and beyond.

OAuth 2.0 with OpenID Connect (OIDC) is the standard for applications that need third-party login (Sign in with Google, GitHub, etc.) or that serve as an identity provider for other applications. OAuth 2.0 handles authorization (what can this app access?), while OIDC adds an identity layer on top (who is this user?). The Authorization Code flow with PKCE (Proof Key for Code Exchange) is the recommended flow for both web and mobile applications. The Implicit flow is deprecated due to token exposure in browser URLs.

SAML 2.0 remains dominant in enterprise SSO. If your application sells to companies that use Okta, Azure AD, or OneLogin, you will need a SAML Service Provider implementation. SAML uses XML-based assertions exchanged between the Identity Provider (IdP) and your Service Provider (SP). The protocol is verbose but battle-tested. Libraries like passport-saml (Node.js), python3-saml, or Spring Security SAML handle the heavy lifting.

Passwordless authentication is gaining traction as a primary authentication method rather than just an alternative. Email magic links, SMS OTPs, and WebAuthn/FIDO2 hardware keys all eliminate the password entirely. WebAuthn is the most secure option – it uses public-key cryptography bound to the user’s device, making phishing virtually impossible. Passkeys (the consumer-friendly branding of WebAuthn) are now supported by Apple, Google, and Microsoft across their platforms.

API key authentication is appropriate for server-to-server communication where there is no human user in the loop. API keys should be treated as secrets: generated with sufficient entropy (at least 256 bits), transmitted only over HTTPS, stored hashed in your database (not plaintext), and rotatable without downtime.

Related: Privacy-First Software Development as Competitive Advantage

Token Management: JWTs, Sessions, and Refresh Strategies

Once a user is authenticated, you need a mechanism to maintain their authenticated state across requests. The two dominant approaches are server-side sessions and JSON Web Tokens (JWTs).

Server-side sessions store a session identifier in a cookie and keep the session data (user ID, roles, permissions) in a server-side store like Redis or PostgreSQL. The advantages are simplicity and immediate revocability – to log a user out, delete their session record. The disadvantage is that every request requires a round-trip to the session store, which adds latency and creates a scaling bottleneck if the store becomes unavailable.

JWTs encode the session data directly in the token, which is signed (and optionally encrypted) so the server can verify it without a database lookup. JWTs are stateless, making them ideal for distributed architectures where requests may hit different backend instances. However, JWTs cannot be revoked before their expiration time without maintaining a blacklist – which reintroduces server-side state and negates much of the stateless benefit.

The practical compromise used by most production systems is a dual-token strategy:

  1. Access token: A short-lived JWT (15 minutes is a common lifetime) containing the user’s identity and permissions. Sent with every API request in the Authorization header.
  2. Refresh token: A long-lived opaque token (7 to 30 days) stored in a server-side database. Used exclusively to obtain new access tokens when the current one expires. Refresh tokens are rotated on each use (one-time use) and can be revoked instantly by deleting the database record.

Store access tokens in memory on the client (not localStorage, which is vulnerable to XSS). Store refresh tokens in an HttpOnly, Secure, SameSite=Strict cookie. This combination minimizes the attack surface while maintaining a good user experience – users stay logged in for days without re-entering credentials, but a compromised access token is only useful for 15 minutes.

Authorization Models: RBAC, ABAC, and ReBAC

Authorization determines what an authenticated user is allowed to do. The three predominant models are Role-Based Access Control (RBAC), Attribute-Based Access Control (ABAC), and Relationship-Based Access Control (ReBAC).

RBAC assigns users to roles (admin, editor, viewer), and roles are granted permissions (create:post, edit:post, delete:post). RBAC is simple to implement and understand, making it the right choice for applications with well-defined, relatively static permission structures. A typical implementation stores role-permission mappings in a database table and checks permissions in middleware before each protected operation.

The limitation of RBAC is that it does not handle contextual permissions well. “Editors can edit posts” is easy. “Editors can edit posts, but only posts they created, and only within 24 hours of creation” requires either proliferating roles (editor-own-recent, editor-own-old, editor-all) or augmenting RBAC with additional logic.

ABAC evaluates policies based on attributes of the user, the resource, the action, and the environment. A policy might state: “Allow if user.department == resource.department AND action == ‘read’ AND environment.time is within business hours.” ABAC is extremely flexible but complex to implement and audit. AWS IAM policies are a real-world example of ABAC at scale.

ReBAC defines permissions based on relationships between entities. “User A can edit Document X because User A is a member of Team Y, and Team Y owns Document X.” Google Zanzibar is the canonical ReBAC system, and open-source implementations like OpenFGA and SpiceDB bring this model to application developers. ReBAC excels in collaborative applications where sharing, teams, and organizational hierarchies create complex permission graphs.

For most applications, start with RBAC and extend to ABAC or ReBAC only when your permission requirements outgrow simple role assignments. Whichever model you choose, enforce authorization at the API layer, never only at the UI layer. A user who bypasses the UI (using curl, Postman, or a script) should hit the same authorization checks.

See also: Building Secure Web Applications: A Complete Security Guide

Securing the Authentication Flow Against Common Attacks

A correctly implemented authentication flow must defend against a specific set of well-documented attacks.

Credential stuffing: Attackers use username/password pairs leaked from other breaches to attempt logins on your site. Defend with rate limiting (no more than 5 failed attempts per account per 15 minutes), CAPTCHA after repeated failures, and breach-password detection (check submitted passwords against the Have I Been Pwned database via their k-anonymity API).

Session fixation: An attacker sets a known session ID in the victim’s browser before authentication, then uses that session ID after the victim logs in. Defend by always generating a new session ID upon successful authentication and invalidating the old one.

Cross-site request forgery (CSRF): An attacker tricks an authenticated user’s browser into making an unintended request. Defend with CSRF tokens for cookie-based sessions (the Synchronizer Token Pattern or the Double Submit Cookie pattern) and SameSite cookie attributes. Token-based APIs using the Authorization header are inherently CSRF-resistant because browsers do not automatically attach custom headers.

Token theft via XSS: If an attacker injects JavaScript into your application, they can steal tokens stored in localStorage or accessible via JavaScript. Defend by storing sensitive tokens in HttpOnly cookies (inaccessible to JavaScript), implementing a strict Content Security Policy (CSP), and sanitizing all user input.

Brute force on password reset: Password reset flows are often weaker than the login flow itself. Use tokens with at least 128 bits of entropy, expire them within 1 hour, allow single use only, and rate-limit reset requests to prevent enumeration.

Multi-Tenancy Authentication Patterns

SaaS applications serving multiple organizations need multi-tenant authentication that isolates tenant data while providing a seamless login experience.

Tenant identification can happen at three points: the subdomain (acme.yourapp.com), a path prefix (/org/acme/dashboard), or post-login via organization selection. Subdomain-based identification is the most user-friendly and allows tenant-specific SSO configurations, but it requires wildcard DNS and TLS certificates.

Tenant-specific IdP configuration is essential for enterprise customers. Company A may require SAML SSO through their Okta instance, Company B may use Azure AD via OIDC, and Company C may be fine with email/password. Your authentication system needs to look up the tenant, retrieve their IdP configuration, and redirect to the correct authentication flow.

Store IdP configurations per tenant in your database: entity ID, SSO URL, certificate (for SAML), client ID and client secret (for OIDC), and a flag indicating whether SSO is enforced (all users must use SSO) or optional (users can also use email/password).

Cross-tenant access prevention is the most critical security concern. Every database query, API response, and file access must be scoped to the authenticated user’s tenant. Implement this as middleware that injects a tenant filter into every query, not as a check that individual developers must remember to add. A single missing tenant filter is a data breach.

Production Hardening Checklist

Before shipping your authentication system, verify these items:

  • Passwords are hashed with bcrypt (cost factor 12+), scrypt, or Argon2id – never MD5, SHA-1, or SHA-256 alone.
  • All authentication endpoints are rate-limited independently.
  • Account lockout uses exponential backoff (1 minute, 5 minutes, 15 minutes) rather than permanent lockout, which enables denial-of-service.
  • Login, logout, password change, MFA enrollment, and permission changes generate audit log entries with timestamp, IP address, user agent, and success/failure status.
  • MFA is available for all users and enforced for admin roles. Support TOTP (Google Authenticator, Authy) at minimum; add WebAuthn for higher security.
  • Token expiration is enforced server-side, not just client-side.
  • Error messages do not reveal whether a username exists (“Invalid credentials” rather than “User not found” or “Wrong password”).
  • All cookies use Secure, HttpOnly, and SameSite attributes.
  • HTTPS is enforced for all endpoints with HSTS headers and a minimum max-age of one year.
  • Your authentication system has its own monitoring dashboard tracking login success rates, MFA adoption, and failed attempt spikes.

Authentication and authorization are foundational to every application, and mistakes here have outsized consequences. If you are building a system that needs robust identity management – whether a multi-tenant SaaS platform, a customer-facing portal, or an internal tool with complex permissions – contact our team. We design and implement authentication architectures that are secure, scalable, and tailored to your specific requirements.

// Related posts