A complete OAuth 2.1 Authorization Server implementation built with Spring Boot 4.0.0 and Spring Authorization Server 7.0.0.
✅ 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
This server implements OAuth 2.1 which includes:
# 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
Two test users are configured:
| Username | Password | Roles |
|---|---|---|
| user | password | USER |
| admin | admin | USER, ADMIN |
public-clientconfidential-clientsecretGET http://localhost:9000/.well-known/oauth-authorization-server
GET http://localhost:9000/oauth2/authorize
POST http://localhost:9000/oauth2/token
GET http://localhost:9000/oauth2/jwks
POST http://localhost:9000/oauth2/introspect
POST http://localhost:9000/oauth2/revoke
GET http://localhost:9000/userinfo
# 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"
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.
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"
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"
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"
curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ http://localhost:9000/userinfo
Access the in-memory database console:
http://localhost:9000/h2-console
Connection Details:
jdbc:h2:mem:oauth2dbsaserver:
port: 9000
spring:
datasource:
url: jdbc:h2:mem:oauth2db
driver-class-name: org.h2.Driver
username: sa
password:
Edit SecurityConfig.java:
@Bean
public UserDetailsService userDetailsService() {
UserDetails newUser = User.builder()
.username("newuser")
.password(passwordEncoder().encode("newpassword"))
.roles("USER")
.build();
return new InMemoryUserDetailsManager(newUser);
}
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);
}
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
server:
port: 443
ssl:
key-store: classpath:keystore.p12
key-store-password: ${KEYSTORE_PASSWORD}
key-store-type: PKCS12
key-alias: oauth2server
Store keys in database or external vault instead of generating on startup.
@Bean
public AuthorizationServerSettings authorizationServerSettings() {
return AuthorizationServerSettings.builder()
.issuer("https://auth.yourdomain.com")
.build();
}
export DB_PASSWORD=your-secure-password export KEYSTORE_PASSWORD=your-keystore-password
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
Solution: Ensure redirect URI matches exactly (including protocol, host, port, and path)
Solution: Verify code_verifier matches the code_challenge used in authorization request
Solution: Check client_id is correct and client is registered
Solution: Refresh tokens are single-use with rotation enabled
This project is for educational and development purposes.