Skip to main content

Kotlin + Spring + Vue: Make Authentication More Secure With Cookies

Let's improve our authentication and store JWT token in a more secure place than Local Storage.

First of all, I strongly recommend reading this article - Please Stop Using Local Storage.

Now we are going to store the JWT token in httpOnly cookies. That means that JWT token will not be accessible from JavaScript anymore. Since we intend to completely abandon the use of Local Storage...

#Step 1

Remove all the logic related to using Local Storage in the frontend subproject. Please, be careful with store/index.js - if your login and logout operations don't run clearly, you will get annoying bugs. We have to use a new way for identifying if user is authenticated - for example, if role stored in Local Storage is defined (that's not sensitive data, so we can still store it in Local Storage) or any another way.

#Step 2

Return JWT as cookie from your backend, not in the body of response:

IMPORTANT: Please, pay attention, that I placed parameters authCookieName and isCookieSecure to application.properties - sending secure cookie is impossible via http (not https), so it can't be debugged on localhost. BUT it's highly recommended in production.

And use the new response entity which doesn't include a special field for JWT.

#Step 3

Update JwtAuthTokenFilter: previously we extracted token from header, but now we take it from cookie:

@Value("\${ksvg.app.authCookieName}")
          lateinit var authCookieName: String

...

private fun getJwt(request: HttpServletRequest): String? {
     for (cookie in request.cookies) {
          if (cookie.name == authCookieName) {
               return cookie.value
          }
     }
     return null
}


#Step 4

Actually, this step is optional. However, it's strange that we protect token storing but still didn't enable CORS policy. Let's fix it by implementing global CORS policy. Update WebSecurityConfig.kt:

@Bean
fun corsConfigurationSource(): CorsConfigurationSource {
     val configuration = CorsConfiguration()
     configuration.allowedOrigins = Arrays.asList("http://localhost:8080", "http://localhost:8081", "https://kotlin-spring-vue-gradle-demo.herokuapp.com")
     configuration.allowedHeaders = Arrays.asList("*")
     configuration.allowedMethods = Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS")
     configuration.allowCredentials = true
     configuration.maxAge = 3600
     val source = UrlBasedCorsConfigurationSource()
     source.registerCorsConfiguration("/**", configuration)
     return source
}

@Throws(Exception::class)
override fun configure(http: HttpSecurity) {
     http
          .cors().and()
          .csrf().disable().authorizeRequests()
          .antMatchers("/**").permitAll()
          .anyRequest().authenticated()
          .and()
          .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
          .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)

     http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter::class.java)
     http.headers().cacheControl().disable()
}

And now we can remove local CORS annotations in the controller classes (@CrossOrigin(origins =...).


IMPORTANT: AllowCredentials parameter is required for sending cookie value in requests from frontend. Read more about that here.


#Step 5

Actualize frontend headers in http-commons.js:

export const AXIOS = axios.create({
     baseURL: `/api`,
     headers: {
          'Access-Control-Allow-Origin': ['http://localhost:8080', 'http://localhost:8081', 'https://kotlin-spring-vue-gradle-demo.herokuapp.com'],
          'Access-Control-Allow-Methods': 'GET,POST,DELETE,PUT,OPTIONS',
          'Access-Control-Allow-Headers': '*',
          'Access-Control-Allow-Credentials': true
     }
})


Check

Now let's try to access our application from non-allowed host. Run backend on port 8080, frontend - on port 8082 and try to sign in:
Sign in request is blocked by CORS policy.

And now let's look at how httpOnly cookies actually work. Go to https://kotlinlang.org/, for example, open development console and execute short JavaScript code:

document.cookie



You will see all non-httpOnly cookies related to this website. They are accessible from JavaScript as you see.
And now let's authorize (this action guarantees that cookie was received) in our application and do the same:

Ways to Improve

  • Use Double Submit Cookies: httpOnly cookies are still not protected against advanced XSS attack. Moreover, that is more applicable solution for application scaling.
  • Make JWT cookie SameSite=strict using any other library or implement it manually. However not all browsers support this feature nowadays.

Links

Comments

Popular posts from this blog

Make Authentication More Secure With Cookies Email Registration Confirmation That's all for this year. See you in 2020!

Kotlin + Spring Boot + Vue.js: + Gradle

Hello, dear visitors! I added two new articles to the Fullstack section: Kotlin + Spring + Vue: Migration from Maven to Gradle Kotlin + Spring + Vue: Deploy to Heroku with Gradle I'll be glad if you will find then useful for yourself.

Kotlin + Spring Boot + Vue.js

Hello there! I've published my first set of articles about developing web applications using Kotlin , Spring Boot and Vue.js : Koltin + Spring Boot + Vue.js Here are the contents of this set: Introduction Start CI/CD REST API Database Connection Authentication Spam Protection (reCAPTCHA) Email