Skip to content

Commit 5ffd65a

Browse files
committed
chore: use nimbus
1 parent 7d885d8 commit 5ffd65a

3 files changed

Lines changed: 74 additions & 54 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ readerIdFuture
260260

261261
- `examples/basic` – lists recent checkouts to verify that your API token works.
262262
- `examples/card-reader-checkout` – lists paired readers and creates a €10 checkout on the first available device.
263-
- `examples/oauth2` – uses ScribeJava to run a local OAuth 2.0 Authorization Code flow with PKCE, exchanges the callback code for an access token, and fetches merchant information using the returned `merchant_code`.
263+
- `examples/oauth2` – uses Nimbus OAuth 2.0 SDK to run a local OAuth 2.0 Authorization Code flow with PKCE, exchanges the callback code for an access token, and fetches merchant information using the returned `merchant_code`.
264264

265265
To run the card reader example locally:
266266

examples/oauth2/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@ application {
99

1010
dependencies {
1111
implementation project(':sumup-sdk')
12-
implementation 'com.github.scribejava:scribejava-core:8.3.3'
12+
implementation 'com.nimbusds:oauth2-oidc-sdk:11.34'
1313
}

examples/oauth2/src/main/java/com/sumup/examples/oauth2/OAuth2Example.java

Lines changed: 72 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,24 @@
11
package com.sumup.examples.oauth2;
22

33
import com.fasterxml.jackson.databind.ObjectMapper;
4-
import com.github.scribejava.core.builder.ServiceBuilder;
5-
import com.github.scribejava.core.builder.api.DefaultApi20;
6-
import com.github.scribejava.core.oauth.AccessTokenRequestParams;
7-
import com.github.scribejava.core.oauth.AuthorizationUrlBuilder;
8-
import com.github.scribejava.core.model.OAuth2AccessToken;
9-
import com.github.scribejava.core.oauth.OAuth20Service;
10-
import com.github.scribejava.core.pkce.PKCE;
4+
import com.nimbusds.oauth2.sdk.AccessTokenResponse;
5+
import com.nimbusds.oauth2.sdk.AuthorizationCode;
6+
import com.nimbusds.oauth2.sdk.AuthorizationCodeGrant;
7+
import com.nimbusds.oauth2.sdk.AuthorizationRequest;
8+
import com.nimbusds.oauth2.sdk.ParseException;
9+
import com.nimbusds.oauth2.sdk.ResponseType;
10+
import com.nimbusds.oauth2.sdk.Scope;
11+
import com.nimbusds.oauth2.sdk.TokenErrorResponse;
12+
import com.nimbusds.oauth2.sdk.TokenRequest;
13+
import com.nimbusds.oauth2.sdk.TokenResponse;
14+
import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic;
15+
import com.nimbusds.oauth2.sdk.auth.Secret;
16+
import com.nimbusds.oauth2.sdk.http.HTTPRequest;
17+
import com.nimbusds.oauth2.sdk.http.HTTPResponse;
18+
import com.nimbusds.oauth2.sdk.id.ClientID;
19+
import com.nimbusds.oauth2.sdk.id.State;
20+
import com.nimbusds.oauth2.sdk.pkce.CodeChallengeMethod;
21+
import com.nimbusds.oauth2.sdk.pkce.CodeVerifier;
1122
import com.sumup.sdk.SumUpClient;
1223
import com.sumup.sdk.core.ApiException;
1324
import com.sumup.sdk.models.Merchant;
@@ -18,8 +29,6 @@
1829
import java.net.InetSocketAddress;
1930
import java.net.URI;
2031
import java.nio.charset.StandardCharsets;
21-
import java.security.SecureRandom;
22-
import java.util.Base64;
2332
import java.util.HashMap;
2433
import java.util.List;
2534
import java.util.Map;
@@ -28,16 +37,17 @@
2837
/**
2938
* OAuth 2.0 Authorization Code flow with SumUp.
3039
*
31-
* <p>This example uses ScribeJava to handle the OAuth2 Authorization Code flow with PKCE. Set
32-
* {@code CLIENT_ID}, {@code CLIENT_SECRET}, and {@code REDIRECT_URI}, then run
40+
* <p>This example uses Nimbus OAuth 2.0 SDK to handle the OAuth2 Authorization Code flow with
41+
* PKCE. Set {@code CLIENT_ID}, {@code CLIENT_SECRET}, and {@code REDIRECT_URI}, then run
3342
* {@code ./gradlew :examples:oauth2:run}.
3443
*/
3544
public final class OAuth2Example {
3645
private static final String STATE_COOKIE_NAME = "oauth_state";
3746
private static final String PKCE_COOKIE_NAME = "oauth_pkce";
38-
private static final String SCOPES = "email profile";
47+
private static final Scope SCOPES = new Scope("email", "profile");
48+
private static final URI AUTHORIZATION_ENDPOINT = URI.create("https://api.sumup.com/authorize");
49+
private static final URI TOKEN_ENDPOINT = URI.create("https://api.sumup.com/token");
3950
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
40-
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
4151

4252
private OAuth2Example() {}
4353

@@ -52,13 +62,6 @@ public static void main(String[] args) throws IOException {
5262
callbackPath = "/callback";
5363
}
5464

55-
OAuth20Service oauthService =
56-
new ServiceBuilder(clientId)
57-
.apiSecret(clientSecret)
58-
.defaultScope(SCOPES)
59-
.callback(redirectUri)
60-
.build(new SumUpOAuthApi());
61-
6265
int listenPort = redirect.getPort() == -1 ? 8080 : redirect.getPort();
6366
HttpServer server = HttpServer.create(new InetSocketAddress(listenPort), 0);
6467

@@ -70,19 +73,24 @@ public static void main(String[] args) throws IOException {
7073
return;
7174
}
7275

73-
String state = randomUrlSafeString(32);
74-
AuthorizationUrlBuilder authorizationUrlBuilder =
75-
oauthService.createAuthorizationUrlBuilder().state(state).initPKCE();
76-
PKCE pkce = authorizationUrlBuilder.getPkce();
76+
State state = new State();
77+
CodeVerifier codeVerifier = new CodeVerifier();
78+
AuthorizationRequest authorizationRequest =
79+
new AuthorizationRequest.Builder(
80+
new ResponseType(ResponseType.Value.CODE), new ClientID(clientId))
81+
.endpointURI(AUTHORIZATION_ENDPOINT)
82+
.redirectionURI(redirect)
83+
.scope(SCOPES)
84+
.state(state)
85+
.codeChallenge(codeVerifier, CodeChallengeMethod.S256)
86+
.build();
7787

7888
exchange.getResponseHeaders()
79-
.add("Set-Cookie", buildCookie(STATE_COOKIE_NAME, state));
89+
.add("Set-Cookie", buildCookie(STATE_COOKIE_NAME, state.getValue()));
8090
exchange.getResponseHeaders()
81-
.add("Set-Cookie", buildCookie(PKCE_COOKIE_NAME, pkce.getCodeVerifier()));
82-
83-
String authorizationUrl = authorizationUrlBuilder.build();
91+
.add("Set-Cookie", buildCookie(PKCE_COOKIE_NAME, codeVerifier.getValue()));
8492

85-
exchange.getResponseHeaders().add("Location", authorizationUrl);
93+
exchange.getResponseHeaders().add("Location", authorizationRequest.toURI().toString());
8694
exchange.sendResponseHeaders(302, -1);
8795
exchange.close();
8896
});
@@ -96,7 +104,7 @@ public static void main(String[] args) throws IOException {
96104
}
97105

