Functional Specifications
- Authorization Code Grant Flow: Issue secure short-lived codes (60s validity) exchanged atomically for access/refresh tokens.
- PKCE Protection (S256): Prevent code interception attacks on public clients using cryptographical SHA-256 verifiers.
- Refresh Token Rotation (RTR): Keep sessions safe by rotating credentials on each reuse. Detect token replays instantly and revoke full lineage trees.
- Fine-Grained Policy Access (RBAC/ABAC): Enforce unified controls by evaluating role-based structures side-by-side with dynamic contextual variables.
Clean reference class setups featuring PKCE S256 verification, JWT base64 mock compilation, and cascaded parent-child RTR family revocation:
// ─── JAVA BLUEPRINT ──────────────────────────────────────────────────────────
import java.util.*;
import java.security.MessageDigest;
import java.nio.charset.StandardCharsets;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.concurrent.ConcurrentHashMap;
class User {
private final String userId;
private final String username;
private final String passwordHash;
private final Set<String> roles;
private final Map<String, String> attributes;
public User(String userId, String username, String passwordHash, Set<String> roles, Map<String, String> attributes) {
this.userId = userId;
this.username = username;
this.passwordHash = passwordHash;
this.roles = Collections.unmodifiableSet(new HashSet<>(roles));
this.attributes = Collections.unmodifiableMap(new HashMap<>(attributes));
}
public String getUserId() { return userId; }
public String getUsername() { return username; }
public String getPasswordHash() { return passwordHash; }
public Set<String> getRoles() { return roles; }
public Map<String, String> getAttributes() { return attributes; }
}
class OAuthClient {
private final String clientId;
private final String clientSecretHash;
private final Set<String> allowedScopes;
private final Set<String> redirectUris;
public OAuthClient(String clientId, String clientSecretHash, Set<String> allowedScopes, Set<String> redirectUris) {
this.clientId = clientId;
this.clientSecretHash = clientSecretHash;
this.allowedScopes = Collections.unmodifiableSet(new HashSet<>(allowedScopes));
this.redirectUris = Collections.unmodifiableSet(new HashSet<>(redirectUris));
}
public String getClientId() { return clientId; }
public String getClientSecretHash() { return clientSecretHash; }
public Set<String> getAllowedScopes() { return allowedScopes; }
public Set<String> getRedirectUris() { return redirectUris; }
}
class AuthorizationCode {
private final String code;
private final String clientId;
private final String userId;
private final String redirectUri;
private final Set<String> scopes;
private final long expiresAt;
private final String codeChallenge;
private final String codeChallengeMethod;
public AuthorizationCode(String code, String clientId, String userId, String redirectUri, Set<String> scopes, long ttlMs, String codeChallenge, String codeChallengeMethod) {
this.code = code;
this.clientId = clientId;
this.userId = userId;
this.redirectUri = redirectUri;
this.scopes = Collections.unmodifiableSet(new HashSet<>(scopes));
this.expiresAt = System.currentTimeMillis() + ttlMs;
this.codeChallenge = codeChallenge;
this.codeChallengeMethod = codeChallengeMethod;
}
public boolean isExpired() { return System.currentTimeMillis() > expiresAt; }
public String getCode() { return code; }
public String getClientId() { return clientId; }
public String getUserId() { return userId; }
public String getRedirectUri() { return redirectUri; }
public Set<String> getScopes() { return scopes; }
public String getCodeChallenge() { return codeChallenge; }
public String getCodeChallengeMethod() { return codeChallengeMethod; }
}
class TokenPair {
private final String accessToken;
private final String refreshToken;
private final long expiresAt;
public TokenPair(String accessToken, String refreshToken, long ttlMs) {
this.accessToken = accessToken;
this.refreshToken = refreshToken;
this.expiresAt = System.currentTimeMillis() + ttlMs;
}
public String getAccessToken() { return accessToken; }
public String getRefreshToken() { return refreshToken; }
public long getExpiresAt() { return expiresAt; }
}
class RefreshTokenMetadata {
final String token;
final String parentToken;
final String userId;
final String clientId;
final Set<String> scopes;
volatile boolean isUsed;
volatile boolean isRevoked;
public RefreshTokenMetadata(String token, String parentToken, String userId, String clientId, Set<String> scopes) {
this.token = token;
this.parentToken = parentToken;
this.userId = userId;
this.clientId = clientId;
this.scopes = Collections.unmodifiableSet(new HashSet<>(scopes));
this.isUsed = false;
this.isRevoked = false;
}
}
class AuthService {
private final Map<String, User> userDb = new ConcurrentHashMap<>();
private final Map<String, OAuthClient> clientDb = new ConcurrentHashMap<>();
private final Map<String, AuthorizationCode> codeDb = new ConcurrentHashMap<>();
private final Map<String, RefreshTokenMetadata> refreshTokenDb = new ConcurrentHashMap<>();
private final Map<String, Set<String>> rbacPolicy = new ConcurrentHashMap<>();
private final String jwtSigningSecret = "oauth-secret-key-signature-hmac-sha-256";
public synchronized void registerUser(User user) { userDb.put(user.getUserId(), user); }
public synchronized void registerClient(OAuthClient client) { clientDb.put(client.getClientId(), client); }
public synchronized void addRolePermission(String role, String permission) {
rbacPolicy.computeIfAbsent(role, k -> ConcurrentHashMap.newKeySet()).add(permission);
}
public synchronized String issueAuthorizationCode(String clientId, String userId, String redirectUri, Set<String> scopes, String challenge, String method) {
OAuthClient client = clientDb.get(clientId);
if (client == null) throw new IllegalArgumentException("Client not found");
if (!client.getRedirectUris().contains(redirectUri)) throw new IllegalArgumentException("Invalid redirect URI");
String code = UUID.randomUUID().toString();
AuthorizationCode authCode = new AuthorizationCode(code, clientId, userId, redirectUri, scopes, 60000, challenge, method);
codeDb.put(code, authCode);
return code;
}
public synchronized TokenPair exchangeCode(String code, String clientId, String clientSecret, String redirectUri, String verifier) throws Exception {
AuthorizationCode authCode = codeDb.remove(code);
if (authCode == null) throw new SecurityException("Invalid/Used auth code");
if (authCode.isExpired()) throw new SecurityException("Auth code expired");
OAuthClient client = clientDb.get(clientId);
if (client == null) throw new IllegalArgumentException("Client not found");
if (!client.getClientSecretHash().equals(hashSecret(clientSecret))) {
throw new SecurityException("Client credentials check failed");
}
if (!authCode.getRedirectUri().equals(redirectUri)) {
throw new SecurityException("Redirect URI mismatch");
}
if (authCode.getCodeChallenge() != null) {
if (verifier == null) throw new SecurityException("PKCE verifier required");
if (!verifyPKCE(authCode.getCodeChallenge(), authCode.getCodeChallengeMethod(), verifier)) {
throw new SecurityException("PKCE verification failed");
}
}
return generateTokenPair(authCode.getUserId(), clientId, authCode.getScopes(), null);
}
public synchronized TokenPair refreshTokens(String refreshToken) throws Exception {
RefreshTokenMetadata meta = refreshTokenDb.get(refreshToken);
if (meta == null) throw new SecurityException("Refresh token does not exist");
if (meta.isUsed || meta.isRevoked) {
revokeFamilyTree(meta.token);
throw new SecurityException("Token reuse alert! Full token family has been revoked.");
}
meta.isUsed = true;
return generateTokenPair(meta.userId, meta.clientId, meta.scopes, meta.token);
}
private synchronized TokenPair generateTokenPair(String userId, String clientId, Set<String> scopes, String parentToken) throws Exception {
User user = userDb.get(userId);
if (user == null) throw new IllegalArgumentException("User not found");
String accessToken = generateMockJWT(user, clientId, scopes);
String refreshToken = UUID.randomUUID().toString();
RefreshTokenMetadata meta = new RefreshTokenMetadata(refreshToken, parentToken, userId, clientId, scopes);
refreshTokenDb.put(refreshToken, meta);
return new TokenPair(accessToken, refreshToken, 900000);
}
private synchronized void revokeFamilyTree(String rootToken) {
RefreshTokenMetadata meta = refreshTokenDb.get(rootToken);
if (meta != null) {
meta.isRevoked = true;
for (RefreshTokenMetadata m : refreshTokenDb.values()) {
if (rootToken.equals(m.parentToken)) {
revokeFamilyTree(m.token);
}
}
}
}
private String generateMockJWT(User user, String clientId, Set<String> scopes) throws Exception {
long exp = System.currentTimeMillis() + 900000;
String header = Base64.getUrlEncoder().withoutPadding().encodeToString("{\"alg\":\"HS256\",\"typ\":\"JWT\"}".getBytes(StandardCharsets.UTF_8));
String rolesStr = "[" + String.join(",", user.getRoles().stream().map(r -> "\"" + r + "\"").toArray(String[]::new)) + "]";
String payloadJson = String.format("{\"sub\":\"%s\",\"aud\":\"%s\",\"roles\":%s,\"exp\":%d}", user.getUserId(), clientId, rolesStr, exp);
String payload = Base64.getUrlEncoder().withoutPadding().encodeToString(payloadJson.getBytes(StandardCharsets.UTF_8));
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(jwtSigningSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
byte[] sigBytes = mac.doFinal((header + "." + payload).getBytes(StandardCharsets.UTF_8));
String sig = Base64.getUrlEncoder().withoutPadding().encodeToString(sigBytes);
return header + "." + payload + "." + sig;
}
private boolean verifyPKCE(String challenge, String method, String verifier) throws Exception {
if ("plain".equalsIgnoreCase(method)) {
return challenge.equals(verifier);
} else if ("S256".equalsIgnoreCase(method)) {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(verifier.getBytes(StandardCharsets.UTF_8));
String calculated = Base64.getUrlEncoder().withoutPadding().encodeToString(hash);
return challenge.equals(calculated);
}
return false;
}
private String hashSecret(String secret) throws Exception {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(secret.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(hash);
}
public synchronized boolean checkAccess(String accessToken, String resourceDept, int requiredClearance) {
try {
String[] parts = accessToken.split(".");
if (parts.length < 3) return false;
String payload = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8);
String userId = null;
int startSub = payload.indexOf("\"sub\":\"");
if (startSub != -1) {
int endSub = payload.indexOf("\"", startSub + 7);
userId = payload.substring(startSub + 7, endSub);
}
if (userId == null) return false;
User user = userDb.get(userId);
if (user == null) return false;
// RBAC Check
boolean hasRole = user.getRoles().contains("Administrator") || user.getRoles().contains("Developer");
System.out.println(" [RBAC Check] User has roles " + user.getRoles() + " -> " + (hasRole ? "PASSED" : "FAILED"));
if (!hasRole) return false;
// ABAC Check
String userDept = user.getAttributes().getOrDefault("department", "");
int userClearance = Integer.parseInt(user.getAttributes().getOrDefault("clearanceLevel", "0"));
boolean deptMatch = userDept.equalsIgnoreCase(resourceDept);
boolean clearanceMatch = userClearance >= requiredClearance;
System.out.println(" [ABAC Check] Dept matches (" + userDept + " vs " + resourceDept + ") -> " + (deptMatch ? "PASSED" : "FAILED"));
System.out.println(" [ABAC Check] Clearance level " + userClearance + " >= " + requiredClearance + " -> " + (clearanceMatch ? "PASSED" : "FAILED"));
return deptMatch && clearanceMatch;
} catch (Exception e) {
System.out.println(" [Access Control] Token validation error: " + e.getMessage());
return false;
}
}
}
public class Main {
public static void main(String[] args) {
try {
System.out.println("=== STARTING AUTH & SSO SERVICE SIMULATION ===");
AuthService authService = new AuthService();
// 1. Setup Identities & Clients
Set<String> devRoles = new HashSet<>(Arrays.asList("Developer"));
Map<String, String> devAttrs = new HashMap<>();
devAttrs.put("department", "Engineering");
devAttrs.put("clearanceLevel", "2");
User bob = new User("usr-bob", "Bob Developer", "hashed_password_123", devRoles, devAttrs);
authService.registerUser(bob);
MessageDigest digest = MessageDigest.getInstance("SHA-256");
String clientSecret = "client-secret-xyz-789";
String secretHash = Base64.getEncoder().encodeToString(digest.digest(clientSecret.getBytes(StandardCharsets.UTF_8)));
OAuthClient client = new OAuthClient("client-portal", secretHash, new HashSet<>(Arrays.asList("openid", "profile")), new HashSet<>(Arrays.asList("https://client.com/callback")));
authService.registerClient(client);
System.out.println("[Init] Registered User: Bob (Developer, Engineering, Clearance: 2)");
System.out.println("[Init] Registered OAuth Client: client-portal");
// 2. PKCE Authorization Flow S256
String verifier = "my-super-secure-pkce-verifier-string-12345-bob-verifier";
byte[] hash = digest.digest(verifier.getBytes(StandardCharsets.UTF_8));
String challenge = Base64.getUrlEncoder().withoutPadding().encodeToString(hash);
System.out.println("\n[Flow 1] Requesting short-lived authorization code with PKCE S256...");
String code = authService.issueAuthorizationCode("client-portal", "usr-bob", "https://client.com/callback", new HashSet<>(Arrays.asList("openid")), challenge, "S256");
System.out.println(" -> Authorization Code issued: " + code);
System.out.println("\n[Flow 2] Exchanging authorization code with verifier...");
TokenPair pair1 = authService.exchangeCode(code, "client-portal", clientSecret, "https://client.com/callback", verifier);
System.out.println(" -> Token exchange successful!");
System.out.println(" -> Access Token (JWT snippet): " + pair1.getAccessToken().substring(0, 35) + "...");
System.out.println(" -> Refresh Token (Opaque): " + pair1.getRefreshToken());
// 3. RBAC + ABAC Authorization
System.out.println("\n[Flow 3] Verifying access to protected resource: /api/secured-dataset (Engineering dept, Clearance >= 2)");
boolean allowed = authService.checkAccess(pair1.getAccessToken(), "Engineering", 2);
System.out.println(" -> Authorization decision: ACCESS " + (allowed ? "GRANTED" : "DENIED"));
System.out.println("\n[Flow 3b] Verifying access to protected resource: /api/finance-records (Finance dept, Clearance >= 3)");
boolean allowedFinance = authService.checkAccess(pair1.getAccessToken(), "Finance", 3);
System.out.println(" -> Authorization decision: ACCESS " + (allowedFinance ? "GRANTED" : "DENIED"));
// 4. Refresh Token Rotation (RTR)
System.out.println("\n[Flow 4] Refreshing session using token: " + pair1.getRefreshToken());
TokenPair pair2 = authService.refreshTokens(pair1.getRefreshToken());
System.out.println(" -> Session rotated successfully!");
System.out.println(" -> New Access Token (JWT snippet): " + pair2.getAccessToken().substring(0, 35) + "...");
System.out.println(" -> New Refresh Token (Opaque): " + pair2.getRefreshToken());
// 5. Token Replay Attack Countermeasures
System.out.println("\n[Flow 5] Simulating Token Replay Attack using the rotated (invalidated) refresh token...");
try {
authService.refreshTokens(pair1.getRefreshToken());
} catch (SecurityException e) {
System.out.println(" -> SECURITY ALERT CAUGHT: " + e.getMessage());
}
System.out.println("\n[Flow 5b] Verifying if cascade revocation successfully invalidated the new active session...");
try {
authService.refreshTokens(pair2.getRefreshToken());
} catch (SecurityException e) {
System.out.println(" -> SUCCESS: Current session also revoked successfully! Message: " + e.getMessage());
}
System.out.println("\n=== SIMULATION COMPLETED SUCCESSFULLY ===");
} catch (Exception e) {
e.printStackTrace();
}
}
}