diff --git a/README.md b/README.md index 004af7c..a8f6008 100644 --- a/README.md +++ b/README.md @@ -33,8 +33,17 @@ KMAuthInitializer.initContext( kmAuthPlatformContext = KMAuthPlatformContext(this) ) -//For Sign In With Google, Then we need to call initialize method from common code -KMAuthInitializer.initialize(KMAuthConfig.forGoogle(webClientId = "YOUR_WEB_CLIENT_ID")) +// For Sign In With Google, call initialize method from common code. +// For Desktop (JVM) platform, googleClientRedirectHost defaults to "localhost:8080". +// IMPORTANT: The port you set here must match exactly with what you have added in your Google Cloud Console "Authorized redirect URIs" (e.g., http://localhost:8080/callback). +// If you have configured a different port in the Google Console (like 56502), you must set the same here. +KMAuthInitializer.initialize( + KMAuthConfig.forGoogle( + webClientId = "YOUR_WEB_CLIENT_ID", + googleClientRedirectHost = "localhost:56502", // Optional: Defaults to "localhost:8080" + googleClientRedirectScheme = "http" // Optional: Defaults to "http" + ) +) // For Sign In With Apple or other providers, we need to call KMAuthSupabase.initialize method from common code KMAuthSupabase.initialize( @@ -54,6 +63,13 @@ KMAuthSupabase.initialize( We need webClientId from Google Cloud Platform Console to setup the serverClientId in Google API for identifying signed-in users in backend server. +### Best Practices for Desktop (JVM) + +When deploying a Desktop application, ensure a smooth login experience: + +1. **Match App Configuration**: Ensure the `googleClientRedirectHost` in your code matches the port you've registered in your Google Cloud Console's **Authorized redirect URIs** (e.g., `http://localhost:8080/callback`). +2. **Port Availability**: Make sure the port you choose (default is 8080) is not being used by other services on your machine. + ### GoogleAuthManager, AppleAuthManager and SupabaseAuthManager After initializing the KMAuthInitializer, you can use the KMAuthGoogle object to get the @@ -379,7 +395,8 @@ KMAuthInitializer.initClientSecret( ) ``` Alternatively, you can setup clientSecret in KMAuthInitializer.initialize method itself from app composable. Then you dont need to set it here. -2. You also need to make sure you have added the redirect url in web client in google cloud platform oauth clients in **Authorized redirect URIs**: http://localhost:8080/callback, And this may take some time to reflect updates in your code while running your code. +2. **Authorized redirect URIs**: You must add your loopback URI to the Google Cloud Console's **Authorized redirect URIs** section (e.g., `http://localhost:8080/callback`). + * **IMPORTANT**: The port you set in your `KMAuthConfig` (default is 8080) must match exactly with what you have added in the Google Console. #### Web (Kotlin/Js and Kotlin/Wasm) @@ -483,5 +500,3 @@ Feel free to contribute if you finds any issues or bugs or want to add a new aut ## Used By Projects List Checkout a voluntary list of projects/companies using KotlinMultiplatformAuth: https://github.com/sunildhiman90/KotlinMultiplatformAuth/discussions/3. Feel free to add your project! - - diff --git a/convention-plugins/src/main/kotlin/root.publication.gradle.kts b/convention-plugins/src/main/kotlin/root.publication.gradle.kts index ff4d2be..9d67e06 100644 --- a/convention-plugins/src/main/kotlin/root.publication.gradle.kts +++ b/convention-plugins/src/main/kotlin/root.publication.gradle.kts @@ -5,5 +5,5 @@ plugins { // these will be used for all projects ie. kmauth-core, kmauth-google etc. allprojects { group = "io.github.sunildhiman90" - version = "0.3.4" + version = "0.3.5" } \ No newline at end of file diff --git a/kmauth-core/src/commonMain/kotlin/com/sunildhiman90/kmauth/core/KMAuthConfig.kt b/kmauth-core/src/commonMain/kotlin/com/sunildhiman90/kmauth/core/KMAuthConfig.kt index 390eb3c..924338b 100644 --- a/kmauth-core/src/commonMain/kotlin/com/sunildhiman90/kmauth/core/KMAuthConfig.kt +++ b/kmauth-core/src/commonMain/kotlin/com/sunildhiman90/kmauth/core/KMAuthConfig.kt @@ -27,6 +27,8 @@ data class KMAuthConfig( val autoRefreshToken: Boolean = true, val deepLinkHost: String? = null, val deepLinkScheme: String? = null, + val googleClientRedirectHost: String? = "localhost:8080", + val googleClientRedirectScheme: String? = "http", val flowType: KMAuthSupabaseFlowType? = null ) { init { @@ -80,11 +82,15 @@ data class KMAuthConfig( fun forGoogle( webClientId: String, kmAuthPlatformContext: KMAuthPlatformContext? = null, - clientSecret: String? = null + clientSecret: String? = null, + googleClientRedirectHost: String? = "localhost:8080", + googleClientRedirectScheme: String? = "http" ): KMAuthConfig { val kmAuthConfig = KMAuthConfig( webClientId = webClientId, - clientSecret = clientSecret + clientSecret = clientSecret, + googleClientRedirectHost = googleClientRedirectHost, + googleClientRedirectScheme = googleClientRedirectScheme ) return if (kmAuthPlatformContext != null) { kmAuthConfig.copy( diff --git a/kmauth-core/src/commonMain/kotlin/com/sunildhiman90/kmauth/core/KMAuthInitializer.kt b/kmauth-core/src/commonMain/kotlin/com/sunildhiman90/kmauth/core/KMAuthInitializer.kt index 9cfe255..ae99e58 100644 --- a/kmauth-core/src/commonMain/kotlin/com/sunildhiman90/kmauth/core/KMAuthInitializer.kt +++ b/kmauth-core/src/commonMain/kotlin/com/sunildhiman90/kmauth/core/KMAuthInitializer.kt @@ -25,7 +25,13 @@ object KMAuthInitializer { supabaseUrl = config.supabaseUrl ?: existingConfig.supabaseUrl, supabaseKey = config.supabaseKey ?: existingConfig.supabaseKey, kmAuthPlatformContext = config.kmAuthPlatformContext - ?: existingConfig.kmAuthPlatformContext + ?: existingConfig.kmAuthPlatformContext, + deepLinkHost = config.deepLinkHost ?: existingConfig.deepLinkHost, + deepLinkScheme = config.deepLinkScheme ?: existingConfig.deepLinkScheme, + googleClientRedirectHost = config.googleClientRedirectHost + ?: existingConfig.googleClientRedirectHost, + googleClientRedirectScheme = config.googleClientRedirectScheme + ?: existingConfig.googleClientRedirectScheme ) ?: config @@ -84,6 +90,36 @@ object KMAuthInitializer { */ fun getKMAuthPlatformContext(): KMAuthPlatformContext? = kmAuthPlatformContext + /** + * Gets the deep link host from the configuration of the specified provider. + * + * @return The deep link host or null if not configured. + */ + fun getDeepLinkHost(providerId: String): String? = configs[providerId]?.deepLinkHost + + /** + * Gets the deep link scheme from the configuration of the specified provider. + * + * @return The deep link scheme or null if not configured. + */ + fun getDeepLinkScheme(providerId: String): String? = configs[providerId]?.deepLinkScheme + + /** + * Gets the google client redirect host from the configuration of the specified provider. + * + * @return The google client redirect host or null if not configured. + */ + fun getGoogleClientRedirectHost(providerId: String): String? = + configs[providerId]?.googleClientRedirectHost + + /** + * Gets the google client redirect scheme from the configuration of the specified provider. + * + * @return The google client redirect scheme or null if not configured. + */ + fun getGoogleClientRedirectScheme(providerId: String): String? = + configs[providerId]?.googleClientRedirectScheme + // Deprecated methods for backward compatibility @Deprecated( "Use initialize(config) with a proper KMAuthConfig instance instead", @@ -144,4 +180,4 @@ object KMAuthInitializer { ) configs[providerId] = newConfig } -} \ No newline at end of file +} diff --git a/kmauth-google/kmauth_google.podspec b/kmauth-google/kmauth_google.podspec index e96dcc9..7d9f678 100644 --- a/kmauth-google/kmauth_google.podspec +++ b/kmauth-google/kmauth_google.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = 'kmauth_google' - spec.version = '0.3.4' + spec.version = '0.3.5' spec.homepage = '' spec.source = { :http=> ''} spec.authors = '' @@ -51,4 +51,4 @@ Pod::Spec.new do |spec| } ] -end \ No newline at end of file +end diff --git a/kmauth-google/src/jvmMain/kotlin/com/sunildhiman90/kmauth/google/GoogleAuthManagerJvm.kt b/kmauth-google/src/jvmMain/kotlin/com/sunildhiman90/kmauth/google/GoogleAuthManagerJvm.kt index 9a1c297..1aa0cb9 100644 --- a/kmauth-google/src/jvmMain/kotlin/com/sunildhiman90/kmauth/google/GoogleAuthManagerJvm.kt +++ b/kmauth-google/src/jvmMain/kotlin/com/sunildhiman90/kmauth/google/GoogleAuthManagerJvm.kt @@ -52,7 +52,18 @@ internal class GoogleAuthManagerJvm : GoogleAuthManager { private var server: EmbeddedServer? = null - private val redirectUri = "http://localhost:8080/callback" // Ktor will listen on this URI + private var actualPort: Int = DEFAULT_PORT + private val redirectUri: String + get() { + val host = KMAuthInitializer.getGoogleClientRedirectHost(providerId) ?: "localhost" + val hostWithoutPort = host.split(":").first() + val scheme = KMAuthInitializer.getGoogleClientRedirectScheme(providerId) ?: "http" + return "$scheme://$hostWithoutPort:$actualPort/callback" + } + private fun getPort(): Int { + val host = KMAuthInitializer.getGoogleClientRedirectHost(providerId) ?: return DEFAULT_PORT + return host.split(":").lastOrNull()?.toIntOrNull() ?: DEFAULT_PORT + } private var uniqueUserId: String? = null private var onSignResult: ((KMAuthUser?, Throwable?) -> Unit)? = null private var scope = CoroutineScope(Dispatchers.IO) @@ -77,7 +88,6 @@ internal class GoogleAuthManagerJvm : GoogleAuthManager { webClientId = KMAuthInitializer.getWebClientId(providerId)!! clientSecret = KMAuthInitializer.getClientSecret(providerId)!! - } override suspend fun signIn(onSignResult: (KMAuthUser?, Throwable?) -> Unit) { @@ -110,7 +120,7 @@ internal class GoogleAuthManagerJvm : GoogleAuthManager { private fun startHttpServer( flow: GoogleAuthorizationCodeFlow, onSignResult: ((KMAuthUser?, Throwable?) -> Unit)? = null, - port: Int = 8080 + port: Int = actualPort ): EmbeddedServer { Logger.d("Starting HTTP server on port $port") val server = embeddedServer(Netty, port = port) { @@ -193,6 +203,18 @@ internal class GoogleAuthManagerJvm : GoogleAuthManager { return server } + private fun findAvailablePort(startPort: Int): Int { + var port = startPort + while (port < startPort + 100) { + try { + java.net.ServerSocket(port).use { return port } + } catch (e: Exception) { + port++ + } + } + return startPort + } + private fun performShutdownCleanup() { scope.launch { try { @@ -223,7 +245,10 @@ internal class GoogleAuthManagerJvm : GoogleAuthManager { val flow = initializeGoogleAuthCodeFlow() + actualPort = getPort() + Logger.d("Using Redirect URI: $redirectUri") val authorizationUrl = flow.newAuthorizationUrl().setRedirectUri(redirectUri).build() + Logger.d("Opening Authorization URL: $authorizationUrl") // Open the user's default web browser to authenticate if (Desktop.isDesktopSupported()) { @@ -234,7 +259,17 @@ internal class GoogleAuthManagerJvm : GoogleAuthManager { // We need to reinitialize the scope, otherwise it will throw exception second time becoz we are cancelling the scope after stopping the server and cancelled scope can not be used to launch coroutine again without recreating new scope scope = CoroutineScope(Dispatchers.IO) scope.launch { - server = startHttpServer(flow, onSignResult) + try { + server = startHttpServer(flow, onSignResult, actualPort) + } catch (e: Exception) { + val errorMessage = if (e is java.net.BindException || e.message?.contains("Address already in use") == true) { + "Port $actualPort is already in use. Please ensure no other service is using this port or configure a different port in KMAuthConfig.forGoogle(googleClientRedirectHost = \"localhost:YOUR_PORT\") and register it in Google Cloud Console web client as well." + } else { + "Failed to start local server for Google Auth: ${e.message}" + } + Logger.e(errorMessage) + onSignResult?.invoke(null, Exception(errorMessage, e)) + } } } catch (e: Exception) { e.printStackTrace() @@ -323,5 +358,6 @@ internal class GoogleAuthManagerJvm : GoogleAuthManager { companion object { private const val TAG = "GoogleAuthManagerJvm" + private const val DEFAULT_PORT = 8080 } -} \ No newline at end of file +}