Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 20 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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!


Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -144,4 +180,4 @@ object KMAuthInitializer {
)
configs[providerId] = newConfig
}
}
}
4 changes: 2 additions & 2 deletions kmauth-google/kmauth_google.podspec
Original file line number Diff line number Diff line change
@@ -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 = ''
Expand Down Expand Up @@ -51,4 +51,4 @@ Pod::Spec.new do |spec|
}
]

end
end
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,18 @@ internal class GoogleAuthManagerJvm : GoogleAuthManager {
private var server: EmbeddedServer<NettyApplicationEngine, NettyApplicationEngine.Configuration>? =
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)
Expand All @@ -77,7 +88,6 @@ internal class GoogleAuthManagerJvm : GoogleAuthManager {

webClientId = KMAuthInitializer.getWebClientId(providerId)!!
clientSecret = KMAuthInitializer.getClientSecret(providerId)!!

}

override suspend fun signIn(onSignResult: (KMAuthUser?, Throwable?) -> Unit) {
Expand Down Expand Up @@ -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<NettyApplicationEngine, NettyApplicationEngine.Configuration> {
Logger.d("Starting HTTP server on port $port")
val server = embeddedServer(Netty, port = port) {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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()) {
Expand All @@ -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()
Expand Down Expand Up @@ -323,5 +358,6 @@ internal class GoogleAuthManagerJvm : GoogleAuthManager {

companion object {
private const val TAG = "GoogleAuthManagerJvm"
private const val DEFAULT_PORT = 8080
}
}
}
Loading