一键导入
maskinporten
Maskinporten JWT-assertion flow med AtomicReference + Mutex token-caching for server-til-server autentisering mot SKE
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Maskinporten JWT-assertion flow med AtomicReference + Mutex token-caching for server-til-server autentisering mot SKE
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Flyway-migrasjoner for PostgreSQL: navnekonvensjoner (V{n}__desc.sql), skjema for krav/feilmelding/filvalideringsfeil, DBListener-testing med TestContainers
Post-send livssyklus: StatusService poller /mottaksstatus; StoppKravService; RapportService + AvstemmingRouting-frontend for manuell reskontroføring
HOCON-basert konfigurasjon med PropertiesConfig singleton for Ktor batch-tjenester på NAIS (FSS)
SlackService feilakkumulering og Tags-basert varsling for prosesseringsfeil i SKE-batch
Endring av eksisterende krav (endre rente og endre hovedstol): EndreKravService, request-DTOer, status-konformering og SKE API PUT-endepunkter
Kotest BehaviorSpec/MockK-mønstre for sokos-ske-krav: scenariostruktur, DBListener/SftpListener, MockHttpClient, circuit-breaker-reset og matchers
| name | maskinporten |
| description | Maskinporten JWT-assertion flow med AtomicReference + Mutex token-caching for server-til-server autentisering mot SKE |
MaskinportenAccessTokenProvider performs the JWT Bearer Grant flow against Maskinporten and caches the access token in memory until just before expiry. Used by SkeClient on every call.
openid-configuration from wellKnownUrl → tokenEndpoint, issueriss = clientId, aud = issuer, scope, exp, random jti)grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer → receive access_tokenMutex)Authorization: Bearer <token> on every SKE call| Decision | Reason |
|---|---|
AtomicReference<AccessToken?> | Lock-free read in the common path (cache hit) |
Mutex.withLock {} around refresh | Prevents thundering-herd when many coroutines race to refresh |
| Renew 60 s before expiry | Guards against clock skew vs. Maskinporten |
jti = UUID.randomUUID() | Required by Maskinporten to prevent replay |
scopes passed through HOCON | Multiple scopes go as space-separated string |
maskinportenClient {
clientId = ${MASKINPORTEN_CLIENT_ID}
wellKnownUrl = ${MASKINPORTEN_WELL_KNOWN_URL}
rsaKeyString = ${MASKINPORTEN_CLIENT_JWK}
scopes = ${MASKINPORTEN_SCOPES}
}
@Serializable
data class MaskinportenClientConfig(
val clientId: String,
val wellKnownUrl: String,
val rsaKeyString: String,
val scopes: String,
) {
val rsaKey: RSAKey? by lazy { RSAKey.parse(rsaKeyString) }
}
class MaskinportenAccessTokenProvider(
private val maskinportenConfig: MaskinportenClientConfig = PropertiesConfig.maskinportenClientProperties,
private val client: HttpClient,
) {
suspend fun getAccessToken(): String // the only public method
}
Both parameters are injectable, so tests pass mockk(relaxed = true).
SkeClient calls tokenProvider.getAccessToken() before every request and appends:
headers {
append(HttpHeaders.Authorization, "Bearer $token")
append("x-correlation-id", corrID)
append("klient-id", KLIENT_ID)
}
Maskinporten is always mocked — tests never reach the real endpoint:
val skeClient = SkeClient(
skeEndpoint = "",
client = MockHttpClient.client(MockResponse(Endpoint.OPPRETT, body, HttpStatusCode.OK)),
tokenProvider = mockk<MaskinportenAccessTokenProvider>(relaxed = true),
)
The relaxed mock returns "" from getAccessToken() — fine since MockEngine ignores headers.
PropertiesConfig.maskinportenClientProperties for configTEAM_LOGS_MARKER (JWT assertion contains key material)MaskinportenAccessTokenProvider in testsaccess_token, assertion, or raw JWKs at any levelMutex around the refresh blockexpiresIn - 60s