During the Woowa Tech Course, I successfully implemented Kakao social login in the Android project Chongdae Market~~ 
However, the login part wasn't completely finished yet. The next issue was regarding the Access Token and Refresh Token.. 
The backend team requested that when the client sends the Access Token and Refresh Token to the server, it should be sent in cookies. But.. what exactly is a cookie?
In this post, I will explain what cookies are and how Chongdae Market utilized them!
1. What is Cookie?
The backend requested the following from Android:
1.
Upon successful login to Chongdae Market, the server sends the Access Token and Refresh Token to the client (Android). These tokens should be sent back encapsulated in a cookie.
2.
For every subsequent server API call, the Access Token must be sent back to the server in the cookie. If the Access Token is invalid or expired, it should be reissued, and the server will return an HTTP status code 401 to inform the client.
3.
If the client receives a 401, it should send the Refresh Token back to the server to obtain a new Access Token. If the Refresh Token is also invalid or expired, the client must re-login, and the server will respond with an HTTP status code 403.
4.
If the client receives a 403, it should navigate to the login screen and prompt the user to re-login. Upon re-login, a new Access Token and Refresh Token will be issued, which the client can then use to communicate with the server.
As someone unfamiliar with the concept of cookies, I initially struggled to understand what it meant to send tokens encapsulated in a cookie. Therefore, the first thing I needed to do was to understand what a cookie is.
An HTTP cookie is a small piece of data that is stored on the user's computer by the web server and has a name.
Since this was my first experience with cookies, I decided not to delve too deeply. I understood cookies to be small pieces of data sent from the server to the client, which the client stores and sends back every time it communicates with the server. This level of understanding was sufficient for using cookies in our Chongdae Market project.
In Chongdae Market, we will store the small string data of the Access Token and Refresh Token in cookies. When the client logs in to the server, it will receive these cookies and store them locally, sending them back during subsequent API communications. With this understanding, I have passed the first hurdle! 
2. How to Receive and Send Cookies?
Now it was time to think about how to handle and manage cookies in our project. I knew that in Android, Access Tokens are typically sent and received using headers and interceptors. However, I was not familiar with using headers and interceptors, and I had questions about whether cookies could be handled in the same way. From here on, it was time for some Googling! I diligently searched for how to store and manage cookies when using Retrofit in Android.
Found a great article!
I was able to quickly find a good article, indicating that others had similar concerns. The article explained that while using interceptors is a common method, it has the downside of requiring a lot of code. However, using CookieJar can help alleviate this issue!! 
3. Let's Learn About CookieJar
CookieJar? Does it mean a jar for cookies? When I first heard the name, I guessed it was something like a container for cookies.
CookieJar is a feature provided by OkHttp that helps clients easily receive and send cookies!! 
CookieJar Interface Overview
The CookieJar interface is declared in the OkHttp library. When we Android developers implement this interface, OkHttp will automatically call the methods of our implementation every time an API communication occurs.
This interface includes two methods: saveFromResponse() and loadForRequest(). Letโs take a look at how these two methods work, one by one.
saveFromResponse() : The saveFromResponse() method is responsible for saving cookies received from an HTTP response into a storage system. As explained further below, the storage where cookies will be saved needs to be implemented by the developer.
This function takes two parameters: url and cookies. When you trace the call location of this function, you can see the internal workings of the CookieJar. It becomes clear that the interceptor captures the response and retrieves cookies from the headers. If the response includes a Set-Cookie header, it fetches those cookies. If you're curious, you can dive in and check it out yourself!
When OkHttp calls this function, it automatically passes the API's URL and the cookies to the two parameters. All you need to do is override the function, and OkHttp takes care of the rest, making it super convenient! 
loadForRequest(): The loadForRequest() method is responsible for returning the cookies to be used in an HTTP request before the request is sent.
If saveFromResponse() is the function for receiving cookies, then loadForRequest() can be seen as the function for sending cookies. Developers need to define the return value according to the return type of List<Cookie>. When OkHttp makes an API request, it calls this function to obtain the List<Cookie>, which is then added to the HTTP request headers and sent to the server.
Here's an example of how you might implement a CookieJar:
class TokensCookieJar : CookieJar {
private val cookies: MutableMap<String, List<Cookie>> = mutableMapOf()
override fun loadForRequest(url: HttpUrl): List<Cookie> {
return cookies[url.host] ?: emptyList()
}
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
this.cookies[url.host] = cookies
}
}
Kotlin
๋ณต์ฌ
You can implement the CookieJar using a MutableMap to store List<Cookie> for each URL. When the server sends cookies to the client, OkHttp will call saveFromResponse() to save the cookies in the cookies map. During an HTTP request, OkHttp will call loadForRequest() to retrieve the stored cookies and add them to the request headers before sending them to the server.
Hereโs how you can create an instance of the CookieJar and use it with OkHttp:
You simply call the cookieJar() function of the OkHttpClient.Builder and pass in an instance of your implemented CookieJar. This way, the CookieJar will automatically handle cookies for you going forward.
4. How do I manage cookies permanently?
However, this is not the end. The method we implemented above only stores the cookies in a MutableMap, meaning they are stored in memory. This implies that once the app's process terminates, these cookies will disappear. 
The official library documentation explains this as follows:
To be honest, I don't fully understand all the explanations, but there is one part that stands out and deserves attention.
As persistence, implementations of this interface must also provide storage of cookies. Simple implementations may store cookies in memory; sophisticated ones may use the file system or database to hold accepted cookies.
I would like to explain in detail about the "storage for cookies" that I mentioned briefly earlier. The CookieJar only handles the extraction and insertion of cookies included in the headers, but it does not provide the functionality to store cookies in a repository. This needs to be implemented by the developer.
In the implementation we created earlier, we stored cookies in a MutableMap in memory. However, since it is stored in volatile memory, the data will be lost when the app's process terminates. While this simple implementation might suffice in some cases, the data we are storing in the cookies is tokens, which need to persist even after the process ends.
Therefore, we need to use local data storage options like a database, Shared Preferences, or Data Store to ensure that the data remains available after the process ends. For our project, since we only needed to store simple string token data, we decided to use Data Store instead of a database.
First, Android's Data Store cannot store objects directly. So, we extracted the tokens stored as strings in the cookies and saved them to Data Store. When retrieving the saved values, we put them back into the cookies.
There was one consideration here:
Where should we handle the operation of saving the token to Data Store?
Since the ViewModel is responsible for data processing in MVVM, it is the most convenient place to access Data Store. Additionally, as the location where API calls are made, it naturally aligns with the responsibility of handling data returned from APIs.
However, if this task is handled by the ViewModel, every ViewModel calling an API would need to inject CookieJar as a constructor parameter. This is because the token resides in the cookies, which are managed by CookieJar. Therefore, all ViewModels would need to perform modification operations, and every time there are changes related to cookies, all ViewModels would need to be updated, which could become cumbersome.
So, what if we let CookieJar handle the token storage? Although there was a minor downside of needing to open a CoroutineScope to access Data Store within CookieJar, it would eliminate the need for other objects to pass the token, leading to a higher cohesion without creating dependencies on other objects.
Moreover, given the nature of the CookieJar object, it makes perfect sense to assign the responsibility of persistently managing the tokens contained in the cookies. With this decision, we moved forward with letting CookieJar handle this task. 
Now, letโs take a look at how this was implemented in code!
override fun saveFromResponse(
url: HttpUrl,
cookies: List<Cookie>,
) {
this.cookies[url.host] = cookies
saveTokensToDataStore(cookies)
}
private fun saveTokensToDataStore(cookies: List<Cookie>) {
val accessToken = cookies.first { it.name == ACCESS_TOKEN_NAME }.value
val refreshToken = cookies.first { it.name == REFRESH_TOKEN_NAME }.value
CoroutineScope(Dispatchers.IO).launch {
userPreferencesDataStore.saveTokens(accessToken, refreshToken)
}
}
Kotlin
๋ณต์ฌ
First, we retrieve the cookies from the Response while simultaneously saving the tokens from those cookies into the Data Store. We receive a parameter of type List<Cookie> and extract the Access Token and Refresh Token from it.
The cookie object has various properties (which will be explained in detail below). Among these, the value property contains the data stored in the cookie, namely the token. The name property is set by the server as "access_token" and "refresh_token." Therefore, we can find the cookie corresponding to these names in the list and extract its value to obtain the desired String-type token!
Then, we can save this token in the Data Store.
init {
loadTokensFromDataStore()
}
private fun loadTokensFromDataStore() {
CoroutineScope(Dispatchers.IO).launch {
val accessToken = userPreferencesDataStore.accessTokenFlow.first() ?: return@launch
val refreshToken = userPreferencesDataStore.refreshTokenFlow.first() ?: return@launch
val accessTokenCookie = makeTokenCookie(ACCESS_TOKEN_NAME, accessToken)
val refreshTokenCookie = makeTokenCookie(REFRESH_TOKEN_NAME, refreshToken)
cookies[urlHost] = listOf(accessTokenCookie, refreshTokenCookie)
}
}
private fun makeTokenCookie(
tokenName: String,
tokenValue: String,
): Cookie {
return Cookie.Builder()
.name(tokenName)
.value(tokenValue)
.hostOnlyDomain(urlHost)
.httpOnly()
.build()
}
Kotlin
๋ณต์ฌ
When the instance of CookieJar is created, we use the init block to retrieve the tokens stored in the Data Store and add them to the cookies.
The instance of CookieJar is created alongside the Retrofit module during the initial setup of the Application. This happens every time the app is launched, so you can think of it as transferring the tokens from the Data Store to memory (cookies) each time the app restarts! While the app is running, we can simply use the tokens from memory instead of fetching them from the Data Store again~~ 
5. Learn more about cookies
We previously discussed the task of storing the tokens from the Data Store into cookies. To do this, we need to create cookies, but how do we go about creating them? Let's take another look at the makeTokenCookie() function.
private fun makeTokenCookie(
tokenName: String,
tokenValue: String,
): Cookie {
return Cookie.Builder()
.name(tokenName)
.value(tokenValue)
.hostOnlyDomain(urlHost)
.httpOnly()
.build()
}
Kotlin
๋ณต์ฌ
We're creating cookies using the builder class of the Cookie object! Let's take a closer look at the Cookie.
As expected, the constructor is private, so we can't create a cookie directly using the constructor. We can see that it has properties like name and value.
This is the builder class used to create cookies. Here, we can see the properties of the cookie at a glance.
We can use functions like name() and value() to store data in the cookie.
After filling the cookie with all the data, we simply call the build() function, which returns the cookie. And just like that, the cookie is created!
Now, the CookieJar will send this cookie to the server.