@agalyaramadoss agalyaramadoss authored on 29 Nov
.mvn/ wrapper first commit 4 months ago
src register page developed 4 months ago
.gitattributes first commit 4 months ago
.gitignore first commit 4 months ago
EXAMPLES.md first commit 4 months ago
IMPLEMENTATION_SUMMARY.md first commit 4 months ago
README.md first commit 4 months ago
mvnw first commit 4 months ago
mvnw.cmd first commit 4 months ago
pom.xml added client test page 4 months ago
postman_collection.json first commit 4 months ago
test-oauth.sh added client test page 4 months ago
README.md

OAuth 2.1 Authorization Server

A complete OAuth 2.1 Authorization Server implementation built with Spring Boot 4.0.0 and Spring Authorization Server 7.0.0.

Features

OAuth 2.1 Compliant - Implements the latest OAuth 2.1 standard with enhanced security ✅ PKCE Required - Proof Key for Code Exchange (PKCE) enforced for all clients ✅ Refresh Token Rotation - Enhanced security with non-reusable refresh tokens ✅ OpenID Connect 1.0 - Full OIDC support for identity federation ✅ JWT Access Tokens - Stateless access tokens with RSA signature ✅ Public & Confidential Clients - Support for both client types

OAuth 2.1 Key Improvements

This server implements OAuth 2.1 which includes:

  1. PKCE Required: All authorization code flows must use PKCE (even confidential clients)
  2. No Implicit Flow: Removed for security (use authorization code + PKCE instead)
  3. No Resource Owner Password Flow: Removed for security
  4. Refresh Token Rotation: Refresh tokens are single-use and rotated on each use
  5. Redirect URI Exact Match: No wildcard or partial matching allowed

Getting Started

Prerequisites

  • Java 21 or higher
  • Maven 3.8+ (or use included Maven wrapper)

Running the Server

# Using Maven wrapper
./mvnw spring-boot:run

# Or build and run
./mvnw clean package
java -jar target/Auth201Server-0.0.1-SNAPSHOT.jar

The server will start on http://localhost:9000

Default Users

Two test users are configured:

Username Password Roles
user password USER
admin admin USER, ADMIN

Registered Clients

1. Public Client (SPA/Mobile Apps)

  • Client ID: public-client
  • Client Secret: None (public client)
  • Grant Types: Authorization Code, Refresh Token
  • PKCE: Required
  • Scopes: openid, profile, email, read, write

2. Confidential Client (Server-to-Server)

  • Client ID: confidential-client
  • Client Secret: secret
  • Grant Types: Authorization Code, Refresh Token, Client Credentials
  • PKCE: Required (recommended)
  • Scopes: openid, profile, email, read, write

Endpoints

Well-Known Configuration

GET http://localhost:9000/.well-known/oauth-authorization-server

Authorization Endpoint

GET http://localhost:9000/oauth2/authorize

Token Endpoint

POST http://localhost:9000/oauth2/token

JWK Set Endpoint

GET http://localhost:9000/oauth2/jwks

Token Introspection

POST http://localhost:9000/oauth2/introspect

Token Revocation

POST http://localhost:9000/oauth2/revoke

UserInfo Endpoint (OIDC)

GET http://localhost:9000/userinfo

Testing the OAuth Flow

1. Authorization Code Flow with PKCE

Step 1: Generate PKCE Challenge

# Generate code verifier (43-128 characters)
CODE_VERIFIER=$(openssl rand -base64 32 | tr -d '+/' | tr '=' '_' | cut -c1-43)

# Generate code challenge
CODE_CHALLENGE=$(echo -n $CODE_VERIFIER | openssl dgst -binary -sha256 | openssl base64 | tr -d '+/' | tr '=' '_')

echo "Code Verifier: $CODE_VERIFIER"
echo "Code Challenge: $CODE_CHALLENGE"

Step 2: Authorization Request

Open in browser:

http://localhost:9000/oauth2/authorize?
  response_type=code&
  client_id=public-client&
  redirect_uri=http://127.0.0.1:8080/authorized&
  scope=openid profile email read write&
  code_challenge=$CODE_CHALLENGE&
  code_challenge_method=S256&
  state=xyz123

Login with user/password, then approve scopes.

Step 3: Exchange Authorization Code for Tokens

curl -X POST http://localhost:9000/oauth2/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=YOUR_AUTHORIZATION_CODE" \
  -d "redirect_uri=http://127.0.0.1:8080/authorized" \
  -d "client_id=public-client" \
  -d "code_verifier=$CODE_VERIFIER"

2. Client Credentials Flow (Confidential Client)

curl -X POST http://localhost:9000/oauth2/token \
  -u confidential-client:secret \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "scope=read write"

3. Refresh Token Flow

curl -X POST http://localhost:9000/oauth2/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token" \
  -d "refresh_token=YOUR_REFRESH_TOKEN" \
  -d "client_id=public-client"

4. Access Protected Resource

curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  http://localhost:9000/userinfo

Token Settings

Access Tokens

  • Lifetime: 15 minutes
  • Format: JWT (signed with RSA)
  • Claims: sub, scope, iss, exp, iat, etc.

Refresh Tokens

  • Lifetime: 24 hours
  • Rotation: Enabled (single-use tokens)
  • Reuse: Disabled (OAuth 2.1 requirement)

Security Features

  1. PKCE Enforcement: Prevents authorization code interception attacks
  2. Refresh Token Rotation: Detects token theft
  3. JWT Signature: RSA 2048-bit keys for token signing
  4. HTTPS Ready: Configure for production with proper TLS certificates
  5. CSRF Protection: Enabled for all non-API endpoints
  6. Form-Based Login: Built-in login page with session management

Development Tools

H2 Console

Access the in-memory database console:

http://localhost:9000/h2-console

Connection Details:

  • JDBC URL: jdbc:h2:mem:oauth2db
  • Username: sa
  • Password: (empty)

Configuration

application.yaml

server:
  port: 9000

spring:
  datasource:
    url: jdbc:h2:mem:oauth2db
    driver-class-name: org.h2.Driver
    username: sa
    password:

Customization

Add New Users

Edit SecurityConfig.java:

@Bean
public UserDetailsService userDetailsService() {
    UserDetails newUser = User.builder()
        .username("newuser")
        .password(passwordEncoder().encode("newpassword"))
        .roles("USER")
        .build();
    
    return new InMemoryUserDetailsManager(newUser);
}

Add New Clients

Edit SecurityConfig.java:

@Bean
public RegisteredClientRepository registeredClientRepository() {
    RegisteredClient myClient = RegisteredClient.withId(UUID.randomUUID().toString())
        .clientId("my-client")
        .clientSecret("{bcrypt}$2a$10$...") // Use passwordEncoder()
        .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
        .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
        .redirectUri("https://my-app.com/callback")
        .scope(OidcScopes.OPENID)
        .clientSettings(ClientSettings.builder()
            .requireProofKey(true)
            .build())
        .build();
    
    return new InMemoryRegisteredClientRepository(myClient);
}

Production Deployment

1. Use PostgreSQL/MySQL

Replace H2 with a production database:

<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
</dependency>
spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/oauth2db
    username: oauth2user
    password: ${DB_PASSWORD}
  jpa:
    hibernate:
      ddl-auto: validate

2. Enable HTTPS

server:
  port: 443
  ssl:
    key-store: classpath:keystore.p12
    key-store-password: ${KEYSTORE_PASSWORD}
    key-store-type: PKCS12
    key-alias: oauth2server

3. Use Persistent JWK Keys

Store keys in database or external vault instead of generating on startup.

4. Configure Proper Issuer

@Bean
public AuthorizationServerSettings authorizationServerSettings() {
    return AuthorizationServerSettings.builder()
        .issuer("https://auth.yourdomain.com")
        .build();
}

5. Environment Variables

export DB_PASSWORD=your-secure-password
export KEYSTORE_PASSWORD=your-keystore-password

Project Structure

src/main/java/com/heaerie/server/auth201/Auth201Server/
├── Auth201ServerApplication.java       # Main application
├── config/
│   └── SecurityConfig.java            # OAuth 2.1 configuration
└── controller/
    └── UserController.java            # Protected resources

src/main/resources/
└── application.yaml                    # Application configuration

Troubleshooting

Problem: "Invalid redirect URI"

Solution: Ensure redirect URI matches exactly (including protocol, host, port, and path)

Problem: "PKCE verification failed"

Solution: Verify code_verifier matches the code_challenge used in authorization request

Problem: "Invalid client"

Solution: Check client_id is correct and client is registered

Problem: "Refresh token invalid"

Solution: Refresh tokens are single-use with rotation enabled

References

License

This project is for educational and development purposes.