1010use Logto \Sdk \Storage \SessionStorage ;
1111use Logto \Sdk \Storage \Storage ;
1212use Logto \Sdk \Storage \StorageKey ;
13+ use Logto \Sdk \Models \DirectSignInOptions ;
14+ use Logto \Sdk \Constants \FirstScreen ;
15+ use Logto \Sdk \Constants \AuthenticationIdentifier ;
1316
1417/**
1518 * The sign-in session that stores the information for the sign-in callback.
@@ -165,21 +168,71 @@ function getRefreshToken(): ?string
165168 * Returns the sign-in URL for the given redirect URI. You should redirect the user
166169 * to the returned URL to sign in.
167170 *
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
173179 * ```php
174180 * header('Location: ' . $client->signIn("https://example.com/callback"));
175181 * ```
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+ * ```
176214 */
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 {
179223 $ codeVerifier = $ this ->oidcCore ::generateCodeVerifier ();
180224 $ codeChallenge = $ this ->oidcCore ::generateCodeChallenge ($ codeVerifier );
181225 $ 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+ );
183236
184237 foreach (StorageKey::cases () as $ key ) {
185238 $ this ->storage ->delete ($ key );
@@ -293,11 +346,20 @@ public function fetchUserInfo(): UserInfoResponse
293346 return $ this ->oidcCore ->fetchUserInfo ($ accessToken );
294347 }
295348
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 {
298359 $ pickValue = function (string |\BackedEnum $ value ): string {
299360 return $ value instanceof \BackedEnum ? $ value ->value : $ value ;
300361 };
362+
301363 $ config = $ this ->config ;
302364 $ scopes = array_unique (
303365 array_map ($ pickValue , array_merge ($ config ->scopes ?: [], $ this ->oidcCore ::DEFAULT_SCOPES ))
@@ -308,7 +370,8 @@ protected function buildSignInUrl(string $redirectUri, string $codeChallenge, st
308370 : ($ config ->resources ?: [])
309371 );
310372
311- $ query = http_build_query ([
373+ // Build the base query parameters
374+ $ queryParams = [
312375 'client_id ' => $ config ->appId ,
313376 'redirect_uri ' => $ redirectUri ,
314377 'response_type ' => 'code ' ,
@@ -317,18 +380,44 @@ protected function buildSignInUrl(string $redirectUri, string $codeChallenge, st
317380 'code_challenge ' => $ codeChallenge ,
318381 'code_challenge_method ' => 'S256 ' ,
319382 '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+ }
322419
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 ;
332421 }
333422
334423 protected function setSignInSession (SignInSession $ data ): void
0 commit comments