In this article, we'll take a look at how to implement security in a Kotlin application using OAuth and JSON Web Tokens (JWT). We'll also explore some of the best practices for doing so.
OAuth is an open standard for authorization that enables third-party applications to access resources without having to store and manage user credentials. It does this by delegating authorization to the resource owner (i.e. the user).
OAuth is commonly used by web applications to allow users to login with their existing credentials from another service, such as Facebook or Google. This is known as "third-party authentication".
JSON Web Token (JWT) is an open standard that defines a compact and self-contained way of transmitting data between parties as a JSON object. This data can be verified and trusted because it is digitally signed.
JWTs are often used in authentication and authorization scenarios, as they can be used to securely transmit information about the user.
Let's take a look at how to implement OAuth in a Kotlin application. We'll be using the ScribeJava library to simplify the process.
First, we need to add the ScribeJava library to our project dependencies:
// build.gradle
repositories {
mavenCentral()
}
dependencies {
implementation("com.github.scribejava:scribejava-core:2.5.3")
}
Next, we need to create a class that represents our OAuth service. This class will be responsible for configuring the OAuth flow and handling the interaction with the OAuth provider.
For this example, we'll be using the Facebook OAuth API:
// FacebookOAuthService.kt
import com.github.scribejava.apis.FacebookApi
import com.github.scribejava.core.builder.ServiceBuilder
import com.github.scribejava.core.model.OAuth2AccessToken
import com.github.scribejava.core.model.OAuthRequest
import com.github.scribejava.core.model.Verb
import com.github.scribejava.core.oauth.OAuth20Service
class FacebookOAuthService(
private val clientId: String,
private val clientSecret: String,
private val callbackUrl: String
) {
private val service: OAuth20Service = ServiceBuilder(clientId)
.apiSecret(clientSecret)
.callback(callbackUrl)
.build(FacebookApi.instance())
fun getAuthorizationUrl(): String {
return service.getAuthorizationUrl()
}
fun getAccessToken(code: String): OAuth2AccessToken {
return service.getAccessToken(code)
}
fun getUserProfile(accessToken: OAuth2AccessToken): String {
val request = OAuthRequest(Verb.GET, "https://graph.facebook.com/me")
service.signRequest(accessToken, request)
return service.execute(request).body
}
}
In the getUserProfile
method, we're using the Facebook Graph API to fetch the current user's profile.
Now that we have our OAuth service class, let's take a look at how to use it in a Kotlin application.
First, we need to create a route that redirects the user to the OAuth provider's login page:
// OAuthController.kt
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.servlet.view.RedirectView
@Controller
@RequestMapping("/oauth")
class OAuthController(
private val facebookOAuthService: FacebookOAuthService
) {
@GetMapping("/login")
fun login(): RedirectView {
val authorizationUrl = facebookOAuthService.getAuthorizationUrl()
return RedirectView(authorizationUrl)
}
}
The login
route redirects the user to the Facebook login page. Once the user logs in, they will be redirected back to the callback
route.
Next, we need to create the callback
route. This route is responsible for fetching the access token from the OAuth provider and then fetching the user's profile:
// OAuthController.kt (continued)
@GetMapping("/callback")
fun callback(@RequestParam("code") code: String): String {
val accessToken = facebookOAuthService.getAccessToken(code)
val userProfile = facebookOAuthService.getUserProfile(accessToken)
// ...
return "redirect:/"
}
Now that we've seen how to implement OAuth, let's take a look at how to implement JWT in Kotlin. We'll be using the Kotlin-JWT library to simplify the process.
First, we need to add the Kotlin-JWT library to our project dependencies:
// build.gradle
repositories {
mavenCentral()
}
dependencies {
implementation("com.auth0:kotlin-jwt:3.3.0")
}
Next, we need to create a class that represents our JWT service. This class will be responsible for creating and verifying JWT tokens.
For this example, we'll be using the HS256 algorithm:
// JWTService.kt
import com.auth0.jwt.JWT
import com.auth0.jwt.algorithms.Algorithm
import com.auth0.jwt.exceptions.JWTVerificationException
import java.util.*
class JWTService(private val secret: String) {
private val algorithm = Algorithm.HMAC256(secret)
fun generateToken(claims: Map<String, Any>): String {
return JWT.create()
.withClaim("iss", "kotlin-jwt-demo")
.withClaim("iat", Date())
.withClaim("exp", Date(Date().time + 60 * 60 * 1000)) // 1 hour
.withClaim("sub", "user@example.com")
.withClaim("aud", "https://example.com")
.withClaim("nbf", Date())
.withClaim("jti", UUID.randomUUID().toString())
.withClaim("foo", "bar")
.withClaim("foo2", "baz")
.withClaim("qaz", "wsx")
.sign(algorithm)
}
fun verifyToken(token: String): Boolean {
return try {
JWT.require(algorithm).build().verify(token)
true
} catch (e: JWTVerificationException) {
false
}
}
}
In the generateToken
method, we're using the withClaim
method to specify the claims that we want to include in the token.
Now that we have our JWT service class, let's take a look at how to use it in a Kotlin application.
First, we need to create a route that generates a JWT token:
// JWTController.kt
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/jwt")
class JWTController(
private val jwtService: JWTService
) {
@GetMapping("/generate")
fun generateToken(): String {
return jwtService.generateToken(emptyMap())
}
}
The generateToken
route generates a JWT token with no claims.
Next, we need to create a route that verifies a JWT token:
// JWTController.kt (continued)
@GetMapping("/verify")
fun verifyToken(@RequestParam("token") token: String): Boolean {
return jwtService.verifyToken(token)
}
The verifyToken
route verifies that the JWT token is valid.
When implementing security in a Kotlin application, there are a few best practices to keep in mind:
Use OAuth for authentication, not authorization. OAuth is an open standard for authorization, not authentication. While it can be used for both purposes, it's best to use it for authorization (i.e. delegating authorization to the resource owner) and use another method, such as JWT, for authentication.
Use JWT for authentication, not authorization. While JWT can be used for both purposes, it's best to use it for authentication (i.e. transmitting information about the user) and use another method, such as OAuth, for authorization.
Don't store sensitive data in JWT claims. JWT claims are public and can be read by anyone. Therefore, it's important not to store sensitive data, such as user passwords, in JWT claims.
Use a secure secret for signing JWT tokens. The secret used to sign JWT tokens should be kept confidential and not be hard-coded in the application. A good way to do this is to use environment variables.
Verify the iss
claim when validating JWT tokens. The iss
(issuer) claim is used to identify the source of the JWT token. When validating a JWT token, it's important to verify that the iss
claim matches the expected value.
Verify the aud
claim when validating JWT tokens. The aud
(audience) claim is used to identify the intended recipient of the JWT token. When validating a JWT token, it's important to verify that the aud
claim matches the expected value.
Verify the exp
claim when validating JWT tokens. The exp
(expiration) claim is used to specify the expiration date of the JWT token. When validating a JWT token, it's important to verify that the current date is before the expiration date.
Verify the nbf
claim when validating JWT tokens. The nbf
(not before) claim is used to specify the date before which the JWT token must not be accepted for processing. When validating a JWT token, it's important to verify that the current date is after the not before date.
Verify the jti
claim when validating JWT tokens. The jti
(JWT ID) claim is used to provide a unique identifier for the JWT token. When validating a JWT token, it's important to verify that the jti
claim is present and that it's value is unique.
Don't use basic authentication. While basic authentication is simple to implement, it's not very secure. It's best to use a more secure method, such as OAuth or JWT.
Don't use the same secret for signing JWT tokens and encrypting data. While it's convenient to use the same secret for both purposes, it's not very secure. It's best to use a different secret for each purpose.
Rotate secrets regularly. Secrets should be rotated regularly to reduce the risk of them being compromised. A good rule of thumb is to rotate secrets every 90 days.
In this article, we've seen how to implement security in a Kotlin application using OAuth and JWT. We've also seen some of the best practices for doing so.