# 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

```bash
# 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

```bash
# 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

```bash
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)

```bash
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

```bash
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

```bash
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

```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`:

```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`:

```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:

```xml
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
</dependency>
```

```yaml
spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/oauth2db
    username: oauth2user
    password: ${DB_PASSWORD}
  jpa:
    hibernate:
      ddl-auto: validate
```

### 2. Enable HTTPS

```yaml
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

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

### 5. Environment Variables

```bash
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

- [OAuth 2.1 Draft](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-09)
- [Spring Authorization Server](https://spring.io/projects/spring-authorization-server)
- [OpenID Connect 1.0](https://openid.net/specs/openid-connect-core-1_0.html)
- [RFC 7636 - PKCE](https://datatracker.ietf.org/doc/html/rfc7636)

## License

This project is for educational and development purposes.
