10
10
use Logto \Sdk \Storage \SessionStorage ;
11
11
use Logto \Sdk \Storage \Storage ;
12
12
use Logto \Sdk \Storage \StorageKey ;
13
+ use Logto \Sdk \Models \DirectSignInOptions ;
14
+ use Logto \Sdk \Constants \FirstScreen ;
15
+ use Logto \Sdk \Constants \AuthenticationIdentifier ;
13
16
14
17
/**
15
18
* The sign-in session that stores the information for the sign-in callback.
@@ -165,21 +168,71 @@ function getRefreshToken(): ?string
165
168
* Returns the sign-in URL for the given redirect URI. You should redirect the user
166
169
* to the returned URL to sign in.
167
170
*
168
- * By specifying the interaction mode, you can control whether the user will be
169
- * prompted for sign-in or sign-up on the first screen. If the interaction mode is
170
- * not specified, the default one will be used.
171
- *
172
- * @example
171
+ * @param string $redirectUri The URI to redirect to after sign-in
172
+ * @param ?InteractionMode $interactionMode Controls whether to show sign-in or sign-up UI first
173
+ * @param ?DirectSignInOptions $directSignIn Direct sign-in configuration for social or SSO, see details at https://docs.logto.io/docs/references/openid-connect/authentication-parameters/#direct-sign-in
174
+ * @param ?FirstScreen $firstScreen Controls which screen to show first (sign-in or register), see details at https://docs.logto.io/docs/references/openid-connect/authentication-parameters/#first-screen
175
+ * @param ?array $identifiers Array of authentication identifiers (email, phone, username) to enable, this parameter MUST work with `firstScreen` parameter
176
+ * @param ?array $extraParams Additional query parameters to include in the sign-in URL
177
+ *
178
+ * @example Basic sign-in
173
179
* ```php
174
180
* header('Location: ' . $client->signIn("https://example.com/callback"));
175
181
* ```
182
+ *
183
+ * @example Sign-in with social provider
184
+ * ```php
185
+ * $directSignIn = new DirectSignInOptions(
186
+ * method: DirectSignInMethod::social,
187
+ * target: 'github'
188
+ * );
189
+ * header('Location: ' . $client->signIn(
190
+ * "https://example.com/callback",
191
+ * directSignIn: $directSignIn
192
+ * ));
193
+ * ```
194
+ *
195
+ * @example Sign-in with specific identifiers
196
+ * ```php
197
+ * header('Location: ' . $client->signIn(
198
+ * "https://example.com/callback",
199
+ * firstScreen: FirstScreen::signIn,
200
+ * identifiers: [AuthenticationIdentifier::email, AuthenticationIdentifier::username]
201
+ * ));
202
+ * ```
203
+ *
204
+ * @example Sign-in with additional parameters
205
+ * ```php
206
+ * header('Location: ' . $client->signIn(
207
+ * "https://example.com/callback",
208
+ * extraParams: [
209
+ * 'foo' => 'bar',
210
+ * 'baz' => 'qux'
211
+ * ]
212
+ * ));
213
+ * ```
176
214
*/
177
- function signIn (string $ redirectUri , ?InteractionMode $ interactionMode = null ): string
178
- {
215
+ function signIn (
216
+ string $ redirectUri ,
217
+ ?InteractionMode $ interactionMode = null ,
218
+ ?DirectSignInOptions $ directSignIn = null ,
219
+ ?FirstScreen $ firstScreen = null ,
220
+ ?array $ identifiers = null ,
221
+ ?array $ extraParams = null
222
+ ): string {
179
223
$ codeVerifier = $ this ->oidcCore ::generateCodeVerifier ();
180
224
$ codeChallenge = $ this ->oidcCore ::generateCodeChallenge ($ codeVerifier );
181
225
$ state = $ this ->oidcCore ::generateState ();
182
- $ signInUrl = $ this ->buildSignInUrl ($ redirectUri , $ codeChallenge , $ state , $ interactionMode );
226
+ $ signInUrl = $ this ->buildSignInUrl (
227
+ $ redirectUri ,
228
+ $ codeChallenge ,
229
+ $ state ,
230
+ $ interactionMode ,
231
+ $ directSignIn ,
232
+ $ firstScreen ,
233
+ $ identifiers ,
234
+ $ extraParams
235
+ );
183
236
184
237
foreach (StorageKey::cases () as $ key ) {
185
238
$ this ->storage ->delete ($ key );
@@ -293,11 +346,20 @@ public function fetchUserInfo(): UserInfoResponse
293
346
return $ this ->oidcCore ->fetchUserInfo ($ accessToken );
294
347
}
295
348
296
- protected function buildSignInUrl (string $ redirectUri , string $ codeChallenge , string $ state , ?InteractionMode $ interactionMode ): string
297
- {
349
+ protected function buildSignInUrl (
350
+ string $ redirectUri ,
351
+ string $ codeChallenge ,
352
+ string $ state ,
353
+ ?InteractionMode $ interactionMode ,
354
+ ?DirectSignInOptions $ directSignIn = null ,
355
+ ?FirstScreen $ firstScreen = null ,
356
+ ?array $ identifiers = null ,
357
+ ?array $ extraParams = null
358
+ ): string {
298
359
$ pickValue = function (string |\BackedEnum $ value ): string {
299
360
return $ value instanceof \BackedEnum ? $ value ->value : $ value ;
300
361
};
362
+
301
363
$ config = $ this ->config ;
302
364
$ scopes = array_unique (
303
365
array_map ($ pickValue , array_merge ($ config ->scopes ?: [], $ this ->oidcCore ::DEFAULT_SCOPES ))
@@ -308,7 +370,8 @@ protected function buildSignInUrl(string $redirectUri, string $codeChallenge, st
308
370
: ($ config ->resources ?: [])
309
371
);
310
372
311
- $ query = http_build_query ([
373
+ // Build the base query parameters
374
+ $ queryParams = [
312
375
'client_id ' => $ config ->appId ,
313
376
'redirect_uri ' => $ redirectUri ,
314
377
'response_type ' => 'code ' ,
@@ -317,18 +380,44 @@ protected function buildSignInUrl(string $redirectUri, string $codeChallenge, st
317
380
'code_challenge ' => $ codeChallenge ,
318
381
'code_challenge_method ' => 'S256 ' ,
319
382
'state ' => $ state ,
320
- 'interaction_mode ' => $ interactionMode ?->value,
321
- ]);
383
+ ];
384
+
385
+ // Add optional parameters
386
+ if ($ interactionMode !== null ) {
387
+ $ queryParams ['interaction_mode ' ] = $ interactionMode ->value ;
388
+ }
389
+
390
+ if ($ firstScreen !== null ) {
391
+ $ queryParams ['first_screen ' ] = $ firstScreen ->value ;
392
+ }
393
+
394
+ // Handle the `identifiers` array parameter
395
+ if ($ identifiers !== null && count ($ identifiers ) > 0 ) {
396
+ $ queryParams ['identifier ' ] = implode (' ' , array_map ($ pickValue , $ identifiers ));
397
+ }
398
+
399
+ // Handle the `direct_sign_in` parameter
400
+ if ($ directSignIn !== null ) {
401
+ $ queryParams ['direct_sign_in ' ] = $ directSignIn ->method ->value . ': ' . $ directSignIn ->target ;
402
+ }
403
+
404
+ // Merge the extra query parameters
405
+ if ($ extraParams !== null ) {
406
+ $ queryParams = array_merge ($ queryParams , $ extraParams );
407
+ }
408
+
409
+ // Build the base URL
410
+ $ url = $ this ->oidcCore ->metadata ->authorization_endpoint . '? ' . http_build_query ($ queryParams );
411
+
412
+ // Add the `resource` parameters
413
+ if (count ($ resources ) > 0 ) {
414
+ $ url .= '& ' . implode ('& ' , array_map (
415
+ fn ($ resource ) => "resource= " . urlencode ($ resource ),
416
+ $ resources
417
+ ));
418
+ }
322
419
323
- return $ this ->oidcCore ->metadata ->authorization_endpoint .
324
- '? ' .
325
- $ query .
326
- (
327
- count ($ resources ) > 0 ?
328
- # Resources need to use the same key name as the query string
329
- '& ' . implode ('& ' , array_map (fn ($ resource ) => "resource= " . urlencode ($ resource ), $ resources )) :
330
- ''
331
- );
420
+ return $ url ;
332
421
}
333
422
334
423
protected function setSignInSession (SignInSession $ data ): void
0 commit comments