11package com .sumup .examples .oauth2 ;
22
33import 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 ;
1122import com .sumup .sdk .SumUpClient ;
1223import com .sumup .sdk .core .ApiException ;
1324import com .sumup .sdk .models .Merchant ;
1829import java .net .InetSocketAddress ;
1930import java .net .URI ;
2031import java .nio .charset .StandardCharsets ;
21- import java .security .SecureRandom ;
22- import java .util .Base64 ;
2332import java .util .HashMap ;
2433import java .util .List ;
2534import java .util .Map ;
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 */
3544public 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