Skip to content

Commit

Permalink
feat: basic client authentication for token exchange grant (#564)
Browse files Browse the repository at this point in the history
* feat: basic client authentication for token exchange grant

* fix: test title too long

---------

Co-authored-by: Youssef Bel Mekki <[email protected]>
  • Loading branch information
valdemon and ybelMekk authored Oct 17, 2023
1 parent 6b6c5ba commit 3019aa3
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package no.nav.security.mock.oauth2.http

import com.nimbusds.oauth2.sdk.GrantType
import com.nimbusds.oauth2.sdk.TokenRequest
import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod
import com.nimbusds.oauth2.sdk.http.HTTPRequest
import com.nimbusds.openid.connect.sdk.AuthenticationRequest
import no.nav.security.mock.oauth2.extensions.clientAuthentication
Expand Down Expand Up @@ -32,7 +33,10 @@ data class OAuth2HttpRequest(

fun asTokenExchangeRequest(): TokenRequest {
val httpRequest: HTTPRequest = this.asNimbusHTTPRequest()
val clientAuthentication = httpRequest.clientAuthentication().requirePrivateKeyJwt(this.url.toString(), 120)
var clientAuthentication = httpRequest.clientAuthentication()
if (clientAuthentication.method == ClientAuthenticationMethod.PRIVATE_KEY_JWT) {
clientAuthentication = clientAuthentication.requirePrivateKeyJwt(this.url.toString(), 120)
}
val tokenExchangeGrant = TokenExchangeGrant.parse(formParameters.map)

// TODO: add scope if present in request
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,22 +87,66 @@ class TokenExchangeGrantIntegrationTest {
}
}

@Test
fun `token request with token exchange grant and client basic auth should exchange subject_token with a new token containing many of the same claims`() {
withMockOAuth2Server {
val initialSubject = "yolo"
val initialToken = this.issueToken(
issuerId = "idprovider",
clientId = "initialClient",
tokenCallback = DefaultOAuth2TokenCallback(
issuerId = "idprovider",
subject = initialSubject,
claims = mapOf(
"claim1" to "value1",
"claim2" to "value2",
),
),
)

val issuerId = "tokenx"
val tokenEndpointUrl = this.tokenEndpointUrl(issuerId)
val targetAudienceForToken = "targetAudience"

val response: ParsedTokenResponse = client.tokenRequest(
url = tokenEndpointUrl,
basicAuth = Pair("client", "secret"),
parameters = mapOf(
"grant_type" to TOKEN_EXCHANGE.value,
"subject_token_type" to SubjectTokenType.TOKEN_TYPE_JWT,
"subject_token" to initialToken.serialize(),
"audience" to targetAudienceForToken,
),
).toTokenResponse()

response shouldBeValidFor TOKEN_EXCHANGE
response.scope shouldBe null
response.tokenType shouldBe "Bearer"
response.issuedTokenType shouldBe "urn:ietf:params:oauth:token-type:access_token"

response.accessToken!! should verifyWith(issuerId, this)

response.accessToken.subject shouldBe initialSubject
response.accessToken.audience shouldContainExactly listOf(targetAudienceForToken)
response.accessToken.claims["claim1"] shouldBe "value1"
response.accessToken.claims["claim2"] shouldBe "value2"
}
}

@Test
fun `token request without client_assertion should fail`() {
withMockOAuth2Server {
val response: Response =
val response: Response =
client.tokenRequest(
url = this.tokenEndpointUrl("tokenx"),
parameters =
mapOf(
"grant_type" to TOKEN_EXCHANGE.value,
"client_id" to "myid",
"client_secret" to "somesecret",
"subject_token_type" to SubjectTokenType.TOKEN_TYPE_JWT,
"subject_token" to "yolo",
"audience" to "targetAudienceForToken",
),
)
url = this.tokenEndpointUrl("tokenx"),
parameters =
mapOf(
"grant_type" to TOKEN_EXCHANGE.value,
"subject_token_type" to SubjectTokenType.TOKEN_TYPE_JWT,
"subject_token" to "yolo",
"audience" to "targetAudienceForToken",
),
)
response.code shouldBe 400
}
}
Expand Down

0 comments on commit 3019aa3

Please sign in to comment.