JWT authentication + mongodb with Ktor
Tags: kotlin, ktor, mongodb
Published: 2021-07-31 17:30:06

In this blog we will be building a simple Authentication API with Ktor and then connecting it to a MongoDB database. This blog is aimed at developers with some experience with kotlin and Ktor.

**Introduction to Ktor**
First of all, what is Ktor? 
Well in simple terms, Ktor is a framework used to build web applications, HTTP services, mobile and browser applications. But due to the scope of this lesson, we will be focusing on HTTP services. For a more detailed introduction to Ktor, please visit:[Link](https://ktor.io/docs/welcome.html)

**Getting started with ktor**
To get Ktor on your machine, I recommend the official guide [Link](https://ktor.io/docs/intellij-idea.html). Note, if you are using the IntelliJ IDEA Community Edition, navigate down to the *Create a new ktor project* heading and click on *Ktor Project Generator*. For this lesson, we will be requiring these features: 
**Security**
*Authentication JWT (JSON Web Token) &
*Sessions (Sessions holds the state of the current user)
**Routing**
*Locations &
*Routing(Locations and Routing helps us to utilize the API routes)
**HTTP**
*ContentNegotiation
**Serialization**
*GSON(Used to serialize and deserialize Java objects)

**Authenticating with ktor**
Ktor makes it very easy to create an authentication layer. As we all know, security is a vital ingredient in most applications, as it helps in  the effective  use of the application. There are various means of authenticating our apps but we will be using JWT today. For more info about JWT visit [Link](https://jwt.io/).
To utilize JWT, first create a JWTConfig.kt class and add the following code:
```
class JwtService {

    private const val secret = "cXRIJ57575KKFDJFcmcm"
    private const val issuer = "ktor.io"
    private val algorithm = Algorithm.HMAC512(secret)

    val verifier: JWTVerifier = JWT
        .require(algorithm)
        .withIssuer(issuer)
        .build()

    fun generateToken(userId: String): String = JWT.create()
        .withSubject("Authentication")
        .withIssuer(issuer)
        .withClaim("userId", userId)
        .withExpiresAt(expiresAt())
        .sign(algorithm)

    private fun expiresAt() =
        Date(System.currentTimeMillis() + 3_600_000 * 24) // 24 hours
}
```
*The verifier value checks whether a token is correct 
*The generateToken method generates the token that will be returned to the user

We now call the class in our Application.kt file and add these lines of code
```
val jwtService = JwtService()

fun Application.module(testing: Boolean = false) {

    install(Authentication) {
        jwt{
            verifier(jwtService.verifier)
            realm = JWT_REALM
           validate {
        val userId = it.payload.getClaim(CLAIM_USERID).asInt()
     val user = mongoDataHandler.finduser(userId) // 4
                PrincipalUser(user?.userId!!.toString())
            
               
            
               }
        }
    }
data class PrincipalUser(val userId: String): Principal
```
*The validate method returns a validated user.
For a more detailed explanation on JWT authentication, I recommend this Youtube tutorial by Caelis : [Link](https://www.youtube.com/watch?v=YvRts-G-1XY&t=1s)

**Integrating with a Database**
To start, we need Docker. What is Docker? In simple terms, Docker is a container system that allows you package your software in a format that can then be run on any platform that supports Docker. For more information on docker, I recommend this 1 hour tutorial by Programming with Mosh [Link](https://www.youtube.com/watch?v=pTFZFxd4hOI&t=1313s)

Getting started with Docker
To get Docker on your machine I recommend the official guide :[Link](https://docs.docker.com/get-docker/). After the installation, head on to Intellij and create a *docker-compose.yml* file(Docker compose file) and add the following code:
```
version: '3'
services:
  mymongo:
    image: mongo:latest
    ports:
      - 27017:27017
```
Next add this dependency 
```
implementation "org.mongodb:mongodb-driver:3.12.9"
```
What is a Docker compose file? As stated in the Docker docs [Link](https://docs.docker.com/compose/);
*When you’re developing software, the ability to run an application in an isolated environment and interact with it is crucial. The Compose command line tool can be used to create the environment and interact with it.
The Compose file provides a way to document and configure all of the application’s service dependencies (databases, queues, caches, web service APIs, etc). Using the Compose command line tool you can create and start one or more containers for each dependency with a single command (docker-compose up).
Together, these features provide a convenient way for developers to get started on a project. Compose can reduce a multi-page “developer getting started guide” to a single machine readable Compose file and a few commands.
The code adds and downloads an image of the MongoDB and exposes its ports.*
 
Next create a MongoDBdata.kt class and add the following code
```
class MongoDBdata{

    val database: MongoDatabase
    val userCollection: MongoCollection<User>
    init{
        val pojoCodecRegistry: CodecRegistry = fromProviders(PojoCodecProvider.builder().automatic(true).build())
        val codecRegistry: CodecRegistry = fromRegistries(
            MongoClientSettings.getDefaultCodecRegistry(),
            pojoCodecRegistry)

        val clientSettings = MongoClientSettings.builder()
            .codecRegistry(codecRegistry)
            .build()

 val mongoClient = MongoClients.create(clientSettings)
 database = mongoClient.getDatabase("mydatabase");
userCollection = database.getCollection(User::class.java.name, User::class.java)
userCollection.insertOne(User(userId = null, email= "good", userName = "username", passwordHash = "password"))
    }

    fun adduser(email: String, username: String, password: String): User? {
        userCollection.insertOne(User(userId = null, email = email, userName = username, passwordHash = password))
 return userCollection.find(Filters.eq("email", email)).first()

    }
    fun finduser(id: Int): User?{
       return userCollection.find(Filters.eq("_id", id)).first()

    }
     fun finduserByEmail(email: String): User?{
     return userCollection.find(Filters.eq("email",email)).first()

    }
}

class User(userId: ObjectId?,
           email: String= "email",
           userName: String = "username",
           passwordHash: String= "password"): Serializable{
    @BsonId
    var userId: ObjectId?
    var email: String
    var userName: String
    var passwordHash: String

    constructor() : this(null, "void", "void"){}
    init{
        this.userId = userId
        this.email = email
        this.userName = userName
        this.passwordHash = passwordHash
    }
}

```
In this code, a mongoClient is created with the clientSetting, we then go on to create a database named *mydatabase* with the mongoClient and finally we create a userCollection passing in a User class. 
The user class has an *@Bson* annotation which tells the database to utilize that parameter as the ID. We initialized a fake user to populate the database on creation. 
The *adduser* method adds a user to the database and returns the user that was added. 
The *finduserByEmail* method finds and returns  an instance of the user using an email address. 
The *finduser* method finds a user by id and returns  an instance of the user.
**Using Authenticated routes**
First, we create a kotlin file called Routes. Here we create an extension function and utilize the Location feature. We add the following code 
```
const val USERLOGIN = "/login"
const val USERCREATE = "/create"
val mongoDBdata= MongoDBdata()

@Location(USERCREATE)
class Register{
}
@Location(USERLOGIN)
class Login{
}
fun Routing.userRoutes(){

post<Register>{
    val signupParameters = call.receiveParameters()
    val password = signupParameters["password"]
        ?: return@post call.respond(
            HttpStatusCode.Unauthorized, "Missing Fields")
    val userName = signupParameters["userName"]
        ?: return@post call.respond(
            HttpStatusCode.Unauthorized, "Missing Fields")
    val email = signupParameters["email"]
        ?: return@post call.respond(
            HttpStatusCode.Unauthorized, "Missing Fields")

    try {
val user = mongoDBdata.adduser(email, userName, password)
        user?.userId?.let{
            call.respondText(
                jwtService.generateToken(user.userId!!.toString()),
                status = HttpStatusCode.Created
            )
        }
        }catch (e: Throwable) {
        application.log.error("Failed to register user", e)
        call.respond(HttpStatusCode.BadRequest, "Problems creating User")
    }
    }

    post<Login> {
        val signinParameters = call.receive<Parameters>()
        val password = signinParameters["password"]
            ?: return@post call.respond(
                HttpStatusCode.Unauthorized, "Missing Fields")
        val email = signinParameters["email"]
            ?: return@post call.respond(
                HttpStatusCode.Unauthorized, "Missing Fields")
       
        try {
            val presentUser = mongoDBdata.finduserByEmail(email) 
            presentUser?.userId?.let {  call.respondText(jwtService.generateToken(presentUser.userId!!.toString()))
                if (currentUser.passwordHash == hash) { 
                    call.sessions.set(MySession(it.toString()))
                    call.respondText(jwtService.generateToken(currentUser.userId!!.toString()))
                } else {
                    call.respond(
                        HttpStatusCode.BadRequest, "Problems retrieving User") 
                }
            }
        } catch (e: Throwable) {
            application.log.error("Failed to register user", e)
            call.respond(HttpStatusCode.BadRequest, "Problems retrieving User")
        }
    }
}
data class MySession(val userId: String)
```
The Register route receives the parameter from the post request and calls the *adduser* function defined earlier to create a user and return the user created. We then extract the *userId* from the returned instance and utilize it in the *generateToken* method.
 
The Login route calls the *finduserbyEmail* function using the email received from the request and returns a token if the password matches the one entered by the current user.

Call userRoutes() in Application.kt and wrap the authenticate function around any route that needs authentication
```
fun Application.module(testing: Boolean = false) {
routing {

        userRoutes()
        authenticate {
            get("/"){
                call.respondText("HELLO WORLD!", contentType = ContentType.Text.Plain)
            }
        }
}
}
```
To start it up, open the *docker-compose.yml* in the terminal and run “docker-compose up -d”.  You just successfully ran the compose file.

Next, run your server and give it a while to startup. Go back to the terminal and type enter in *docker exec -it scripts_mymongo_1 sh*(scripts_mymongo_1 represents the container name). To check for the name of your container enter “docker ps”. 
Now enter in *mongo*. You are now in the mongo shell, ready to interact with mongoDB.
 
To show the available database enter *show db*. 
To use a database enter *use mydatabase*(development represents the database name). 
To show collections enter *show collections*. 
To see contents of a collection enter *db.”collection name.find()*, you should see a single fake user detail.
 
Thank you for sticking this long, hope you learned a thing or two. See you in the next one.