kotlinx-rpc
kotlinx-rpc copied to clipboard
Support for json web token?
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?
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
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.
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
I was using REST before, and auth is 100% ok. I just switch from normal ktor to rpc.
@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
@EricDeng1001 were you able to resolve the issue?
I will try to add headers and see. Will report asap.
Need newest patch to test it in production env. Waiting for it.
why not just pass the access token with the function arguments?
Did anyone find a solution for this? I'm not able to find a way to make my authenticated services to work. In my case I do have this:
// Server
fun Application.routes() = routing {
authenticate("auth-jwt") {
rpc("/bire") {
rpcConfig {
serialization { json() }
}
registerService<TransactionsService> { context ->
TransactionsRpcService(context)
}
}
}
}
// Client
fun rpcClient(httpClient: HttpClient): suspend () -> RPCClient = {
httpClient.rpc {
url("ws://localhost:8080/bire")
bearerAuth("token") // 1) should this inject the token in the websocket request?
headers["Authentication"] = "Bearer token" // 2) I also tried this but same result
rpcConfig {
waitForServices = true
serialization {
json()
}
}
}
}
cc @EricDeng1001 @Mr3zee
Bearer authentication allows passing the token in the url query. That is how I worked around this.
Source https://datatracker.ietf.org/doc/html/rfc6750#section-2.3
Obviously it is needed to by handled manually since Ktor utilizes the Authorization header only
yes I did something similar, thanks @lsafer-meemer 🙏🏻
As op I no longer have time to investigate in this issue, so closing it. Any future related issue should open a new one. And thanks for the author for making efforts in such a wonderful plugin.