Skip to content

Commit d283ea6

Browse files
committed
fix: Update OAuth implementation 2
1 parent c0d614d commit d283ea6

2 files changed

Lines changed: 39 additions & 45 deletions

File tree

src/main/java/com/contentstack/cms/models/OAuthConfig.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public String getFormattedAuthorizationEndpoint() {
7474
hostname = hostname.replace("api", "app");
7575
}
7676

77-
return "https://" + hostname + "/apps/oauth/authorize";
77+
return "https://" + hostname + "/#!/apps/" + appId + "/authorize";
7878
}
7979

8080
/**
@@ -94,7 +94,7 @@ public String getTokenEndpoint() {
9494
.replaceAll("^dev\\d+", "dev") // Replace dev1, dev2, etc. with dev
9595
.replace("io", "com");
9696

97-
return "https://" + hostname + "/apps/oauth/token";
97+
return "https://" + hostname;
9898
}
9999

100100
/**

src/main/java/com/contentstack/cms/oauth/OAuthHandler.java

Lines changed: 37 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,12 @@ public OAuthHandler(OkHttpClient httpClient, OAuthConfig config) {
5151

5252
// Validate config before proceeding
5353
config.validate();
54-
this.state = generateCodeVerifier();
5554

56-
// Generate PKCE parameters if needed
57-
if (config.isPkceEnabled()) {
58-
generatePkceParameters();
55+
// Only generate PKCE codeVerifier if clientSecret is not provided
56+
if (config.getClientSecret() == null || config.getClientSecret().trim().isEmpty()) {
57+
this.codeVerifier = generateCodeVerifier();
58+
// codeChallenge will be generated during authorize()
59+
this.codeChallenge = null;
5960
}
6061
}
6162

@@ -72,13 +73,13 @@ private Request.Builder _getHeaders() {
7273
* @return A random URL-safe string between 43-128 characters
7374
*/
7475
private String generateCodeVerifier() {
75-
SecureRandom secureRandom = new SecureRandom();
76-
byte[] randomBytes = new byte[32];
77-
secureRandom.nextBytes(randomBytes);
78-
79-
return Base64.getUrlEncoder()
80-
.withoutPadding()
81-
.encodeToString(randomBytes);
76+
final String charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
77+
SecureRandom random = new SecureRandom();
78+
StringBuilder verifier = new StringBuilder();
79+
for (int i = 0; i < 128; i++) {
80+
verifier.append(charset.charAt(random.nextInt(charset.length())));
81+
}
82+
return verifier.toString();
8283
}
8384

8485
/**
@@ -91,9 +92,11 @@ private String generateCodeChallenge(String verifier) {
9192
MessageDigest digest = MessageDigest.getInstance("SHA-256");
9293
byte[] hash = digest.digest(verifier.getBytes(StandardCharsets.UTF_8));
9394

94-
return Base64.getUrlEncoder()
95-
.withoutPadding()
96-
.encodeToString(hash);
95+
String base64String = Base64.getEncoder().encodeToString(hash);
96+
return base64String
97+
.replace('+', '-')
98+
.replace('/', '_')
99+
.replaceAll("=+$", "");
97100
} catch (NoSuchAlgorithmException e) {
98101
throw new RuntimeException("SHA-256 algorithm not available", e);
99102
}
@@ -114,32 +117,23 @@ private void generatePkceParameters() {
114117
*/
115118
public String authorize() {
116119
try {
117-
StringBuilder urlBuilder = new StringBuilder();
118-
119-
// Build the authorization URL with parameters in correct order
120-
urlBuilder.append(config.getFormattedAuthorizationEndpoint())
121-
.append("?app_id=").append(URLEncoder.encode(config.getAppId(), "UTF-8"))
122-
.append("&response_type=").append(config.getResponseType())
123-
.append("&client_id=").append(URLEncoder.encode(config.getClientId(), "UTF-8"))
124-
.append("&redirect_uri=").append(URLEncoder.encode(config.getRedirectUri(), "UTF-8"));
125-
126-
// Add state for CSRF protection (always needed)
127-
if (this.state != null) {
128-
urlBuilder.append("&state=").append(URLEncoder.encode(this.state, "UTF-8"));
129-
}
130-
131-
// Add PKCE parameters if enabled
132-
if (config.isPkceEnabled()) {
120+
String baseUrl = String.format("%s/#!/apps/%s/authorize",
121+
config.getFormattedAuthorizationEndpoint(),
122+
config.getAppId());
123+
124+
StringBuilder urlBuilder = new StringBuilder(baseUrl);
125+
urlBuilder.append("?response_type=").append(config.getResponseType())
126+
.append("&client_id=").append(URLEncoder.encode(config.getClientId(), "UTF-8"));
127+
128+
if (config.getClientSecret() != null && !config.getClientSecret().trim().isEmpty()) {
129+
return urlBuilder.toString();
130+
} else {
131+
// PKCE flow: add code_challenge
132+
this.codeChallenge = generateCodeChallenge(this.codeVerifier);
133133
urlBuilder.append("&code_challenge=").append(URLEncoder.encode(this.codeChallenge, "UTF-8"))
134134
.append("&code_challenge_method=S256");
135+
return urlBuilder.toString();
135136
}
136-
137-
// Add scope if present
138-
if (config.getScope() != null && !config.getScope().trim().isEmpty()) {
139-
urlBuilder.append("&scope=").append(URLEncoder.encode(config.getScope(), "UTF-8"));
140-
}
141-
142-
return urlBuilder.toString();
143137
} catch (IOException e) {
144138
throw new RuntimeException("Failed to encode URL parameters", e);
145139
}
@@ -154,16 +148,16 @@ public CompletableFuture<OAuthTokens> exchangeCodeForToken(String code) {
154148
return CompletableFuture.supplyAsync(() -> {
155149
try {
156150
FormBody.Builder formBuilder = new FormBody.Builder()
157-
.add("app_id", config.getAppId())
158151
.add("grant_type", "authorization_code")
159152
.add("code", code)
160-
.add("client_id", config.getClientId())
161-
.add("redirect_uri", config.getRedirectUri());
153+
.add("redirect_uri", config.getRedirectUri())
154+
.add("client_id", config.getClientId());
162155

163-
if (config.isPkceEnabled()) {
164-
formBuilder.add("code_verifier", this.codeVerifier);
165-
} else {
156+
// Choose between client_secret and code_verifier like JS SDK
157+
if (config.getClientSecret() != null) {
166158
formBuilder.add("client_secret", config.getClientSecret());
159+
} else {
160+
formBuilder.add("code_verifier", this.codeVerifier);
167161
}
168162

169163
Request request = _getHeaders()

0 commit comments

Comments
 (0)