Example Token Manager
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package me.amryousef.auth | |
import com.auth0.jwt.JWT | |
import com.auth0.jwt.JWTVerifier | |
import com.auth0.jwt.algorithms.Algorithm | |
import com.auth0.jwt.interfaces.Payload | |
import io.ktor.auth.Principal | |
import me.amryousef.model.TokenType | |
import me.amryousef.model.User | |
import me.amryousef.usecase.GetUserByIdUseCase | |
import java.util.* | |
import java.util.concurrent.TimeUnit | |
class JwtTokenManager(private val getUserByIdUseCase: GetUserByIdUseCase) { | |
private companion object { | |
private const val secret = "super-secret-stuff" | |
private const val issuer = "amryousef" | |
private val refreshTokenValidityInMs = TimeUnit.DAYS.toMillis(7) | |
private val accessTokenValidityInMs = TimeUnit.HOURS.toMillis(8) | |
private val algorithm = Algorithm.HMAC512(secret) | |
private const val ID_CLAIM_KEY = "id" | |
private const val ROLE_CLAIM_KEY = "role" | |
private const val AUTHENTICATION_SUBJECT = "Authentication" | |
private const val REFRESH_SUBJECT = "Refresh" | |
} | |
val verifier: JWTVerifier = JWT.require(algorithm).withIssuer(issuer).build() | |
fun createToken(user: User) = | |
TokenResult( | |
accessToken = JWT.create() | |
.withSubject(AUTHENTICATION_SUBJECT) | |
.withIssuer(issuer) | |
.withClaim(ID_CLAIM_KEY, user.id) | |
.withClaim(ROLE_CLAIM_KEY, user.roles.map { it::class.java.simpleName }.joinToString(", ")) | |
.withExpiresAt(getExpiration(accessTokenValidityInMs)) | |
.sign(algorithm), | |
refreshToken = JWT.create() | |
.withSubject(REFRESH_SUBJECT) | |
.withIssuer(issuer) | |
.withClaim(ID_CLAIM_KEY, user.id) | |
.withExpiresAt(getExpiration(refreshTokenValidityInMs)) | |
.sign(algorithm) | |
) | |
fun isValidToken(token: Payload): TokenValidity { | |
if (token.expiresAt.before(Date())) { | |
return TokenValidity.NotValid | |
} | |
return token.claims["id"]?.asInt()?.let { | |
TokenValidity.Valid( | |
getUserByIdUseCase.execute(it), | |
token.subject.toTokenType() | |
) | |
} ?: TokenValidity.NotValid | |
} | |
@Throws(IllegalArgumentException::class) | |
private fun String.toTokenType() = when (this) { | |
AUTHENTICATION_SUBJECT -> TokenType.ACCESS | |
REFRESH_SUBJECT -> TokenType.REFRESH | |
else -> throw IllegalArgumentException() | |
} | |
private fun getExpiration(validityInMs: Long) = Date(System.currentTimeMillis() + validityInMs) | |
data class TokenResult(val accessToken: String, val refreshToken: String) | |
sealed class TokenValidity { | |
data class Valid( | |
val user: User, | |
val tokenType: TokenType | |
) : TokenValidity(), Principal | |
object NotValid : TokenValidity() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment