55package kotlinx.rpc.grpc.client
66
77import kotlinx.rpc.grpc.GrpcMetadata
8+ import kotlinx.rpc.grpc.descriptor.MethodDescriptor
9+ import kotlinx.rpc.grpc.plus
810
911/* *
1012 * Provides per-call authentication credentials for gRPC calls.
@@ -20,12 +22,36 @@ import kotlinx.rpc.grpc.GrpcMetadata
2022 *
2123 * ```kotlin
2224 * class BearerTokenCredentials(private val token: String) : GrpcCallCredentials {
23- * override suspend fun GrpcMetadata.applyOnMetadata(callOptions: GrpcCallOptions) {
24- * append("Authorization", "Bearer $token")
25+ * override suspend fun Context.getRequestMetadata(): GrpcMetadata {
26+ * return buildGrpcMetadata {
27+ * append("Authorization", "Bearer $token")
28+ * }
2529 * }
2630 * }
2731 * ```
2832 *
33+ * ## Context-Aware Credentials
34+ *
35+ * Use the [Context] to implement sophisticated authentication strategies:
36+ *
37+ * ```kotlin
38+ * class MethodScopedCredentials : GrpcCallCredentials {
39+ * override suspend fun Context.getRequestMetadata(): GrpcMetadata {
40+ * val scope = when (method.name) {
41+ * "GetUser" -> "read:users"
42+ * "UpdateUser" -> "write:users"
43+ * else -> "default"
44+ * }
45+ * val token = fetchTokenWithScope(scope)
46+ * return buildGrpcMetadata {
47+ * append("Authorization", "Bearer $token")
48+ * }
49+ * }
50+ * }
51+ * ```
52+ *
53+ * ## Combining Credentials
54+ *
2955 * Credentials can be combined using the [plus] operator or [combine] function:
3056 *
3157 * ```kotlin
@@ -37,52 +63,70 @@ import kotlinx.rpc.grpc.GrpcMetadata
3763 * By default, call credentials require transport security (TLS) to prevent credential leakage.
3864 * Override [requiresTransportSecurity] to `false` only for testing or non-production environments.
3965 *
40- * @see applyOnMetadata
66+ * @see getRequestMetadata
67+ * @see Context
4168 * @see requiresTransportSecurity
4269 * @see plus
4370 * @see combine
4471 */
4572public interface GrpcCallCredentials {
4673
4774 /* *
48- * Applies authentication metadata to the gRPC call.
75+ * Retrieves authentication metadata for the gRPC call.
76+ *
77+ * This method is invoked before each gRPC call to generate authentication headers or metadata.
78+ * Implementations should return a [GrpcMetadata] object containing the necessary authentication
79+ * information for the request.
4980 *
50- * This method is invoked before each gRPC call to add authentication headers or metadata.
51- * Implementations should append the necessary authentication information to the [GrpcMetadata] receiver.
81+ * The method is suspending to allow asynchronous operations such as:
82+ * - Token retrieval from secure storage
83+ * - OAuth token refresh or exchange
84+ * - Dynamic token generation or signing
85+ * - Network calls to authentication services
5286 *
53- * The method is suspending to allow asynchronous token retrieval or refresh operations,
54- * such as fetching tokens from secure storage or performing OAuth token exchanges.
87+ * ## Context Information
88+ *
89+ * The [Context] receiver provides access to call-specific information:
90+ * - [Context.method]: The method being invoked (for method-specific auth)
91+ * - [Context.authority]: The target authority (for tenant-aware auth)
5592 *
5693 * ## Examples
5794 *
58- * Adding a bearer token:
95+ * Simple bearer token:
5996 * ```kotlin
60- * override suspend fun GrpcMetadata.applyOnMetadata(callOptions: GrpcCallOptions) {
61- * append("Authorization", "Bearer $token")
97+ * override suspend fun Context.getRequestMetadata(): GrpcMetadata {
98+ * return buildGrpcMetadata {
99+ * append("Authorization", "Bearer $token")
100+ * }
62101 * }
63102 * ```
64103 *
65104 * Throwing a [kotlinx.rpc.grpc.StatusException] to fail the call:
66105 * ```kotlin
67- * override suspend fun GrpcMetadata.applyOnMetadata(callOptions: GrpcCallOptions) {
68- * if (!isValid) {
69- * throw StatusException(Status(StatusCode.UNAUTHENTICATED, "Invalid credentials"))
106+ * override suspend fun Context.getRequestMetadata(): GrpcMetadata {
107+ * val token = try {
108+ * refreshToken()
109+ * } catch (e: Exception) {
110+ * throw StatusException(Status(StatusCode.UNAUTHENTICATED, "Token refresh failed"))
111+ * }
112+ *
113+ * return buildGrpcMetadata {
114+ * append("Authorization", "Bearer $token")
70115 * }
71- * append("Authorization", "Bearer $token")
72116 * }
73117 * ```
74118 *
75- * @param callOptions The options for the current call, providing context and configuration .
76- * @receiver The metadata to which authentication information should be added .
119+ * @receiver Context information about the call being authenticated .
120+ * @return Metadata containing authentication information to attach to the request .
77121 * @throws kotlinx.rpc.grpc.StatusException to abort the call with a specific gRPC status.
78122 */
79- public suspend fun GrpcMetadata. applyOnMetadata ( callOptions : GrpcCallOptions )
123+ public suspend fun Context. getRequestMetadata (): GrpcMetadata
80124
81125 /* *
82126 * Indicates whether this credential requires transport security (TLS).
83127 *
84128 * When `true` (the default), the credential will only be applied to calls over secure transports.
85- * If transport security is not present, the call will fail with ` UNAUTHENTICATED` .
129+ * If transport security is not present, the call will fail with [kotlinx.rpc.grpc.StatusCode. UNAUTHENTICATED] .
86130 *
87131 * Set to `false` only for credentials that are safe to send over insecure connections,
88132 * such as in testing environments or for non-sensitive authentication mechanisms.
@@ -91,6 +135,20 @@ public interface GrpcCallCredentials {
91135 */
92136 public val requiresTransportSecurity: Boolean
93137 get() = true
138+
139+ /* *
140+ * Context information available when retrieving call credentials.
141+ *
142+ * Provides metadata about the RPC call to enable method-specific authentication strategies.
143+ *
144+ * @property method The method descriptor of the RPC being invoked.
145+ * @property authority The authority (host:port) for this call.
146+ */
147+ // TODO: check whether we should add GrpcCallOptions in the context (KRPC-232)
148+ public data class Context (
149+ val method : MethodDescriptor <* , * >,
150+ val authority : String ,
151+ )
94152}
95153
96154/* *
@@ -154,8 +212,8 @@ public fun GrpcCallCredentials.combine(other: GrpcCallCredentials): GrpcCallCred
154212 * ```
155213 */
156214public object EmptyCallCredentials : GrpcCallCredentials {
157- override suspend fun GrpcMetadata. applyOnMetadata ( callOptions : GrpcCallOptions ) {
158- // do nothing
215+ override suspend fun GrpcCallCredentials.Context. getRequestMetadata (): GrpcMetadata {
216+ return GrpcMetadata ()
159217 }
160218 override val requiresTransportSecurity: Boolean = false
161219}
@@ -164,15 +222,10 @@ internal class CombinedCallCredentials(
164222 private val first : GrpcCallCredentials ,
165223 private val second : GrpcCallCredentials
166224) : GrpcCallCredentials {
167- override suspend fun GrpcMetadata.applyOnMetadata (
168- callOptions : GrpcCallOptions
169- ) {
170- with (first) {
171- this @applyOnMetadata.applyOnMetadata(callOptions)
172- }
173- with (second) {
174- this @applyOnMetadata.applyOnMetadata(callOptions)
175- }
225+ override suspend fun GrpcCallCredentials.Context.getRequestMetadata (): GrpcMetadata {
226+ val firstMetadata = with (first) { getRequestMetadata() }
227+ val secondMetadata = with (second) { getRequestMetadata() }
228+ return firstMetadata + secondMetadata
176229 }
177230
178231 override val requiresTransportSecurity: Boolean = first.requiresTransportSecurity || second.requiresTransportSecurity
0 commit comments