Kotlin / kotlinx-rpc

Add asynchronous RPC services to your multiplatform applications.
https://kotlin.github.io/kotlinx-rpc/
Apache License 2.0
688 stars 16 forks source link

Support for json web token? #177

Open EricDeng1001 opened 3 weeks ago

EricDeng1001 commented 3 weeks ago

I have a set of Service that must be used after login. I issue jwt to client so they can identify themselves. And I also got a AuthService which issue this token. So how do I setup my rpc server and client for this scenario? For example, how do I carry this jwt in client, and how do I verify this jwt in server? Besides, do I need to setup two paths, one for AuthService, because it does not require jwt, and another for rest of services, which requires jwt?

Mr3zee commented 3 weeks ago

Hi! If you are using kRPC protocol with Ktor integration - they have a JWT plugin for this: https://ktor.io/docs/server-jwt.html#authenticate-route

Otherwise - no builtin solution is available

EricDeng1001 commented 3 weeks ago

Hello! I am already using this, but encounter a problem: when user not logged in, trying to connect ws returns 401, and service creation is failed. But I must have init these service when my app start. Another problem is, it seems that rpc request is not carrtying bearer token like normal http call. I will post my config code below.

EricDeng1001 commented 3 weeks ago

client:

object RPC {
    private val client = HttpClient {
        installRPC()
        install(Auth) {
            bearer {
                loadTokens {
                    BearerTokens(UserContext.token, "")
                }
                refreshTokens {
                    BearerTokens(UserContext.token, "")
                }
            }
        }
    }
    private lateinit var rpcClient: RPCClient
    lateinit var authService: AuthService
    lateinit var subscriptionManager: SubscriptionManager
    lateinit var channelManager: ChannelManager
    lateinit var purchaseService: PurchaseService
    lateinit var accountManager: AccountManager
    lateinit var pushManager: PushManager

    suspend fun init() {
        rpcClient = client.rpc {
            url("ws://localhost:3000/api")

            rpcConfig {
                serialization {
                    json()
                }
            }
        }
        authService = client.rpc {
            url("ws://localhost:3000/api/auth")

            rpcConfig {
                serialization {
                    json()
                }
            }
        }.withService()
        subscriptionManager = rpcClient.withService()
        channelManager = rpcClient.withService()
        purchaseService = rpcClient.withService()
        accountManager = rpcClient.withService()
        pushManager = rpcClient.withService()
    }
}

And init is called when App start using LauchedEffect. server:

fun Application.module() {
    install(CORS) {
        allowOrigins { true }
    }
    install(RPC) {

    }
    install(Authentication) {
        jwt {
            verifier(
                AuthContext.verifier
            )
            validate { credential ->
                AuthContext.validate(credential)
            }
            challenge { defaultScheme, realm ->
                call.respond(HttpStatusCode.Unauthorized, "Token is not valid or has expired")
            }
            realm = "Access to maotao"
        }
    }
    install(DoubleReceive)
    install(CallLogging) {
        level = Level.INFO
        format { call ->
            runBlocking {
                val status = call.response.status()
                val httpMethod = call.request.httpMethod.value
                val userAgent = call.request.headers["User-Agent"]
                val body = call.receiveText()
                val uri = call.request.uri
                "$httpMethod $uri $status \n$userAgent \n$body"
            }
        }
    }

    routing {
        pwaDispatch()
        auth()
        authenticate {
            api()
        }
    }
}

And when app is started, server logged:

2024-08-21 21:12:50.065 [eventLoopGroupProxy-4-1] INFO  Application - GET /api 401 Unauthorized 
Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1 Edg/127.0.0.0 
EricDeng1001 commented 3 weeks ago

I was using REST before, and auth is 100% ok. I just switch from normal ktor to rpc.

Mr3zee commented 3 weeks ago

@EricDeng1001 your auth service is under url("ws://localhost:3000/api/auth") protected route:

authenticate {
    api()
}

Hence the error. You can try to move auth service into another route, which is not protocted. At least that is what I understood from the Ktor docs

Mr3zee commented 2 weeks ago

@EricDeng1001 were you able to resolve the issue?

EricDeng1001 commented 2 weeks ago

I will try to add headers and see. Will report asap.

EricDeng1001 commented 2 weeks ago

Need newest patch to test it in production env. Waiting for it.