Implementing PKCE with Angular, Spring Boot, and Okta
Securing Modern SPA Applications with OAuth 2.0 Authorization Code Flow
Implementing PKCE with Angular, Spring Boot, and Okta

Generated using AI tool(ChatGPT)
Securing Modern SPA Applications with OAuth 2.0 Authorization Code Flow
A few years ago, authentication was much simpler in most enterprise applications.
Most applications were server-rendered, where both frontend and backend lived together in the same application. The backend handled authentication completely — managing user sessions, storing client secrets securely, and communicating directly with authentication providers. Since everything sensitive stayed on the server side, traditional OAuth Authorization Code Flow worked without many concerns.
But application architecture has changed a lot over the years.
Modern web applications are no longer simple server-rendered systems. Today’s frontend applications are usually built using frameworks like Angular or React and communicate with backend APIs independently. This architecture gives us better scalability, cleaner separation between frontend and backend, and a much smoother user experience.
At the same time, it also introduces new security challenges around authentication and token handling.
Unlike backend applications, frontend SPA applications run entirely inside the browser. That means we can no longer safely store sensitive credentials like client secrets inside the application because browser code is publicly accessible.
This became a major concern in OAuth-based authentication flows.
Earlier, applications commonly used the OAuth Implicit Flow for browser applications, but over time it became clear that exposing tokens directly in the browser was not the safest approach. Authorization codes could potentially be intercepted, and frontend applications had no secure way to protect secrets like backend servers do.
That is exactly why PKCE was introduced.
What is PKCE?
PKCE (Proof Key for Code Exchange) is a security extension added to the OAuth 2.0 Authorization Code Flow.
It was introduced to secure public client applications such as:
- Angular applications
- React applications
- Mobile applications
- Single Page Applications (SPAs)
Unlike backend applications, frontend applications cannot securely store client secrets because all code is accessible in the browser. PKCE solves this problem by introducing a temporary secret generated during login.
Why PKCE is Important in Modern Applications
Traditional OAuth flows were vulnerable to authorization code interception attacks.
Without PKCE:
- User logs in
- Authorization server returns authorization code
- Frontend exchanges code for access token
If an attacker intercepts the authorization code, they may generate valid tokens.
PKCE prevents this by adding two important values:
ValuePurposecode_verifierRandom secret generated by frontendcode_challengeSHA-256 hashed version of verifier
The authorization server validates both values before issuing tokens.
Even if the authorization code is intercepted, it cannot be used without the original verifier.
High-Level Architecture
Our implementation includes:
ComponentTechnologyFrontend SPAAngular / ReactAuthorization ServerOktaBackend APISpring BootAuthentication ProtocolOAuth 2.0 with PKCE
PKCE Authentication Flow

Generated using AI tool(ChatGPT)
Step 1 — Configure Okta Application
First, create an application in Okta.
Create SPA Application
Inside Okta:
- Go to Applications
- Click Create App Integration
- Choose:
- Sign-in method → OIDC
- Application type → Single Page Application (SPA)
Configure Redirect URIs
Add your frontend redirect URLs:
For Angular:
http://localhost:4200/login/callback
For React:
http://localhost:3000/login/callback
Enable PKCE
Okta automatically enables PKCE for SPA applications.
After creation, note down:
- Client ID
- Issuer URL
Example:
Issuer: https://dev-123456.okta.com/oauth2/default
Client ID: 0oa123example
Step 2 — Configure Angular / React Application
Frontend applications use Okta SDKs to simplify authentication handling.
Angular Configuration
Install Okta SDK
npm install @okta/okta-angular @okta/okta-auth-js
Configure Okta
Create okta.config.ts:
import { OktaAuth } from '@okta/okta-auth-js';
export const oktaConfig = new OktaAuth({
issuer: 'https://dev-123456.okta.com/oauth2/default',
clientId: '0oa123example',
redirectUri: window.location.origin + '/login/callback',
scopes: ['openid', 'profile', 'email'],
pkce: true
});
What Each Property Does
PropertyPurposeissuerOkta authorization server URLclientIdUnique application identifierredirectUriURL Okta redirects to after loginscopesDefines user information accesspkceEnables PKCE Authorization Flow
React Configuration
Install Dependencies
npm install @okta/okta-react @okta/okta-auth-js
Configure Okta
const oktaConfig = {
issuer: 'https://dev-123456.okta.com/oauth2/default',
clientId: '0oa123example',
redirectUri: window.location.origin + '/login/callback',
scopes: ['openid', 'profile', 'email'],
pkce: true
};
What Happens During Login?
When the user clicks Login:
- Angular/React generates:
code_verifiercode_challenge
- User is redirected to Okta
/authorizeendpoint - User authenticates successfully
- Okta returns an authorization code
- Frontend sends:
- authorization code
- code verifier
- Okta validates both values
- Okta returns JWT tokens
Understanding JWT Tokens
After successful authentication, Okta returns:
TokenPurposeAccess TokenUsed to call backend APIsID TokenContains user identity informationRefresh TokenUsed to obtain new access tokens
JWT (JSON Web Token) contains encoded user claims such as:
{
"sub": "user123",
"email": "user@example.com",
"roles": ["USER"],
"exp": 1712345678
}
JWT tokens are digitally signed by Okta, allowing backend APIs to verify authenticity.
Step 3 — Configure Spring Boot Backend
The backend acts as a Resource Server and validates JWT tokens issued by Okta.
Add Dependencies
Gradle
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
Configure Application Properties
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://dev-123456.okta.com/oauth2/default
Spring Security automatically downloads Okta’s public keys and validates JWT signatures.
Spring Security Configuration
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/actuator/**").permitAll()
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 ->
oauth2.jwt()
);
return http.build();
}
}
How JWT Validation Works in Spring Boot
When the frontend calls a secured API:
Authorization: Bearer eyJhbGciOi...
Spring Security performs:
- JWT signature verification
- Expiration validation
- Issuer validation
- Claim extraction
- User authentication setup
Only valid tokens are allowed to access protected APIs.
Why PKCE is the Recommended Standard Today
PKCE is now considered the recommended OAuth flow for browser-based applications because:
No client secret required More secure than implicit flow Prevents authorization code theft Supported by Okta and modern OAuth providers Works seamlessly with Angular, React, and Spring Boot
Final Thoughts
As frontend applications continue moving toward SPA architectures, securing authentication flows becomes critical.
Using PKCE with Angular or React, Okta as the identity provider, and Spring Boot as the secured backend creates a modern and secure authentication architecture.
With minimal configuration, developers can implement enterprise-grade OAuth security while protecting users and APIs from common attack vectors.
References
메타데이터
- post_id
- 4dafd18910f0
- slug
- implementing-pkce-with-angular-spring-boot-and-okta-4dafd18910f0
- url
- https://medium.com/@manjududam84/implementing-pkce-with-angular-spring-boot-and-okta-4dafd18910f0
- canonical_url
- https://medium.com/@manjududam84/implementing-pkce-with-angular-spring-boot-and-okta-4dafd18910f0
- author_url
- https://medium.com/@manjududam84
- status
- ok
- fetched_at
- 2026-06-20 20:29:01