98106
try {
99-
handleCallback(exchange, oauthService);
107+
handleCallback(exchange, clientId, clientSecret, redirect);
100108
} catch (Exception ex) {
101109
sendText(exchange, 500, "OAuth2 error: " + ex.getMessage());
102110
}
@@ -110,7 +118,7 @@ public static void main(String[] args) throws IOException {
110118
<html>
111119
<body>
112120
<h1>SumUp OAuth2 Example</h1>
113-
<p>This example uses ScribeJava for the OAuth2 Authorization Code flow with PKCE.</p>
121+
<p>This example uses Nimbus OAuth 2.0 SDK for the OAuth2 Authorization Code flow with PKCE.</p>
114122
<p><a href="/login">Start OAuth2 Flow</a></p>
115123
</body>
116124
</html>
@@ -123,7 +131,8 @@ public static void main(String[] args) throws IOException {
123131
System.out.printf("Server is running at %s%n", redirectUri);
124132
}
125133

126-
private static void handleCallback(HttpExchange exchange, OAuth20Service oauthService)
134+
private static void handleCallback(
135+
HttpExchange exchange, String clientId, String clientSecret, URI redirectUri)
127136
throws Exception {
128137
Map<String, String> queryParams = parseQuery(exchange.getRequestURI().getRawQuery());
129138
String expectedState = readCookie(exchange, STATE_COOKIE_NAME);
@@ -152,10 +161,10 @@ private static void handleCallback(HttpExchange exchange, OAuth20Service oauthSe
152161
return;
153162
}
154163

155-
OAuth2AccessToken accessToken =
156-
oauthService.getAccessToken(
157-
AccessTokenRequestParams.create(code).pkceCodeVerifier(codeVerifier));
158-
SumUpClient client = new SumUpClient(accessToken.getAccessToken());
164+
AccessTokenResponse accessTokenResponse =
165+
exchangeAccessToken(clientId, clientSecret, redirectUri, code, codeVerifier);
166+
SumUpClient client =
167+
new SumUpClient(accessTokenResponse.getTokens().getAccessToken().getValue());
159168

160169
try {
161170
Merchant merchant = client.merchants().getMerchant(merchantCode);
@@ -170,10 +179,33 @@ private static void handleCallback(HttpExchange exchange, OAuth20Service oauthSe
170179
}
171180
}
172181

173-
private static String randomUrlSafeString(int byteCount) {
174-
byte[] bytes = new byte[byteCount];
175-
SECURE_RANDOM.nextBytes(bytes);
176-
return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
182+
private static AccessTokenResponse exchangeAccessToken(
183+
String clientId, String clientSecret, URI redirectUri, String code, String codeVerifier)
184+
throws IOException, ParseException {
185+
AuthorizationCodeGrant codeGrant =
186+
new AuthorizationCodeGrant(
187+
new AuthorizationCode(code), redirectUri, new CodeVerifier(codeVerifier));
188+
TokenRequest tokenRequest =
189+
new TokenRequest(
190+
TOKEN_ENDPOINT,
191+
new ClientSecretBasic(new ClientID(clientId), new Secret(clientSecret)),
192+
codeGrant,
193+
null);
194+
HTTPRequest httpRequest = tokenRequest.toHTTPRequest();
195+
HTTPResponse httpResponse = httpRequest.send();
196+
TokenResponse tokenResponse = TokenResponse.parse(httpResponse);
197+
198+
if (!tokenResponse.indicatesSuccess()) {
199+
TokenErrorResponse errorResponse = tokenResponse.toErrorResponse();
200+
String description = errorResponse.getErrorObject().getDescription();
201+
String message =
202+
description == null || description.isBlank()
203+
? errorResponse.getErrorObject().getCode()
204+
: errorResponse.getErrorObject().getCode() + ": " + description;
205+
throw new IOException("Failed to exchange authorization code: " + message);
206+
}
207+
208+
return tokenResponse.toSuccessResponse();
177209
}
178210

179211
private static Map<String, String> parseQuery(String rawQuery) {
@@ -252,16 +284,4 @@ private static String requireEnv(String name) {
252284
}
253285
return value;
254286
}
255-
256-
private static final class SumUpOAuthApi extends DefaultApi20 {
257-
@Override
258-
public String getAccessTokenEndpoint() {
259-
return "https://api.sumup.com/token";
260-
}
261-
262-
@Override
263-
protected String getAuthorizationBaseUrl() {
264-
return "https://api.sumup.com/authorize";
265-
}
266-
}
267287
}

0 commit comments

Comments
 (0)