Skip to content

Commit cff4f7e

Browse files
committed
chore: use nimbus
1 parent 1acd16d commit cff4f7e

File tree

3 files changed

+81
-59
lines changed

3 files changed

+81
-59
lines changed

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: 79 additions & 57 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
33-
* {@code ./gradlew :examples:oauth2:run}.
40+
* <p>This example uses Nimbus OAuth 2.0 SDK to handle the OAuth2 Authorization Code flow with PKCE.
41+
* Set {@code CLIENT_ID}, {@code CLIENT_SECRET}, and {@code REDIRECT_URI}, then run {@code ./gradlew
42+
* :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,26 @@ 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();
77-
78-
exchange.getResponseHeaders()
79-
.add("Set-Cookie", buildCookie(STATE_COOKIE_NAME, state));
80-
exchange.getResponseHeaders()
81-
.add("Set-Cookie", buildCookie(PKCE_COOKIE_NAME, pkce.getCodeVerifier()));
82-
83-
String authorizationUrl = authorizationUrlBuilder.build();
84-
85-
exchange.getResponseHeaders().add("Location", authorizationUrl);
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();
87+
88+
exchange
89+
.getResponseHeaders()
90+
.add("Set-Cookie", buildCookie(STATE_COOKIE_NAME, state.getValue()));
91+
exchange
92+
.getResponseHeaders()
93+
.add("Set-Cookie", buildCookie(PKCE_COOKIE_NAME, codeVerifier.getValue()));
94+
95+
exchange.getResponseHeaders().add("Location", authorizationRequest.toURI().toString());
8696
exchange.sendResponseHeaders(302, -1);
8797
exchange.close();
8898
});
@@ -96,7 +106,7 @@ public static void main(String[] args) throws IOException {
96106
}
97107

98108
try {
99-
handleCallback(exchange, oauthService);
109+
handleCallback(exchange, clientId, clientSecret, redirect);
100110
} catch (Exception ex) {
101111
sendText(exchange, 500, "OAuth2 error: " + ex.getMessage());
102112
}
@@ -110,7 +120,7 @@ public static void main(String[] args) throws IOException {
110120
<html>
111121
<body>
112122
<h1>SumUp OAuth2 Example</h1>
113-
<p>This example uses ScribeJava for the OAuth2 Authorization Code flow with PKCE.</p>
123+
<p>This example uses Nimbus OAuth 2.0 SDK for the OAuth2 Authorization Code flow with PKCE.</p>
114124
<p><a href="/login">Start OAuth2 Flow</a></p>
115125
</body>
116126
</html>
@@ -123,7 +133,8 @@ public static void main(String[] args) throws IOException {
123133
System.out.printf("Server is running at %s%n", redirectUri);
124134
}
125135

126-
private static void handleCallback(HttpExchange exchange, OAuth20Service oauthService)
136+
private static void handleCallback(
137+
HttpExchange exchange, String clientId, String clientSecret, URI redirectUri)
127138
throws Exception {
128139
Map<String, String> queryParams = parseQuery(exchange.getRequestURI().getRawQuery());
129140
String expectedState = readCookie(exchange, STATE_COOKIE_NAME);
@@ -152,10 +163,10 @@ private static void handleCallback(HttpExchange exchange, OAuth20Service oauthSe
152163
return;
153164
}
154165

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

160171
try {
161172
Merchant merchant = client.merchants().getMerchant(merchantCode);
@@ -170,10 +181,33 @@ private static void handleCallback(HttpExchange exchange, OAuth20Service oauthSe
170181
}
171182
}
172183

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);
184+
private static AccessTokenResponse exchangeAccessToken(
185+
String clientId, String clientSecret, URI redirectUri, String code, String codeVerifier)
186+
throws IOException, ParseException {
187+
AuthorizationCodeGrant codeGrant =
188+
new AuthorizationCodeGrant(
189+
new AuthorizationCode(code), redirectUri, new CodeVerifier(codeVerifier));
190+
TokenRequest tokenRequest =
191+
new TokenRequest(
192+
TOKEN_ENDPOINT,
193+
new ClientSecretBasic(new ClientID(clientId), new Secret(clientSecret)),
194+
codeGrant,
195+
null);
196+
HTTPRequest httpRequest = tokenRequest.toHTTPRequest();
197+
HTTPResponse httpResponse = httpRequest.send();
198+
TokenResponse tokenResponse = TokenResponse.parse(httpResponse);
199+
200+
if (!tokenResponse.indicatesSuccess()) {
201+
TokenErrorResponse errorResponse = tokenResponse.toErrorResponse();
202+
String description = errorResponse.getErrorObject().getDescription();
203+
String message =
204+
description == null || description.isBlank()
205+
? errorResponse.getErrorObject().getCode()
206+
: errorResponse.getErrorObject().getCode() + ": " + description;
207+
throw new IOException("Failed to exchange authorization code: " + message);
208+
}
209+
210+
return tokenResponse.toSuccessResponse();
177211
}
178212

179213
private static Map<String, String> parseQuery(String rawQuery) {
@@ -252,16 +286,4 @@ private static String requireEnv(String name) {
252286
}
253287
return value;
254288
}
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-
}
267289
}

0 commit comments

Comments
 (0)