Skip to content

Commit abe7742

Browse files
committed
PKCE configuration - enabled by default
Signed-off-by: Rohan Naik <[email protected]>
1 parent 7a7d2ca commit abe7742

File tree

8 files changed

+109
-80
lines changed

8 files changed

+109
-80
lines changed

config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2ClientConfigurerTests.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ public void configureWhenAuthorizationCodeRequestThenRedirectForAuthorization()
156156
.andExpect(status().is3xxRedirection()).andReturn();
157157
assertThat(mvcResult.getResponse().getRedirectedUrl())
158158
.matches("https://provider.com/oauth2/authorize\\?" + "response_type=code&client_id=client-1&"
159-
+ "scope=user&state=.{15,}&" + "redirect_uri=http://localhost/client-1");
159+
+ "scope=user&state=.{15,}&" + "redirect_uri=http://localhost/client-1&code_challenge=([a-zA-Z0-9\\-\\.\\_\\~]){43}&code_challenge_method=S256");
160160
// @formatter:on
161161
}
162162

@@ -166,9 +166,9 @@ public void configureWhenOauth2ClientInLambdaThenRedirectForAuthorization() thro
166166
MvcResult mvcResult = this.mockMvc.perform(get("/oauth2/authorization/registration-1"))
167167
.andExpect(status().is3xxRedirection())
168168
.andReturn();
169-
assertThat(mvcResult.getResponse().getRedirectedUrl())
170-
.matches("https://provider.com/oauth2/authorize\\?" + "response_type=code&client_id=client-1&"
171-
+ "scope=user&state=.{15,}&" + "redirect_uri=http://localhost/client-1");
169+
assertThat(mvcResult.getResponse().getRedirectedUrl()).matches("https://provider.com/oauth2/authorize\\?"
170+
+ "response_type=code&client_id=client-1&" + "scope=user&state=.{15,}&"
171+
+ "redirect_uri=http://localhost/client-1&code_challenge=([a-zA-Z0-9\\-\\.\\_\\~]){43}&code_challenge_method=S256");
172172
}
173173

174174
@Test
@@ -215,9 +215,9 @@ public void configureWhenRequestCacheProvidedAndClientAuthorizationRequiredExcep
215215
MvcResult mvcResult = this.mockMvc.perform(get("/resource1").with(user("user1")))
216216
.andExpect(status().is3xxRedirection())
217217
.andReturn();
218-
assertThat(mvcResult.getResponse().getRedirectedUrl())
219-
.matches("https://provider.com/oauth2/authorize\\?" + "response_type=code&client_id=client-1&"
220-
+ "scope=user&state=.{15,}&" + "redirect_uri=http://localhost/client-1");
218+
assertThat(mvcResult.getResponse().getRedirectedUrl()).matches("https://provider.com/oauth2/authorize\\?"
219+
+ "response_type=code&client_id=client-1&" + "scope=user&state=.{15,}&"
220+
+ "redirect_uri=http://localhost/client-1&code_challenge=([a-zA-Z0-9\\-\\.\\_\\~]){43}&code_challenge_method=S256");
221221
verify(requestCache).saveRequest(any(HttpServletRequest.class), any(HttpServletResponse.class));
222222
}
223223

config/src/test/java/org/springframework/security/config/http/OAuth2ClientBeanDefinitionParserTests.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,9 @@ public void requestWhenAuthorizeThenRedirect() throws Exception {
112112
.andExpect(status().is3xxRedirection())
113113
.andReturn();
114114
// @formatter:on
115-
assertThat(result.getResponse().getRedirectedUrl()).matches(
116-
"https://accounts.google.com/o/oauth2/v2/auth\\?" + "response_type=code&client_id=google-client-id&"
117-
+ "scope=scope1%20scope2&state=.{15,}&redirect_uri=http://localhost/callback/google");
115+
assertThat(result.getResponse().getRedirectedUrl()).matches("https://accounts.google.com/o/oauth2/v2/auth\\?"
116+
+ "response_type=code&client_id=google-client-id&"
117+
+ "scope=scope1%20scope2&state=.{15,}&redirect_uri=http://localhost/callback/google&code_challenge=([a-zA-Z0-9\\-\\.\\_\\~]){43}&code_challenge_method=S256");
118118
}
119119

