---
title: "Secure Authentication and Authorization in SPAs: OpenID Connect and OAuth 2.0 with PKCE"
source: https://bitvaria.com/en/secure-auth-spa-oauth
---

19\. Okt. 2024 · Daniel Schleipfer · [IT Security](https://bitvaria.com/category/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.](https://bitvaria.com/_astro/fingerprint.Bkjct1ta.jpeg)

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 solid 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 PKCE is 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 because the access token is no longer delivered through the redirect URL: the SPA first exchanges an authorization code together with the code verifier for the token. Where that token lives afterwards is an architectural decision, not part of PKCE. To keep it out of the browser entirely, put a backend for frontend in front of it that holds the token and hands the SPA only a session cookie.

### Detailed Process

#### 1\. Initial Authentication Request to the Authorization Server

```mermaid
%%{init: {'theme':'dark'}}%%
sequenceDiagram
    participant User
    participant SPA
    participant AuthServer as Authorization Server

    SPA->>SPA: Generate Code Verifier and Code Challenge
    User->>SPA: User initiates login
    SPA->>AuthServer: Redirect to /authorize with Code Challenge
```

A Code Verifier and Code Challenge are generated for PKCE, and the Code Challenge is sent to the Authorization Server.

```javascript
function generateRandomString(length) {
    const array = new Uint8Array(length);
    window.crypto.getRandomValues(array);
    return Array.from(
        array,
        (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

```mermaid
%%{init: {'theme':'dark'}}%%
sequenceDiagram
    participant SPA
    participant Backend
    participant AuthServer as Authorization Server

    SPA->>Backend: Send Authorization Code and Code Verifier
    Backend->>AuthServer: POST /token with Code and Code Verifier
    AuthServer->>Backend: Returns Access Token and Refresh Token
    Backend->>Backend: Store 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.

#### 3\. Creating a Short-Lived Session Token and Setting it in an HTTP-Only Cookie

```mermaid
%%{init: {'theme':'dark'}}%%
sequenceDiagram
    participant SPA
    participant Backend

    Backend->>SPA: Set HTTP-Only Session Token Cookie
```

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

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

```mermaid
%%{init: {'theme':'dark'}}%%
sequenceDiagram
    participant SPA
    participant Backend

    Backend->>SPA: Provide CSRF Token
    SPA->>Backend: Include CSRF Token in requests
    Backend->>Backend: Verify CSRF Token on each request
```

Additionally, the backend generates a CSRF token and passes it to the SPA, either in a non-HTTP-Only cookie or 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

```mermaid
%%{init: {'theme':'dark'}}%%
sequenceDiagram
    participant SPA
    participant Backend

    SPA->>Backend: Authenticated API request with Session Token and CSRF Token
    Backend->>Backend: Verify Session and CSRF Token
    Backend->>SPA: Return 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

```mermaid
%%{init: {'theme':'dark'}}%%
sequenceDiagram
    participant SPA
    participant Backend
    participant AuthServer as Authorization Server

    Backend->>AuthServer: Use Refresh Token to get new Access Token
    AuthServer->>Backend: Return new Access Token
    Backend->>Backend: Update 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.

-   [OAuth 2.0](https://bitvaria.com/tag/oauth-2-0/)
-   [PKCE](https://bitvaria.com/tag/pkce/)
-   [Authentication](https://bitvaria.com/tag/authentication/)
-   [Security Architecture](https://bitvaria.com/tag/security-architecture/)

Share:

[Back to Blog](https://bitvaria.com/blog/)

## Terms explained on this page

- **PKCE**: Proof Key for Code Exchange. Enhances security when creating and using the access token
- **Code Verifier**: A random, hard-to-predict string used once in the authentication process.
- **Code Challenge**: The Code Verifier is hashed with SHA-256 and base64-url-encoded to create a Code Challenge. The Code Challenge is used only once in the authentication process and does not need to be stored.
- **HTTP-Only cookie**: HTTP-Only cookies cannot be accessed by JavaScript and are automatically sent with each request.
- **CSRF token**: The CSRF token prevents cross-site request forgery attacks. The CSRF token is a random, hard-to-predict string (often between 32 and 128 characters long) generated for each session or user interaction.
- **non-HTTP-Only cookie**: This cookie must be accessible by JavaScript so that it can be explicitly added to the request header.
