CORS inspector
|

Conquer Authentication with Ktor: Part 8 – Protect Access with CORS

Ensuring security and flexibility of web services when it comes to cross-origin resource sharing is essential. This is elegantly managed through the implementation of Cross-Origin Resource Sharing (CORS), an established practice for modern web applications. A well defined CORS policy not only enhances security but also promotes a seamless interaction between different domains. Thankfully, Ktor makes this process straightforward and efficient. In this final part of our series on authentication with Ktor, we will provide clear examples to guide you. By the end of this post, you’ll see how effortless it is to integrate CORS into your Ktor projects, ensuring your services are both secure and accessible.

This post is part of a hands-on tutorial. Feel free to check out the project from GitHub and follow along.

Table of Contents

Installing Dependencies

CORS ships as a plugin. Please add the following dependency to use it.

implementation("io.ktor:ktor-server-cors:$ktor_version")

As of time of this writing the project documentation refers to an older version of the plugin. Please note that io.ktor.server.plugins.cors.CORS has been deprecated. The plugin has moved to a different package. This is what you need to import in your project:

import io.ktor.server.plugins.cors.routing.CORS

Configuring CORS in Ktor

You can configure CORS in Ktor by using the install(CORS) feature. You can use this example as a referernce:


import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.plugins.cors.routing.CORS

fun Application.module() {
    install(CORS) {
       allowMethod(HttpMethod.Options)
       allowMethod(HttpMethod.Put)
       allowMethod(HttpMethod.Delete)
       allowMethod(HttpMethod.Patch)
       allowHeader(HttpHeaders.Authorization)
       allowCredentials = true
       allowNonSimpleContentTypes = true
       allowSameOrigin = true
       anyHost()  // Adjust this to suit your needs
    }
}

This example

  • enables selected HTTP methods
  • allows the Authorization header
  • allows credentials. Browsers don’t send credentials or authentication cookies by default. Allowing credentials sets the Access-Control-Allow-Credentials response header to true.
  • allows non-simple content types. Without this setting our server won’t accept requests with JSON payload, for example. Only simple types (text/plain, application/x-www-form-urlencoded and multipart/form-data) are allowed by default.
  • allows all hosts. Please make sure to adjust this to suit your needs.

The anyHost() function is too permissive and it’s wise to replace it with the specific origin or the list of origins you want to allow. If you need to allow specific hosts only, please replace anyHost() with allowHost() configuration.

install(CORS) {
    allowHost("www.example.com", schemes = listOf("http", "https"))
    // Other configuration options
}

When using the install(CORS) feature, Ktor automatically handles preflight OPTIONS requests behind the scenes for you. Be sure to enable all the methods, headers, or any other configurations you wish to support in these preflight requests.

What is OPTIONS Request?

In short, it’s a preflight request sent with the OPTIONS method so that the server can respond if it is acceptable to send the actual request.

The HTTP OPTIONS method is used by clients to find out what HTTP methods and other communication options are available for a specific URL endpoint on your server. When a client sends an OPTIONS request, the server responds with information about the communication options available for the resource in question, such as supported HTTP methods (GET, POST, PUT, DELETE etc.) and CORS policies.

In the context of CORS, an OPTIONS request serves as a preflight request that is sent before the actual request to determine if it makes sense to send the actual request. During this preflight, the browser can ask what methods are allowed when accessing a resource, or what non-simple content types can be used.

CORS is Not a Silver Bullet

Please note that CORS won’t prevent your server from receiving requests from unknown or malicious origins. It only advises the browser to either pass or drop the response to a request. Therefore, you should add other security measures, like CSRF tokens and checking the origin of non-idempotent requests.

Non-idempotent HTTP requests are those where making the same request multiple times can lead to different results or side effects. Simply put, repeated POST requests typically create multiple resources in your system. Similarly, calling a PATCH endpoint with different parameters modifies the underlying resources and changes its state every time. Whether the browser prevents or allows a response to such requests is irrelevant as the potential harm happens when such a request is processed by our server.

Testing CORS with Ktor

Can I verify that CORS policy works with unit tests? Absolutely! You can create unit tests in Ktor to verify that your setup is working as expected.

Here is an example:

import io.ktor.client.request.*
import io.ktor.http.*
import io.ktor.http.HttpHeaders.AccessControlRequestMethod
import io.ktor.server.testing.*
import org.junit.Test
import kotlin.test.assertEquals

class CorsTest {

    @Test
    fun `test CORS options`() = testApplication {
        application {
            // Reuse the same CORS configuration 
            // as in the main Application module
        }
        client.options("/") {
            header(HttpHeaders.Origin, "https://www.example.com")
            header(AccessControlRequestMethod, "GET")
        }.apply {
            assertEquals(HttpStatusCode.OK, status)
            assertEquals(
                "https://www.example.com",
                headers[HttpHeaders.AccessControlAllowOrigin]
            )
            assertEquals(
                "true",
                headers[HttpHeaders.AccessControlAllowCredentials]
            )
        }
    }
}

In this test, an OPTIONS request is sent along with an Origin and Access-Control-Request-Method header. The test checks that:

  • the request is successful (status 200 OK)
  • the Access-Control-Allow-Origin response header is correctly set, allowing requests from https://www.example.com
  • the Access-Control-Allow-Credentials response header is set to true. Meaning that cookies, HTTP authentication, and client-side SSL certificates are supported.

You could design similar tests to ensure that requests from unlisted origins are properly blocked, or that requests with unsupported methods are disallowed.

Remember to verify not only your CORS configuration but also the overall functioning and security of your application.

Summary

We’ve explored how CORS works and its role in modern web development. We’ve seen how simple it is to implement and test CORS within the Ktor framework. While relying solely on CORS does not guarantee full protection against various cyber attacks, ensuring the principles of CORS are applied within our projects is a step we must all take to safeguard our resources. As usual, the code examples are available on GitHub. Thanks for reading, hope you this post helped you get inspired to find out more about common security practices and how to achieve them with Ktor.

Similar Posts