· IT Security  · 6 min read

Secure Authentication and Authorization in SPAs: OpenID Connect and OAuth 2.0 with PKCE

A detailed guide to secure authentication and authorization strategies for Single-Page Applications (SPAs). OAuth 2.0 and OpenID Connect are combined with PKCE to ensure secure and efficient access control.

A detailed guide to secure authentication and authorization strategies for Single-Page Applications (SPAs). OAuth 2.0 and OpenID Connect are combined with PKCE to ensure secure and efficient access control.

Introduction

Modern web applications, especially Single-Page Applications (SPAs), place unique demands on authentication and authorization mechanisms. Standards like OAuth 2.0 and OpenID Connect are increasingly recognized as best practices, providing a robust foundation for secure authentication processes.

What is OAuth 2.0?

OAuth 2.0 is an industry-standard protocol for authorization, allowing applications to securely access resources on behalf of a user without directly handling their credentials. Instead, applications receive an access token, which defines and limits the scope of access, offering a more secure approach particularly beneficial for dynamic web applications.

What is OpenID Connect?

OpenID Connect builds on OAuth 2.0, adding an additional authentication layer. While OAuth 2.0 is primarily for authorization, OpenID Connect enables secure and standardized authentication. This is achieved through an ID token that provides information about the user’s identity. For instance, an application that needs to securely verify not only access to specific resources but also the user’s identity, such as profile information or a unique user ID, would benefit from OpenID Connect.

Best Practice for SPAs: Authorization Code Flow with PKCE

For SPAs, the Authorization Code Flow with PKCEis the recommended authentication flow. This method enhances security by introducing an additional verification layer between client and server. The previously recommended Implicit Flow for SPAs is now outdated due to security risks, such as exposing access tokens directly in the browser. The PKCE flow prevents this through a two-step token issuance, keeping access tokens securely stored in the backend.

Detailed Process

1. Initial Authentication Request to the Authorization Server

Authorization ServerSPAUserAuthorization ServerSPAUserGenerate Code Verifier and Code ChallengeUser initiates loginRedirect to /authorize with Code Challenge

A Code Verifierand Code Challengeare generated for PKCE, and the Code Challenge is sent to the Authorization Server.

function generateRandomString(length) {
    const array = new Uint8Array(length);
    window.crypto.getRandomValues(array);
    return Array.from(
        (_, byte) => ('0' + byte.toString(16)).slice(-2)
        ).join('');
}

async function generateCodeChallenge(codeVerifier) {
    const encoder = new TextEncoder();
    const data = encoder.encode(codeVerifier);
    const digest = await window.crypto.subtle.digest('SHA-256', data);
    return btoa(String.fromCharCode(...new Uint8Array(digest)))
        .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}

const codeVerifier = generateRandomString(64);
const codeChallenge = await generateCodeChallenge(codeVerifier); 

The SPA initiates the authentication process by redirecting the user to the Authorization Server’s login page, including the generated challenge.

https://auth-server.com/authorize
?response_type=code
&client_id=your-client-id
&redirect_uri=https://your-app.com/callback
&scope=profile email
&state=xyz123
&code_challenge=generatedCodeChallenge
&code_challenge_method=S256

2. Receiving the Authorization Code and Requesting the Access Token

Authorization ServerBackendSPAAuthorization ServerBackendSPASend Authorization Code and Code VerifierPOST /token with Code and Code VerifierReturns Access Token and Refresh TokenStore Access and Refresh Tokens securely

Upon successful authentication, the Authorization Server redirects the user to the SPA’s redirect_uri with the Authorization Code. The SPA extracts the Authorization Code and sends it along with the Code Verifier in a secure POST request to the backend.

POST /token HTTP/1.1
Host: auth-server.com
Content-Type: application/x-www-form-urlencoded

client_id=your-client-id
&code=abcd1234
&redirect_uri=https://your-app.com/callback
&grant_type=authorization_code
&code_verifier=originalCodeVerifier

The backend securely stores the tokens and does not pass them to the SPA.

BackendSPABackendSPASet HTTP-Only Session Token Cookie

The backend creates a short-lived session token and sets it as an HTTP-Only cookiethat is used only over secure connections (HTTPS).

4. CSRF-Token für zusätzliche Sicherheit

BackendSPABackendSPAProvide CSRF TokenInclude CSRF Token in requestsVerify CSRF Token on each request

Additionally, the backend generates a CSRF tokenand passes it to the SPA, either in a non-HTTP-Only cookieor as a <meta> tag. The SPA includes the CSRF token in every request header, allowing the backend to verify requests.

5. Authenticated Requests from the SPA to the Backend

BackendSPABackendSPAAuthenticated API request with Session Token and CSRF TokenVerify Session and CSRF TokenReturn protected resources

The SPA uses the session token for authorized requests to the backend. The HTTP-Only cookie with the session token is automatically sent with each request. The CSRF token is also sent and verified by the backend.

6. Token Refresh on Access Token Expiry

Authorization ServerBackendSPAAuthorization ServerBackendSPAUse Refresh Token to get new Access TokenReturn new Access TokenUpdate Session Token and set new cookie

The backend manages access token renewal using the refresh token when the access token expires. If the session or access token expires, the backend automatically requests a new access token and updates the session token.


Security Benefits

  1. Token Management in the Backend: The access token and refresh token are stored in the backend, protecting them from frontend attacks.
  2. Protection via HTTP-Only Cookie: The session token in an HTTP-Only cookie is secure from JavaScript and XSS attacks.
  3. CSRF Protection: The CSRF token prevents cross-site request forgery attacks.
  4. Short-Lived Session Token: The short lifespan of the session token limits access to the backend in case of compromise.

Threat Modeling and Security Risks without PKCE

Without the PKCE mechanism, attackers could intercept the Authorization Code via “Authorization Code Injection” or “Code Interception” attacks to illegitimately access sensitive data. By introducing an additional validation layer between client and authorization server, PKCE ensures that only the initially authorized client has access. Generating a unique code verifier and hashing it to a code challenge makes PKCE a reliable solution for SPAs and enhances backend security.

Best Practices for Implementation

A careful implementation of the PKCE flow requires adherence to specific security requirements:

  1. Secure Storage of the Code Verifier: The code verifier should be securely stored throughout the authentication process and discarded afterward to prevent unauthorized access.
  2. Use of Secure Connections: All communication between the SPA, backend, and authorization server should occur over HTTPS to prevent eavesdropping attacks.
  3. Restrictions for Redirect URIs: Only predefined and trusted redirect URIs should be allowed in the OAuth configuration to prevent possible redirection to malicious domains.

Following these best practices minimizes the risk of security vulnerabilities and builds a stable authentication architecture.

Additional Security Measures

In addition to PKCE implementation, further security measures can be employed:

  • Content Security Policy (CSP): Setting a CSP restricts sources for scripts and resources, preventing the execution of potentially harmful content.
  • Subresource Integrity (SRI): SRI hashes ensure the integrity of external resources, allowing content to load only if it matches the specified hash, which protects against tampering by malicious third parties.
  • Secure Storage: Any token storage on the client (if required) should use secure web APIs like sessionStorage or secure cookies and be strictly secured with httpOnly and SameSite attributes.

These additional security measures complement the PKCE-based authorization code flow and ensure that the SPA is optimally protected against attacks.

Back to Blog

Related Posts

View All Posts »