120120
@Test
@@ -134,9 +134,9 @@ public void requestWhenCustomClientRegistrationRepositoryThenCalled() throws Exc
134134
.andExpect(status().is3xxRedirection())
135135
.andReturn();
136136
// @formatter:on
137-
assertThat(result.getResponse().getRedirectedUrl()).matches(
138-
"https://accounts.google.com/o/oauth2/v2/auth\\?" + "response_type=code&client_id=google-client-id&"
139-
+ "scope=scope1%20scope2&state=.{15,}&redirect_uri=http://localhost/callback/google");
137+
assertThat(result.getResponse().getRedirectedUrl()).matches("https://accounts.google.com/o/oauth2/v2/auth\\?"
138+
+ "response_type=code&client_id=google-client-id&"
139+
+ "scope=scope1%20scope2&state=.{15,}&redirect_uri=http://localhost/callback/google&code_challenge=([a-zA-Z0-9\\-\\.\\_\\~]){43}&code_challenge_method=S256");
140140
verify(this.clientRegistrationRepository).findByRegistrationId(any());
141141
}
142142

docs/modules/ROOT/pages/servlet/oauth2/client/core.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ This information is available only if the Spring Boot property `spring.security.
6969
<15> `(userInfoEndpoint)authenticationMethod`: The authentication method used when sending the access token to the UserInfo Endpoint.
7070
The supported values are *header*, *form*, and *query*.
7171
<16> `userNameAttributeName`: The name of the attribute returned in the UserInfo Response that references the Name or Identifier of the end-user.
72-
<17> [[oauth2Client-client-registration-requireProofKey]]`requireProofKey`: If `true` or if `authorizationGrantType` is `none`, then PKCE will be enabled by default.
72+
<17> [[oauth2Client-client-registration-requireProofKey]]`requireProofKey`: If `true` or if `authorizationGrantType` is `none`, then PKCE will be enabled.
7373

7474
You can initially configure a `ClientRegistration` by using discovery of an OpenID Connect Provider's https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig[Configuration endpoint] or an Authorization Server's https://tools.ietf.org/html/rfc8414#section-3[Metadata endpoint].
7575

oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistration.java

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,15 @@ private ClientRegistration create() {
655655
clientRegistration.clientName = StringUtils.hasText(this.clientName) ? this.clientName
656656
: this.registrationId;
657657
clientRegistration.clientSettings = this.clientSettings;
658+
if (clientRegistration.clientSettings.requireProofKey) {
659+
clientRegistration.clientSettings.requireProofKey = AuthorizationGrantType.AUTHORIZATION_CODE
660+
.equals(this.authorizationGrantType)
661+
|| ClientAuthenticationMethod.NONE.equals(this.clientAuthenticationMethod);
662+
}
663+
else {
664+
clientRegistration.clientSettings.requireProofKey = ClientAuthenticationMethod.NONE
665+
.equals(this.clientAuthenticationMethod);
666+
}
658667
return clientRegistration;
659668
}
660669

@@ -713,12 +722,6 @@ private void validateAuthorizationGrantTypes() {
713722
"AuthorizationGrantType: %s does not match the pre-defined constant %s and won't match a valid OAuth2AuthorizedClientProvider",
714723
this.authorizationGrantType, authorizationGrantType));
715724
}
716-
if (!AuthorizationGrantType.AUTHORIZATION_CODE.equals(this.authorizationGrantType)
717-
&& this.clientSettings.isRequireProofKey()) {
718-
throw new IllegalStateException(
719-
"clientSettings.isRequireProofKey=true is only valid with authorizationGrantType=AUTHORIZATION_CODE. Got authorizationGrantType="
720-
+ this.authorizationGrantType);
721-
}
722725
}
723726
}
724727

@@ -754,7 +757,7 @@ public static final class ClientSettings implements Serializable {
754757
@Serial
755758
private static final long serialVersionUID = 7495627155437124692L;
756759

757-
private boolean requireProofKey;
760+
private boolean requireProofKey = true;
758761

759762
private ClientSettings() {
760763

@@ -791,7 +794,7 @@ public static Builder builder() {
791794

792795
public static final class Builder {
793796

794-
private boolean requireProofKey;
797+
private boolean requireProofKey = true;
795798

796799
private Builder() {
797800
}

oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/ClientRegistrationTests.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@
3737

3838
import static org.assertj.core.api.Assertions.assertThat;
3939
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
40-
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
4140

4241
/**
4342
* Tests for {@link ClientRegistration}.
@@ -780,7 +779,7 @@ void buildWhenDefaultClientSettingsThenDefaulted() {
780779
// should not be null
781780
assertThat(clientRegistration.getClientSettings()).isNotNull();
782781
// proof key should be false for passivity
783-
assertThat(clientRegistration.getClientSettings().isRequireProofKey()).isFalse();
782+
assertThat(clientRegistration.getClientSettings().isRequireProofKey()).isTrue();
784783
}
785784

786785
// gh-16382
@@ -816,10 +815,8 @@ void buildWhenInvalidGrantTypeForPkceThenException(AuthorizationGrantType invali
816815
.authorizationUri(AUTHORIZATION_URI)
817816
.tokenUri(TOKEN_URI);
818817

819-
assertThatIllegalStateException().describedAs(
820-
"clientSettings.isRequireProofKey=true is only valid with authorizationGrantType=AUTHORIZATION_CODE. Got authorizationGrantType={}",
821-
invalidGrantType)
822-
.isThrownBy(builder::build);
818+
ClientRegistration clientRegistration = builder.build();
819+
assertThat(clientRegistration.getClientSettings().isRequireProofKey()).isFalse();
823820
}
824821

825822
static List<AuthorizationGrantType> invalidPkceGrantTypes() {

oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizationRequestResolverTests.java

Lines changed: 51 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ public class DefaultOAuth2AuthorizationRequestResolverTests {
5858

5959
private ClientRegistration pkceClientRegistration;
6060

61+
private ClientRegistration nonProofKeyPublicClientRegistration;
62+
6163
private ClientRegistration fineRedirectUriTemplateRegistration;
6264

6365
private ClientRegistration publicClientRegistration;
@@ -76,7 +78,11 @@ public void setUp() {
7678
this.registration2 = TestClientRegistrations.clientRegistration2().build();
7779

7880
this.pkceClientRegistration = pkceClientRegistration().build();
79-
81+
this.nonProofKeyPublicClientRegistration = TestClientRegistrations.clientRegistration()
82+
.registrationId("invalid-public-client-registration-id")
83+
.clientAuthenticationMethod(ClientAuthenticationMethod.NONE)
84+
.clientSettings(ClientRegistration.ClientSettings.builder().requireProofKey(false).build())
85+
.build();
8086
this.fineRedirectUriTemplateRegistration = fineRedirectUriTemplateClientRegistration().build();
8187
// @formatter:off
8288
this.publicClientRegistration = TestClientRegistrations.clientRegistration()
@@ -92,7 +98,7 @@ public void setUp() {
9298
// @formatter:on
9399
this.clientRegistrationRepository = new InMemoryClientRegistrationRepository(this.registration1,
94100
this.registration2, this.pkceClientRegistration, this.fineRedirectUriTemplateRegistration,
95-
this.publicClientRegistration, this.oidcRegistration);
101+
this.publicClientRegistration, this.oidcRegistration, this.nonProofKeyPublicClientRegistration);
96102
this.resolver = new DefaultOAuth2AuthorizationRequestResolver(this.clientRegistrationRepository,
97103
this.authorizationRequestBaseUri);
98104
}
@@ -173,12 +179,14 @@ public void resolveWhenAuthorizationRequestWithValidClientThenResolves() {
173179
assertThat(authorizationRequest.getState()).isNotNull();
174180
assertThat(authorizationRequest.getAdditionalParameters())
175181
.doesNotContainKey(OAuth2ParameterNames.REGISTRATION_ID);
176-
assertThat(authorizationRequest.getAttributes())
177-
.containsExactly(entry(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId()));
182+
assertThat(authorizationRequest.getAttributes()).containsExactly(
183+
entry(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId()),
184+
entry(PkceParameterNames.CODE_VERIFIER,
185+
authorizationRequest.getAttributes().get(PkceParameterNames.CODE_VERIFIER)));
178186
assertThat(authorizationRequest.getAuthorizationRequestUri())
179187
.matches("https://example.com/login/oauth/authorize\\?" + "response_type=code&client_id=client-id&"
180188
+ "scope=read:user&state=.{15,}&"
181-
+ "redirect_uri=http://localhost/login/oauth2/code/registration-id");
189+
+ "redirect_uri=http://localhost/login/oauth2/code/registration-id&code_challenge=([a-zA-Z0-9\\-\\.\\_\\~]){43}&code_challenge_method=S256");
182190
}
183191

184192
@Test
@@ -190,8 +198,10 @@ public void resolveWhenClientAuthorizationRequiredExceptionAvailableThenResolves
190198
OAuth2AuthorizationRequest authorizationRequest = this.resolver.resolve(request,
191199
clientRegistration.getRegistrationId());
192200
assertThat(authorizationRequest).isNotNull();
193-
assertThat(authorizationRequest.getAttributes())
194-
.containsExactly(entry(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId()));
201+
assertThat(authorizationRequest.getAttributes()).containsExactly(
202+
entry(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId()),
203+
entry(PkceParameterNames.CODE_VERIFIER,
204+
authorizationRequest.getAttributes().get(PkceParameterNames.CODE_VERIFIER)));
195205
}
196206

197207
@Test
@@ -299,7 +309,8 @@ public void resolveWhenAuthorizationRequestIncludesPort80ThenExpandedRedirectUri
299309
assertThat(authorizationRequest.getAuthorizationRequestUri())
300310
.matches("https://example.com/login/oauth/authorize\\?" + "response_type=code&client_id=client-id&"
301311
+ "scope=read:user&state=.{15,}&"
302-
+ "redirect_uri=http://localhost/login/oauth2/code/registration-id");
312+
+ "redirect_uri=http://localhost/login/oauth2/code/registration-id"
313+
+ "&code_challenge=([a-zA-Z0-9\\-\\.\\_\\~]){43}&code_challenge_method=S256");
303314
}
304315

305316
@Test
@@ -315,7 +326,8 @@ public void resolveWhenAuthorizationRequestIncludesPort443ThenExpandedRedirectUr
315326
assertThat(authorizationRequest.getAuthorizationRequestUri())
316327
.matches("https://example.com/login/oauth/authorize\\?" + "response_type=code&client_id=client-id&"
317328
+ "scope=read:user&state=.{15,}&"
318-
+ "redirect_uri=https://example.com/login/oauth2/code/registration-id");
329+
+ "redirect_uri=https://example.com/login/oauth2/code/registration-id"
330+
+ "&code_challenge=([a-zA-Z0-9\\-\\.\\_\\~]){43}&code_challenge_method=S256");
319331
}
320332

321333
@Test
@@ -329,7 +341,7 @@ public void resolveWhenClientAuthorizationRequiredExceptionAvailableThenRedirect
329341
assertThat(authorizationRequest.getAuthorizationRequestUri())
330342
.matches("https://example.com/login/oauth/authorize\\?" + "response_type=code&client_id=client-id&"
331343
+ "scope=read:user&state=.{15,}&"
332-
+ "redirect_uri=http://localhost/authorize/oauth2/code/registration-id");
344+
+ "redirect_uri=http://localhost/authorize/oauth2/code/registration-id&code_challenge=([a-zA-Z0-9\\-\\.\\_\\~]){43}&code_challenge_method=S256");
333345
}
334346

335347
@Test
@@ -342,7 +354,8 @@ public void resolveWhenAuthorizationRequestOAuth2LoginThenRedirectUriIsLogin() {
342354
assertThat(authorizationRequest.getAuthorizationRequestUri())
343355
.matches("https://example.com/login/oauth/authorize\\?" + "response_type=code&client_id=client-id-2&"
344356
+ "scope=read:user&state=.{15,}&"
345-
+ "redirect_uri=http://localhost/login/oauth2/code/registration-id-2");
357+
+ "redirect_uri=http://localhost/login/oauth2/code/registration-id-2"
358+
+ "&code_challenge=([a-zA-Z0-9\\-\\.\\_\\~]){43}&code_challenge_method=S256");
346359
}
347360

348361
@Test
@@ -356,7 +369,8 @@ public void resolveWhenAuthorizationRequestHasActionParameterAuthorizeThenRedire
356369
assertThat(authorizationRequest.getAuthorizationRequestUri())
357370
.matches("https://example.com/login/oauth/authorize\\?" + "response_type=code&client_id=client-id&"
358371
+ "scope=read:user&state=.{15,}&"
359-
+ "redirect_uri=http://localhost/authorize/oauth2/code/registration-id");
372+
+ "redirect_uri=http://localhost/authorize/oauth2/code/registration-id&"
373+
+ "code_challenge=([a-zA-Z0-9\\-\\.\\_\\~]){43}&code_challenge_method=S256");
360374
}
361375

362376
@Test
@@ -370,7 +384,7 @@ public void resolveWhenAuthorizationRequestHasActionParameterLoginThenRedirectUr
370384
assertThat(authorizationRequest.getAuthorizationRequestUri())
371385
.matches("https://example.com/login/oauth/authorize\\?" + "response_type=code&client_id=client-id-2&"
372386
+ "scope=read:user&state=.{15,}&"
373-
+ "redirect_uri=http://localhost/login/oauth2/code/registration-id-2");
387+
+ "redirect_uri=http://localhost/login/oauth2/code/registration-id-2&code_challenge=([a-zA-Z0-9\\-\\.\\_\\~]){43}&code_challenge_method=S256");
374388
}
375389

376390
@Test
@@ -427,33 +441,32 @@ public void resolveWhenAuthorizationRequestApplyPkceToConfidentialClientsThenApp
427441
assertPkceApplied(authorizationRequest, clientRegistration);
428442
}
429443

430-
// gh-6548
431444
@Test
432-
public void resolveWhenAuthorizationRequestApplyPkceToSpecificConfidentialClientThenApplied() {
433-
this.resolver.setAuthorizationRequestCustomizer((builder) -> {
434-
builder.attributes((attrs) -> {
435-
String registrationId = (String) attrs.get(OAuth2ParameterNames.REGISTRATION_ID);
436-
if (this.registration1.getRegistrationId().equals(registrationId)) {
437-
OAuth2AuthorizationRequestCustomizers.withPkce().accept(builder);
438-
}
439-
});
440-
});
441-
445+
public void resolveWhenAuthorizationRequestApplyPkceToConfidentialClientThenApplied() {
446+
// pkce enabled by default for private clients
442447
ClientRegistration clientRegistration = this.registration1;
443448
String requestUri = this.authorizationRequestBaseUri + "/" + clientRegistration.getRegistrationId();
444449
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
445-
request.setServletPath(requestUri);
446450
OAuth2AuthorizationRequest authorizationRequest = this.resolver.resolve(request);
447451
assertPkceApplied(authorizationRequest, clientRegistration);
452+
}
448453

449-
clientRegistration = this.registration2;
450-
requestUri = this.authorizationRequestBaseUri + "/" + clientRegistration.getRegistrationId();
451-
request = new MockHttpServletRequest("GET", requestUri);
452-
request.setServletPath(requestUri);
453-
authorizationRequest = this.resolver.resolve(request);
454-
assertPkceNotApplied(authorizationRequest, clientRegistration);
454+
@Test
455+
public void resolveWhenAuthorizationRequestApplyPkceToPublicClientWithRequireProofKeyFalseThenApplied() {
456+
ClientRegistration clientRegistration = this.nonProofKeyPublicClientRegistration; // change
457+
// to
458+
// non
459+
// proof
460+
// key
461+
// public
462+
// client
463+
String requestUri = this.authorizationRequestBaseUri + "/" + clientRegistration.getRegistrationId();
464+
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
465+
OAuth2AuthorizationRequest authorizationRequest = this.resolver.resolve(request);
466+
assertPkceApplied(authorizationRequest, clientRegistration);
455467
}
456468

469+
// gh-6548
457470
private void assertPkceApplied(OAuth2AuthorizationRequest authorizationRequest,
458471
ClientRegistration clientRegistration) {
459472
assertThat(authorizationRequest.getAdditionalParameters()).containsKey(PkceParameterNames.CODE_CHALLENGE);
@@ -510,7 +523,7 @@ public void resolveWhenAuthenticationRequestWithValidOidcClientThenResolves() {
510523
.matches("https://example.com/login/oauth/authorize\\?" + "response_type=code&client_id=client-id&"
511524
+ "scope=openid&state=.{15,}&"
512525
+ "redirect_uri=http://localhost/login/oauth2/code/oidc-registration-id&"
513-
+ "nonce=([a-zA-Z0-9\\-\\.\\_\\~]){43}");
526+
+ "nonce=([a-zA-Z0-9\\-\\.\\_\\~]){43}&code_challenge=([a-zA-Z0-9\\-\\.\\_\\~]){43}&code_challenge_method=S256");
514527
}
515528

516529
// gh-7696
@@ -530,7 +543,8 @@ public void resolveWhenAuthorizationRequestCustomizerRemovesNonceThenQueryExclud
530543
assertThat(authorizationRequest.getAuthorizationRequestUri())
531544
.matches("https://example.com/login/oauth/authorize\\?" + "response_type=code&client_id=client-id&"
532545
+ "scope=openid&state=.{15,}&"
533-
+ "redirect_uri=http://localhost/login/oauth2/code/oidc-registration-id");
546+
+ "redirect_uri=http://localhost/login/oauth2/code/oidc-registration-id&"
547+
+ "code_challenge=([a-zA-Z0-9\\-\\.\\_\\~]){43}&code_challenge_method=S256");
534548
}
535549

536550
@Test
@@ -548,7 +562,8 @@ public void resolveWhenAuthorizationRequestCustomizerAddsParameterThenQueryInclu
548562
.matches("https://example.com/login/oauth/authorize\\?" + "response_type=code&client_id=client-id&"
549563
+ "scope=openid&state=.{15,}&"
550564
+ "redirect_uri=http://localhost/login/oauth2/code/oidc-registration-id&"
551-
+ "nonce=([a-zA-Z0-9\\-\\.\\_\\~]){43}&" + "param1=value1");
565+
+ "nonce=([a-zA-Z0-9\\-\\.\\_\\~]){43}"
566+
+ "&code_challenge=([a-zA-Z0-9\\-\\.\\_\\~]){43}&code_challenge_method=S256&param1=value1");
552567
}
553568

554569
@Test
@@ -565,7 +580,8 @@ public void resolveWhenAuthorizationRequestCustomizerOverridesParameterThenQuery
565580
assertThat(authorizationRequest.getAuthorizationRequestUri()).matches(
566581
"https://example.com/login/oauth/authorize\\?" + "response_type=code&" + "scope=openid&state=.{15,}&"
567582
+ "redirect_uri=http://localhost/login/oauth2/code/oidc-registration-id&"
568-
+ "nonce=([a-zA-Z0-9\\-\\.\\_\\~]){43}&" + "appid=client-id");
583+
+ "nonce=([a-zA-Z0-9\\-\\.\\_\\~]){43}"
584+
+ "&code_challenge=([a-zA-Z0-9\\-\\.\\_\\~]){43}&code_challenge_method=S256&appid=client-id");
569585
}
570586

571587
@Test

0 commit comments

Comments
 (0)