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
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 */
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,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