<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Tapan Kumar Swain</title>
    <description>The latest articles on DEV Community by Tapan Kumar Swain (@tapan-7).</description>
    <link>https://dev.to/tapan-7</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3638323%2F894bcaaa-9408-40d7-8d2a-86b79b49d645.jpeg</url>
      <title>DEV Community: Tapan Kumar Swain</title>
      <link>https://dev.to/tapan-7</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tapan-7"/>
    <language>en</language>
    <item>
      <title>Google Drive Integration: From OAuth Setup to Background Sync</title>
      <dc:creator>Tapan Kumar Swain</dc:creator>
      <pubDate>Thu, 16 Apr 2026 10:44:37 +0000</pubDate>
      <link>https://dev.to/tapan-7/google-drive-integration-from-oauth-setup-to-background-sync-251d</link>
      <guid>https://dev.to/tapan-7/google-drive-integration-from-oauth-setup-to-background-sync-251d</guid>
      <description>&lt;p&gt;Integrating Google Drive isn't just about adding a "File Upload" button. It's about building a seamless, automated bridge between your user's cloud universe and your application's logic. &lt;/p&gt;

&lt;p&gt;This guide will walk you through everything from clicking the first button in the Google Console to handling background token refreshes like a pro.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table Of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;1. The Foundation (Google Cloud Console)&lt;/li&gt;
&lt;li&gt;2. The Environment Map&lt;/li&gt;
&lt;li&gt;3. The OAuth "Handshake"&lt;/li&gt;
&lt;li&gt;4. The "Eternal" Connection (Token Refresh)&lt;/li&gt;
&lt;li&gt;5. The Sync Engine (Architecture)&lt;/li&gt;
&lt;li&gt;Best Practices Checklist&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  1: The Foundation (Google Cloud Console)
&lt;/h2&gt;

&lt;p&gt;Before we write a single line of code, we need to tell Google who we are. &lt;/p&gt;

&lt;p&gt;
  Click here for the step-by-step Console setup
  &lt;h3&gt;
  
  
  1. Create Your Project
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; Head over to the &lt;a href="https://console.cloud.google.com/" rel="noopener noreferrer"&gt;Google Cloud Console&lt;/a&gt;. &lt;/li&gt;
&lt;li&gt; Create a &lt;strong&gt;New Project&lt;/strong&gt;. Give it a name you'll recognize, like &lt;code&gt;My-Awesome-App-Sync&lt;/code&gt;. &lt;/li&gt;
&lt;li&gt; &lt;strong&gt;The ID Card&lt;/strong&gt;: Once created, go to the &lt;strong&gt;Dashboard&lt;/strong&gt; and find your &lt;strong&gt;Project Number&lt;/strong&gt;. &lt;/li&gt;
&lt;li&gt; &lt;em&gt;In our code, this is:&lt;/em&gt; &lt;code&gt;NEXT_PUBLIC_GOOGLE_APP_ID&lt;/code&gt;.
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  2. Turn on the "Lights" (Enable APIs)
&lt;/h3&gt;

&lt;p&gt;Your project starts empty. You need to enable the specific tools we'll use: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;Library&lt;/strong&gt;. &lt;/li&gt;
&lt;li&gt;Search and &lt;strong&gt;Enable&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Google Drive API&lt;/strong&gt;: For the backend to read/sync files.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google Picker API&lt;/strong&gt;: For that beautiful file-selection popup on the frontend.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  3. The "Permissions" Screen (OAuth Consent)
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;OAuth consent screen&lt;/strong&gt;. &lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;External&lt;/strong&gt; (standard for most apps). &lt;/li&gt;
&lt;li&gt;Fill in your App Name and Support Email. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scopes&lt;/strong&gt;: This is crucial. Add &lt;code&gt;https://www.googleapis.com/auth/drive.readonly&lt;/code&gt;. This tells the user exactly what you'll be doing (only reading files, not deleting them!).
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  4. Getting Your Keys (Credentials)
&lt;/h3&gt;

&lt;p&gt;You need two things here:  &lt;/p&gt;
&lt;h4&gt;
  
  
  A. OAuth 2.0 Client ID
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Create Credentials &amp;gt; OAuth client ID&lt;/strong&gt;. &lt;/li&gt;
&lt;li&gt;Application type: &lt;strong&gt;Web application&lt;/strong&gt;. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Origins&lt;/strong&gt;: &lt;code&gt;http://localhost:port&lt;/code&gt; (for dev). &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redirect URIs&lt;/strong&gt;: &lt;code&gt;http://localhost:port/route_name&lt;/code&gt;.
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;
  
  
  B. API Key
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Create Credentials &amp;gt; API Key&lt;/strong&gt;. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Restrict it&lt;/strong&gt;: Under "API restrictions", select &lt;strong&gt;Google Picker API&lt;/strong&gt;. This ensures your key can't be stolen and used for other expensive services.
&lt;/li&gt;
&lt;/ol&gt;




&lt;/p&gt;

&lt;h2&gt;
  
  
  2: The Environment Map
&lt;/h2&gt;

&lt;p&gt;Take those keys and drop them into your &lt;code&gt;.env&lt;/code&gt; files. We use a card here to keep your configuration organized.&lt;/p&gt;
&lt;h3&gt;
  
  
  🔑 Environment Variables
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Backend (&lt;code&gt;backend/.env&lt;/code&gt;)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="err"&gt;env&lt;/span&gt;
&lt;span class="py"&gt;GOOGLE_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"xxx-yyy.apps.googleusercontent.com"&lt;/span&gt;
&lt;span class="py"&gt;GOOGLE_CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"GOCSPX-your-secret"&lt;/span&gt;
&lt;span class="py"&gt;GOOGLE_CALLBACK_URL&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"http://localhost:port/route_name"&lt;/span&gt;
&lt;span class="err"&gt;Frontend&lt;/span&gt; &lt;span class="err"&gt;(frontend/.env)&lt;/span&gt;

&lt;span class="py"&gt;NEXT_PUBLIC_GOOGLE_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"xxx-yyy.apps.googleusercontent.com"&lt;/span&gt;
&lt;span class="py"&gt;NEXT_PUBLIC_GOOGLE_API_KEY&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"AIza-your-api-key"&lt;/span&gt;
&lt;span class="py"&gt;NEXT_PUBLIC_GOOGLE_APP_ID&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"your-project-number
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3: The OAuth "Handshake" (How it Works)&lt;br&gt;
This is where the magic happens. We use a Hybrid Flow for maximum security and longevity.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Frontend "Ask"
The user clicks "Connect". We use the Google Identity Services (GIS) library to open a popup.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Initialization URL: &lt;a href="https://accounts.google.com/gsi/client" rel="noopener noreferrer"&gt;https://accounts.google.com/gsi/client&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oauth2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initCodeClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GOOGLE_CONFIG&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://www.googleapis.com/auth/drive.readonly&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;ux_mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;popup&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// This gives us an 'authorization_code'&lt;/span&gt;
    &lt;span class="nf"&gt;sendCodeToBackend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;requestCode&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;The Backend "Exchange"
Your frontend sends that code to your backend. Your backend then talks to Google privately to get the "Keys to the Kingdom."&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Token Exchange URL: POST &lt;a href="https://oauth2.googleapis.com/token" rel="noopener noreferrer"&gt;https://oauth2.googleapis.com/token&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Example Request (Node.js/Fetch):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://oauth2.googleapis.com/token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/x-www-form-urlencoded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;authCodeFromFrontend&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GOOGLE_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;client_secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GOOGLE_CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;redirect_uri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;postmessage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Crucial for GIS popup flow&lt;/span&gt;
    &lt;span class="na"&gt;grant_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;authorization_code&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; 

&lt;span class="c1"&gt;// Returns: access_token, refresh_token, expires_in (usually 3599)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;4: The "Eternal" Connection (Token Refresh)&lt;br&gt;
Google's access_token is like a temporary pass—it expires in 1 hour (3600 seconds). To keep syncing files while the user is asleep, you need the refresh_token.&lt;/p&gt;

&lt;p&gt;How to get a new Access Token&lt;br&gt;
When your database shows the expiresAt is near, or you get a 401 Unauthorized from Google, call this:&lt;/p&gt;

&lt;p&gt;Token Refresh URL: POST &lt;a href="https://oauth2.googleapis.com/token" rel="noopener noreferrer"&gt;https://oauth2.googleapis.com/token&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Example Request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;grant_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;refresh_token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;encryptedStoredRefreshToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GOOGLE_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;client_secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GOOGLE_CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://oauth2.googleapis.com/token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/x-www-form-urlencoded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// New access_token received!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;// Update your DB with the new token and new expiry time&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: Always refresh the token 5-10 minutes before it expires to account for network lag and ensure your background jobs never fail.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;5: The Sync Engine (Architecture)&lt;br&gt;
Once authenticated, our platform uses a Recursive Crawler to map the user's Drive.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Recursive Traversal: Google Drive is "flat." We start at the folder the user picked and recursively call drive.files.list for every sub-folder.&lt;/li&gt;
&lt;li&gt;Encryption at Rest: We never store raw tokens. Everything is encrypted using AES-256-GCM before hitting the database.&lt;/li&gt;
&lt;li&gt;Background Jobs: We queue the files for processing so the user doesn't have to wait for the sync to finish.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Best Practices Checklist
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The "Pro" Checklist:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[x] Restrict API Keys: Limit your key to the "Google Picker API" only in the Google Console.&lt;/li&gt;
&lt;li&gt;[x] Encrypt Everything: Store tokens using AES-256-GCM or a similar standard.&lt;/li&gt;
&lt;li&gt;[x] Offline Access: Always ensure you are requesting access_type: 'offline' to receive that vital refresh token.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Critical Mistakes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Never commit your GOOGLE_CLIENT_SECRET to GitHub or expose it in frontend code.&lt;/li&gt;
&lt;li&gt;Never hardcode your redirect URIs; always use environment variables to switch between dev and production.&lt;/li&gt;
&lt;li&gt;Don't Forget: Handle the case where a user revokes access from their Google Account settings manually. &lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>googlecloud</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Google Drive Integration: From OAuth Setup to Background Sync</title>
      <dc:creator>Tapan Kumar Swain</dc:creator>
      <pubDate>Thu, 16 Apr 2026 10:33:03 +0000</pubDate>
      <link>https://dev.to/tapan-7/google-drive-integration-from-oauth-setup-to-background-sync-4119</link>
      <guid>https://dev.to/tapan-7/google-drive-integration-from-oauth-setup-to-background-sync-4119</guid>
      <description>&lt;p&gt;Integrating Google Drive isn't just about adding a "File Upload" button. It's about building a seamless, automated bridge between your user's cloud universe and your application's logic. &lt;/p&gt;

&lt;p&gt;This guide will walk you through everything from clicking the first button in the Google Console to handling background token refreshes like a pro.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table Of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;1. The Foundation (Google Cloud Console)&lt;/li&gt;
&lt;li&gt;2. The Environment Map&lt;/li&gt;
&lt;li&gt;3. The OAuth "Handshake"&lt;/li&gt;
&lt;li&gt;4. The "Eternal" Connection (Token Refresh)&lt;/li&gt;
&lt;li&gt;5. The Sync Engine (Architecture)&lt;/li&gt;
&lt;li&gt;Best Practices Checklist&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  1: The Foundation (Google Cloud Console)
&lt;/h2&gt;

&lt;p&gt;Before we write a single line of code, we need to tell Google who we are. &lt;/p&gt;

&lt;p&gt;
  Click here for the step-by-step Console setup
  &lt;h3&gt;
  
  
  1. Create Your Project
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; Head over to the &lt;a href="https://console.cloud.google.com/" rel="noopener noreferrer"&gt;Google Cloud Console&lt;/a&gt;. &lt;/li&gt;
&lt;li&gt; Create a &lt;strong&gt;New Project&lt;/strong&gt;. Give it a name you'll recognize, like &lt;code&gt;My-Awesome-App-Sync&lt;/code&gt;. &lt;/li&gt;
&lt;li&gt; &lt;strong&gt;The ID Card&lt;/strong&gt;: Once created, go to the &lt;strong&gt;Dashboard&lt;/strong&gt; and find your &lt;strong&gt;Project Number&lt;/strong&gt;. &lt;/li&gt;
&lt;li&gt; &lt;em&gt;In our code, this is:&lt;/em&gt; &lt;code&gt;NEXT_PUBLIC_GOOGLE_APP_ID&lt;/code&gt;.
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  2. Turn on the "Lights" (Enable APIs)
&lt;/h3&gt;

&lt;p&gt;Your project starts empty. You need to enable the specific tools we'll use: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;Library&lt;/strong&gt;. &lt;/li&gt;
&lt;li&gt;Search and &lt;strong&gt;Enable&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Google Drive API&lt;/strong&gt;: For the backend to read/sync files.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google Picker API&lt;/strong&gt;: For that beautiful file-selection popup on the frontend.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  3. The "Permissions" Screen (OAuth Consent)
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;OAuth consent screen&lt;/strong&gt;. &lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;External&lt;/strong&gt; (standard for most apps). &lt;/li&gt;
&lt;li&gt;Fill in your App Name and Support Email. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scopes&lt;/strong&gt;: This is crucial. Add &lt;code&gt;https://www.googleapis.com/auth/drive.readonly&lt;/code&gt;. This tells the user exactly what you'll be doing (only reading files, not deleting them!).
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  4. Getting Your Keys (Credentials)
&lt;/h3&gt;

&lt;p&gt;You need two things here:  &lt;/p&gt;
&lt;h4&gt;
  
  
  A. OAuth 2.0 Client ID
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Create Credentials &amp;gt; OAuth client ID&lt;/strong&gt;. &lt;/li&gt;
&lt;li&gt;Application type: &lt;strong&gt;Web application&lt;/strong&gt;. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Origins&lt;/strong&gt;: &lt;code&gt;http://localhost:port&lt;/code&gt; (for dev). &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redirect URIs&lt;/strong&gt;: &lt;code&gt;http://localhost:port/route_name&lt;/code&gt;.
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;
  
  
  B. API Key
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Create Credentials &amp;gt; API Key&lt;/strong&gt;. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Restrict it&lt;/strong&gt;: Under "API restrictions", select &lt;strong&gt;Google Picker API&lt;/strong&gt;. This ensures your key can't be stolen and used for other expensive services.
&lt;/li&gt;
&lt;/ol&gt;




&lt;/p&gt;

&lt;h2&gt;
  
  
  2: The Environment Map
&lt;/h2&gt;

&lt;p&gt;Take those keys and drop them into your &lt;code&gt;.env&lt;/code&gt; files. We use a card here to keep your configuration organized.&lt;/p&gt;
&lt;h3&gt;
  
  
  🔑 Environment Variables
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Backend (&lt;code&gt;backend/.env&lt;/code&gt;)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="err"&gt;env&lt;/span&gt;
&lt;span class="py"&gt;GOOGLE_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"xxx-yyy.apps.googleusercontent.com"&lt;/span&gt;
&lt;span class="py"&gt;GOOGLE_CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"GOCSPX-your-secret"&lt;/span&gt;
&lt;span class="py"&gt;GOOGLE_CALLBACK_URL&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"http://localhost:port/route_name"&lt;/span&gt;
&lt;span class="err"&gt;Frontend&lt;/span&gt; &lt;span class="err"&gt;(frontend/.env)&lt;/span&gt;

&lt;span class="py"&gt;NEXT_PUBLIC_GOOGLE_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"xxx-yyy.apps.googleusercontent.com"&lt;/span&gt;
&lt;span class="py"&gt;NEXT_PUBLIC_GOOGLE_API_KEY&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"AIza-your-api-key"&lt;/span&gt;
&lt;span class="py"&gt;NEXT_PUBLIC_GOOGLE_APP_ID&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"your-project-number
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3: The OAuth "Handshake" (How it Works)
&lt;/h2&gt;

&lt;p&gt;This is where the magic happens. We use a Hybrid Flow for maximum security and longevity.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Frontend "Ask"
The user clicks "Connect". We use the Google Identity Services (GIS) library to open a popup.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Initialization URL: &lt;a href="https://accounts.google.com/gsi/client" rel="noopener noreferrer"&gt;https://accounts.google.com/gsi/client&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oauth2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initCodeClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GOOGLE_CONFIG&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://www.googleapis.com/auth/drive.readonly&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;ux_mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;popup&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// This gives us an 'authorization_code'&lt;/span&gt;
    &lt;span class="nf"&gt;sendCodeToBackend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;requestCode&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;The Backend "Exchange"
Your frontend sends that code to your backend. Your backend then talks to Google privately to get the "Keys to the Kingdom."&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Token Exchange URL: POST &lt;a href="https://oauth2.googleapis.com/token" rel="noopener noreferrer"&gt;https://oauth2.googleapis.com/token&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Example Request (Node.js/Fetch):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://oauth2.googleapis.com/token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/x-www-form-urlencoded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;authCodeFromFrontend&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GOOGLE_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;client_secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GOOGLE_CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;redirect_uri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;postmessage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Crucial for GIS popup flow&lt;/span&gt;
    &lt;span class="na"&gt;grant_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;authorization_code&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; 

&lt;span class="c1"&gt;// Returns: access_token, refresh_token, expires_in (usually 3599)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4: The "Eternal" Connection (Token Refresh)
&lt;/h2&gt;

&lt;p&gt;Google's access_token is like a temporary pass—it expires in 1 hour (3600 seconds). To keep syncing files while the user is asleep, you need the refresh_token.&lt;/p&gt;

&lt;p&gt;How to get a new Access Token&lt;br&gt;
When your database shows the expiresAt is near, or you get a 401 Unauthorized from Google, call this:&lt;/p&gt;

&lt;p&gt;Token Refresh URL: POST &lt;a href="https://oauth2.googleapis.com/token" rel="noopener noreferrer"&gt;https://oauth2.googleapis.com/token&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Example Request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;grant_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;refresh_token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;encryptedStoredRefreshToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GOOGLE_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;client_secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GOOGLE_CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://oauth2.googleapis.com/token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/x-www-form-urlencoded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// New access_token received!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;// Update your DB with the new token and new expiry time&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Note: Always refresh the token 5-10 minutes before it expires to account for network lag and ensure your background jobs never fail.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5: The Sync Engine (Architecture)
&lt;/h2&gt;

&lt;p&gt;Once authenticated, our platform uses a Recursive Crawler to map the user's Drive.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Recursive Traversal: Google Drive is "flat." We start at the folder the user picked and recursively call drive.files.list for every sub-folder.&lt;/li&gt;
&lt;li&gt;Encryption at Rest: We never store raw tokens. Everything is encrypted using AES-256-GCM before hitting the database.&lt;/li&gt;
&lt;li&gt;Background Jobs: We queue the files for processing so the user doesn't have to wait for the sync to finish.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Best Practices Checklist
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The "Pro" Checklist:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[x] Restrict API Keys: Limit your key to the "Google Picker API" only in the Google Console.&lt;/li&gt;
&lt;li&gt;[x] Encrypt Everything: Store tokens using AES-256-GCM or a similar standard.&lt;/li&gt;
&lt;li&gt;[x] Offline Access: Always ensure you are requesting access_type: 'offline' to receive that vital refresh token.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Critical Mistakes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Never commit your GOOGLE_CLIENT_SECRET to GitHub or expose it in frontend code.&lt;/li&gt;
&lt;li&gt;Never hardcode your redirect URIs; always use environment variables to switch between dev and production.&lt;/li&gt;
&lt;li&gt;Don't Forget: Handle the case where a user revokes access from their Google Account settings manually. &lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>googlecloud</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Store VideoSDK Cloud Recordings Securely on AWS S3</title>
      <dc:creator>Tapan Kumar Swain</dc:creator>
      <pubDate>Thu, 08 Jan 2026 06:18:31 +0000</pubDate>
      <link>https://dev.to/tapan-7/how-to-store-videosdk-cloud-recordings-securely-on-aws-s3-g5</link>
      <guid>https://dev.to/tapan-7/how-to-store-videosdk-cloud-recordings-securely-on-aws-s3-g5</guid>
      <description>&lt;p&gt;When using &lt;strong&gt;VideoSDK Live&lt;/strong&gt; for video conferencing or live streaming, cloud recording is a common requirement. VideoSDK supports &lt;strong&gt;direct upload of recorded videos to AWS S3&lt;/strong&gt;, but to enable this securely, a specific AWS configuration is required.&lt;/p&gt;

&lt;p&gt;This guide walks you through the &lt;strong&gt;complete AWS setup&lt;/strong&gt;, from creating an IAM user with minimal permissions to configuring storage in the VideoSDK dashboard.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why a Dedicated IAM User Is Required
&lt;/h2&gt;

&lt;p&gt;For security best practices, VideoSDK should &lt;strong&gt;never use your AWS root account&lt;/strong&gt;. Instead, create a &lt;strong&gt;dedicated IAM user&lt;/strong&gt; with restricted permissions so it can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Upload video recordings
&lt;/li&gt;
&lt;li&gt;Read or delete recordings if required
&lt;/li&gt;
&lt;li&gt;Access &lt;strong&gt;only one specific S3 bucket&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Benefits
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt; Least-privilege security
&lt;/li&gt;
&lt;li&gt; Easy credential rotation
&lt;/li&gt;
&lt;li&gt; Reduced blast radius
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 1: Create an IAM User for VideoSDK
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Log in to the &lt;strong&gt;AWS Console&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt; Navigate to &lt;strong&gt;Services → Security, Identity &amp;amp; Compliance → IAM&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt; Click &lt;strong&gt;Users → Create user&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  User Details
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;User name:&lt;/strong&gt; &lt;code&gt;videosdk-storage&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Access type:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt; Programmatic access
&lt;/li&gt;
&lt;li&gt; Console access (not required)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Click &lt;strong&gt;Next&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Create a Custom S3 Policy
&lt;/h2&gt;

&lt;p&gt;To restrict access to only the required bucket and actions, create a &lt;strong&gt;custom IAM policy&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;On the permissions page:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Select &lt;strong&gt;Attach policies directly&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create policy&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Switch to the &lt;strong&gt;JSON&lt;/strong&gt; tab and paste the following:&lt;br&gt;&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"s3:PutObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"s3:PutObjectAcl"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"s3:DeleteObject"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::YOUR-BUCKET-NAME/*"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"s3:ListBucket"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::YOUR-BUCKET-NAME"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Replace Bucket Name
&lt;/h3&gt;

&lt;p&gt;Replace &lt;strong&gt;YOUR-BUCKET-NAME&lt;/strong&gt; with your actual S3 bucket name.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;arn:aws:s3:::my-video-recordings/*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Save the Policy
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Name: VideoSDK-S3-Access&lt;/li&gt;
&lt;li&gt;Description: S3 access for VideoSDK recordings&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Click &lt;strong&gt;Create policy&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Attach the Policy to the IAM User
&lt;/h2&gt;

&lt;p&gt;This step ensures the IAM user actually has permission to access your S3 bucket.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; Return to the &lt;strong&gt;IAM user creation screen&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt; Refresh the policy list&lt;/li&gt;
&lt;li&gt; Search for &lt;strong&gt;VideoSDK-S3-Access&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt; Select the policy&lt;/li&gt;
&lt;li&gt; Click &lt;strong&gt;Next → Create user&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Your IAM user is now correctly permissioned.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Generate AWS Access Keys
&lt;/h2&gt;

&lt;p&gt;After the user is created:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; Open the user: &lt;strong&gt;videosdk-storage&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt; Go to the &lt;strong&gt;Security credentials&lt;/strong&gt; tab&lt;/li&gt;
&lt;li&gt; Click &lt;strong&gt;Create access key&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt; Select &lt;strong&gt;Third-party service&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt; Confirm and continue&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Save These Credentials
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Access Key ID&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Secret Access Key&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;⚠️ The secret key will be shown &lt;strong&gt;only once&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
Download the &lt;code&gt;.csv&lt;/code&gt; file for backup.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: Create an S3 Bucket (If Needed)
&lt;/h2&gt;

&lt;p&gt;If you don’t already have an S3 bucket:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; Go to &lt;strong&gt;Services → S3&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt; Click &lt;strong&gt;Create bucket&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Bucket Configuration
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bucket name:&lt;/strong&gt; &lt;code&gt;your-videosdk-recordings&lt;/code&gt; (must be globally unique)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Region:&lt;/strong&gt; Choose and remember this&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Block all public access:&lt;/strong&gt; Enabled&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Click &lt;strong&gt;Create bucket&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6: Configure AWS S3 in VideoSDK Dashboard
&lt;/h2&gt;

&lt;p&gt;VideoSDK Live supports &lt;strong&gt;cloud recording with direct S3 upload&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuration Steps
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt; Open &lt;strong&gt;VideoSDK Dashboard&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt; Go to &lt;strong&gt;API Keys&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt; Select your project&lt;/li&gt;
&lt;li&gt; Scroll to &lt;strong&gt;Storage Configuration&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt; Add your AWS S3 details:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"bucket"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-recordings-bucket"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"accessKeyId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"YOUR_ACCESS_KEY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"secretAccessKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"YOUR_SECRET_KEY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"acl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"private"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Field Explanation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;bucket – Your S3 bucket name&lt;/li&gt;
&lt;li&gt;region – Bucket region&lt;/li&gt;
&lt;li&gt;accessKeyId – IAM access key&lt;/li&gt;
&lt;li&gt;secretAccessKey – IAM secret key&lt;/li&gt;
&lt;li&gt;acl – Use private (recommended)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Save the configuration.&lt;/p&gt;

</description>
      <category>videosdk</category>
      <category>aws</category>
    </item>
    <item>
      <title>Integrating Health Connect in Android + React Native Apps</title>
      <dc:creator>Tapan Kumar Swain</dc:creator>
      <pubDate>Mon, 01 Dec 2025 09:06:47 +0000</pubDate>
      <link>https://dev.to/tapan-7/integrating-health-connect-in-android-react-native-apps-2cj4</link>
      <guid>https://dev.to/tapan-7/integrating-health-connect-in-android-react-native-apps-2cj4</guid>
      <description>&lt;p&gt;Health Connect is Google’s unified API for managing health and fitness data on Android.&lt;br&gt;
Instead of working with multiple SDKs, you can now use one standard interface to read and write health metrics such as steps, heart rate, workouts, calories, sleep, and much more.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Health Connect?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;One central location for all health data.&lt;/li&gt;
&lt;li&gt;Compatible with major apps and devices.&lt;/li&gt;
&lt;li&gt;User-controlled permissions to ensure privacy.&lt;/li&gt;
&lt;li&gt;Secure storage protected by device lock.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: Health Connect is available only on Android. iOS uses HealthKit, which is not related.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A React Native project set up (0.71+ is recommended).&lt;/li&gt;
&lt;li&gt;Android Studio installed.&lt;/li&gt;
&lt;li&gt;A device or emulator running Android 13 or higher (the Health Connect app is needed on Android 13).&lt;/li&gt;
&lt;li&gt;Screen lock enabled on the device.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;1. Installing Health Connect&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For Android 13 (API 33) and lower: Download the Health Connect app from the Play Store.&lt;/li&gt;
&lt;li&gt;For Android 14 and above: Health Connect is already included.&lt;/li&gt;
&lt;li&gt;Tip: Check availability with:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { isAvailable } from "react-native-health-connect";
const available = await isAvailable();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;2. Add SDK Dependency&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Add the following in app/build.gradle:

dependencies {
    implementation "androidx.health.connect:connect-client:1.1.0-alpha11"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Configure Permissions&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Include necessary permissions in AndroidManifest.xml.&lt;/li&gt;
&lt;li&gt;Example for heart rate and steps:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!-- TODO: Required to specify which Health Connect permissions the app can request --&amp;gt;
  &amp;lt;uses-permission android:name="android.permission.health.READ_HEART_RATE"/&amp;gt;
  &amp;lt;uses-permission android:name="android.permission.health.WRITE_HEART_RATE"/&amp;gt;
  &amp;lt;uses-permission android:name="android.permission.health.READ_STEPS"/&amp;gt;
  &amp;lt;uses-permission android:name="android.permission.health.WRITE_STEPS"/&amp;gt;
  &amp;lt;uses-permission android:name="android.permission.health.READ_EXERCISE"/&amp;gt;
  &amp;lt;uses-permission android:name="android.permission.health.WRITE_EXERCISE"/&amp;gt;
  &amp;lt;uses-permission android:name="android.permission.health.READ_TOTAL_CALORIES_BURNED"/&amp;gt;
  &amp;lt;uses-permission android:name="android.permission.health.WRITE_TOTAL_CALORIES_BURNED"/&amp;gt;
  &amp;lt;uses-permission android:name="android.permission.health.READ_WEIGHT"/&amp;gt;
  &amp;lt;uses-permission android:name="android.permission.health.WRITE_WEIGHT"/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!-- TODO: declare Health Connect visibility --&amp;gt;
&amp;lt;queries&amp;gt;
   &amp;lt;package android:name="com.google.android.apps.healthdata" /&amp;gt;
&amp;lt;/queries&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!-- TODO: Add intent filter to handle permission rationale intent --&amp;gt;
&amp;lt;!-- Permission handling for Android 13 and before --&amp;gt;
&amp;lt;intent-filter&amp;gt;
  &amp;lt;action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" /&amp;gt;
&amp;lt;/intent-filter&amp;gt;

&amp;lt;!-- Permission handling for Android 14 and later --&amp;gt;
&amp;lt;intent-filter&amp;gt;
  &amp;lt;action android:name="android.intent.action.VIEW_PERMISSION_USAGE"/&amp;gt;
  &amp;lt;category android:name="android.intent.category.HEALTH_PERMISSIONS"/&amp;gt;
&amp;lt;/intent-filter&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Tip: Each data type requires explicit read and write permissions.&lt;/li&gt;
&lt;li&gt;Android 14 and above requires an activity-alias for permission rationale.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. Update MainActivity.kt&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;React Native requires a permission delegate:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HealthConnectPermissionDelegate.setPermissionDelegate(this);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Place it inside onCreate() of MainActivity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;5. Build Configurations&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Minimum SDK version: 26&lt;/li&gt;
&lt;li&gt;Compile SDK version: 35+
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;minSdkVersion 26
compileSdkVersion 35
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;6. Using Health Connect in React Native&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Request Permissions&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { requestPermission } from "react-native-health-connect";

await requestPermission([
  { accessType: "read", recordType: "Steps" },
  { accessType: "write", recordType: "HeartRate" }
]);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Read Records&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { readRecords } from "react-native-health-connect";
const steps = await readRecords("Steps");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Write Records&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { writeRecords } from "react-native-health-connect";
await writeRecords("HeartRate", [{ value: 72, startTime: new Date() }]);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Tip: Always check if you have permission before reading or writing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;7. Common Issues &amp;amp; Fixes&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Issue&lt;/th&gt;
&lt;th&gt;Cause&lt;/th&gt;
&lt;th&gt;Solution&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SDK_UNAVAILABLE&lt;/td&gt;
&lt;td&gt;Health Connect is not installed&lt;/td&gt;
&lt;td&gt;Install Health Connect (for Android 13 and lower)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Permission screen not opening&lt;/td&gt;
&lt;td&gt;Permission delegate missing&lt;/td&gt;
&lt;td&gt;Add the delegate to MainActivity.kt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Crash on Android 14&lt;/td&gt;
&lt;td&gt;Missing activity-alias&lt;/td&gt;
&lt;td&gt;Add the alias block mentioned above&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cannot request permissions again&lt;/td&gt;
&lt;td&gt;User denied twice&lt;/td&gt;
&lt;td&gt;User must reset permissions in Health Connect settings&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;8. Tips &amp;amp; Best Practices&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Test on actual devices; emulators have limited support.&lt;/li&gt;
&lt;li&gt;Encourage users to enable a screen lock.&lt;/li&gt;
&lt;li&gt;Always handle denied permissions in a user-friendly way.&lt;/li&gt;
&lt;li&gt;Consider batching reads and writes to minimize unnecessary API calls.&lt;/li&gt;
&lt;li&gt;Respect user privacy; never send raw health data to outside servers without consent.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;9. References&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Official Android &lt;a href="https://developer.android.com/codelabs/health-connect" rel="noopener noreferrer"&gt;Health Connect Documentation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;React Native &lt;a href="https://www.npmjs.com/package/react-native-health-connect" rel="noopener noreferrer"&gt;Health Connect Library&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>reactnative</category>
      <category>android</category>
      <category>healthconnect</category>
      <category>fitnessapp</category>
    </item>
  </channel>
</rss>
