Skip to content

Commit 9ccd524

Browse files
committed
Support implicit flow
1 parent 6eb770e commit 9ccd524

File tree

6 files changed

+166
-4
lines changed

6 files changed

+166
-4
lines changed

kotlin-oauth2-server-core/src/main/java/nl/myndocs/oauth2/TokenService.kt

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,10 +184,58 @@ class TokenService(
184184

185185
tokenStore.storeCodeToken(codeToken)
186186

187-
188187
return codeToken
189188
}
190189

190+
fun redirect(redirect: RedirectTokenRequest): AccessToken {
191+
if (redirect.clientId == null) {
192+
throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("client_id"))
193+
}
194+
195+
if (redirect.username == null) {
196+
throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("username"))
197+
}
198+
199+
if (redirect.password == null) {
200+
throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("password"))
201+
}
202+
if (redirect.redirectUri == null) {
203+
throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("redirect_uri"))
204+
}
205+
206+
val clientOf = clientService.clientOf(redirect.clientId) ?: throw InvalidClientException()
207+
208+
if (!clientOf.redirectUris.contains(redirect.redirectUri)) {
209+
throw InvalidGrantException("invalid 'redirect_uri'")
210+
}
211+
212+
val identityOf = identityService.identityOf(clientOf, redirect.username) ?: throw InvalidIdentityException()
213+
214+
var validIdentity = identityService.validCredentials(clientOf, identityOf, redirect.password)
215+
216+
if (!validIdentity) {
217+
throw InvalidIdentityException()
218+
}
219+
220+
val requestedScopes = ScopeParser.parseScopes(redirect.scope)
221+
222+
val scopesAllowed = scopesAllowed(clientOf.clientScopes, requestedScopes)
223+
if (!scopesAllowed) {
224+
throw InvalidScopeException(requestedScopes.minus(clientOf.clientScopes))
225+
}
226+
227+
val accessToken = accessTokenConverter.convertToToken(
228+
identityOf.username,
229+
clientOf.clientId,
230+
requestedScopes,
231+
null
232+
)
233+
234+
tokenStore.storeAccessToken(accessToken)
235+
236+
return accessToken
237+
}
238+
191239
private fun throwExceptionIfUnverifiedClient(clientRequest: ClientRequest) {
192240
if (clientRequest.clientId == null) {
193241
throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("client_id"))
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package nl.myndocs.oauth2.request
2+
3+
class RedirectTokenRequest(
4+
val clientId: String?,
5+
val redirectUri: String?,
6+
val username: String?,
7+
val password: String?,
8+
val scope: String?
9+
)

kotlin-oauth2-server-ktor/src/main/java/nl/myndocs/oauth2/ktor/feature/Oauth2ServerFeature.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import io.ktor.util.AttributeKey
66
import nl.myndocs.oauth2.TokenService
77
import nl.myndocs.oauth2.client.ClientService
88
import nl.myndocs.oauth2.identity.IdentityService
9-
import nl.myndocs.oauth2.ktor.feature.routing.authorize.configureAuthorizationCodeGranting
9+
import nl.myndocs.oauth2.ktor.feature.routing.authorize.configureAuthorizeEndpoint
1010
import nl.myndocs.oauth2.ktor.feature.routing.token.configureTokenEndpoint
1111
import nl.myndocs.oauth2.token.TokenStore
1212
import nl.myndocs.oauth2.token.converter.*
@@ -51,7 +51,7 @@ class Oauth2ServerFeature(configuration: Configuration) {
5151

5252
pipeline.intercept(ApplicationCallPipeline.Infrastructure) {
5353
configureTokenEndpoint(feature)
54-
configureAuthorizationCodeGranting(feature)
54+
configureAuthorizeEndpoint(feature)
5555
}
5656

5757
return feature
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package nl.myndocs.oauth2.ktor.feature.routing.authorize
2+
3+
import io.ktor.application.ApplicationCall
4+
import io.ktor.application.call
5+
import io.ktor.http.HttpMethod
6+
import io.ktor.http.HttpStatusCode
7+
import io.ktor.pipeline.PipelineContext
8+
import io.ktor.request.httpMethod
9+
import io.ktor.request.path
10+
import io.ktor.response.respond
11+
import io.ktor.response.respondText
12+
import nl.myndocs.oauth2.exception.OauthException
13+
import nl.myndocs.oauth2.ktor.feature.Oauth2ServerFeature
14+
import nl.myndocs.oauth2.ktor.feature.util.toJson
15+
16+
suspend fun PipelineContext<Unit, ApplicationCall>.configureAuthorizeEndpoint(feature: Oauth2ServerFeature) {
17+
try {
18+
if (call.request.httpMethod != HttpMethod.Get) {
19+
proceed()
20+
return
21+
}
22+
23+
val requestPath = call.request.path()
24+
if (requestPath != feature.authorizeEndpoint) {
25+
proceed()
26+
return
27+
}
28+
29+
val allowedResponseTypes = setOf("code", "token")
30+
val responseType = call.request.queryParameters["response_type"]
31+
32+
if (responseType == null) {
33+
call.respond(HttpStatusCode.BadRequest, "'response_type' not given")
34+
finish()
35+
return
36+
}
37+
38+
if (!allowedResponseTypes.contains(responseType)) {
39+
call.respond(HttpStatusCode.BadRequest, "'response_type' with value '$responseType' not allowed")
40+
finish()
41+
return
42+
}
43+
44+
try {
45+
when (responseType) {
46+
"code" -> configureAuthorizationCodeGranting(feature)
47+
"token" -> configureImplicitTokenGranting(feature)
48+
}
49+
} catch (oauthException: OauthException) {
50+
call.respondText(text = oauthException.toJson(), status = HttpStatusCode.BadRequest)
51+
finish()
52+
return
53+
}
54+
55+
} catch (t: Throwable) {
56+
t.printStackTrace()
57+
}
58+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package nl.myndocs.oauth2.ktor.feature.routing.authorize
2+
3+
import io.ktor.application.ApplicationCall
4+
import io.ktor.application.call
5+
import io.ktor.http.HttpStatusCode
6+
import io.ktor.pipeline.PipelineContext
7+
import io.ktor.request.header
8+
import io.ktor.response.header
9+
import io.ktor.response.respond
10+
import io.ktor.response.respondRedirect
11+
import nl.myndocs.oauth2.exception.InvalidIdentityException
12+
import nl.myndocs.oauth2.ktor.feature.Oauth2ServerFeature
13+
import nl.myndocs.oauth2.ktor.feature.util.BasicAuth
14+
import nl.myndocs.oauth2.request.RedirectTokenRequest
15+
16+
suspend fun PipelineContext<Unit, ApplicationCall>.configureImplicitTokenGranting(feature: Oauth2ServerFeature) {
17+
val queryParameters = call.request.queryParameters
18+
19+
val authorizationHeader = call.request.header("authorization") ?: ""
20+
val credentials = BasicAuth.parse(authorizationHeader)
21+
22+
try {
23+
val redirect = feature.tokenService.redirect(
24+
RedirectTokenRequest(
25+
queryParameters["client_id"],
26+
queryParameters["redirect_uri"],
27+
credentials.username ?: "",
28+
credentials.password ?: "",
29+
queryParameters["scope"]
30+
)
31+
)
32+
33+
34+
call.respondRedirect(
35+
queryParameters["redirect_uri"] + "#access_token=${redirect.accessToken}" +
36+
"&token_type=bearer&expires_in=${redirect.expiresIn()}"
37+
)
38+
39+
finish()
40+
return
41+
} catch (unverifiedIdentityException: InvalidIdentityException) {
42+
call.response.header("WWW-Authenticate", "Basic realm=\"${queryParameters["client_id"]}\"")
43+
call.respond(HttpStatusCode.Unauthorized)
44+
finish()
45+
return
46+
}
47+
}

kotlin-oauth2-server-ktor/src/main/java/nl/myndocs/oauth2/ktor/feature/routing/token/TokenEndpointRouting.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ suspend fun PipelineContext<Unit, ApplicationCall>.configureTokenEndpoint(featur
2727
}
2828
val formParams = call.receiveParameters()
2929

30-
val allowedGrantTypes = setOf("password", "implicit", "authorization_code", "refresh_token")
30+
val allowedGrantTypes = setOf("password", "authorization_code", "refresh_token")
3131
val grantType = formParams["grant_type"]
3232
if (grantType == null) {
3333
call.respond(HttpStatusCode.BadRequest, "'grant_type' not given")

0 commit comments

Comments
 (0)