← Back to blogYouTube Video

Published November 10, 2025

Syncing projects with Google Drive - Part 6 (Refreshing OAuth Tokens)

Part 6 of 19 in Syncing Projects with Google Drive

Can't play the video or having issues? Here's the direct link.

Summary

I wire up automatic OAuth token refresh for the Google Drive syncer so I never have to manually re-authorize access again. I build a testAccess function that hits the Drive files list endpoint with a page size of one to check whether the current token still works, and map the result to three states: access granted, access expired, or access rejected. Google returns two different error messages for an expired token (token expired and token has been expired), so I catch both before falling back to a hard rejection. The change that actually removes the manual step is config.TokenSource in the get client function: it refreshes the access token when it expires, reuses it when it's still valid, and I save the refreshed token back to disk and build the client with oauth2.NewClient. The result is that the program reconnects on its own, and I can finally focus on features instead of re-authenticating.

Key takeaways

  • Validate API access before every run with a cheap call — listing a single Drive file is fast and tells you exactly whether your token still works.
  • Handle both of Google's expiry errors: token expired and token has been expired mean the same thing, and you'll hit both in different scenarios.
  • Use config.TokenSource to refresh tokens: it reuses a valid access token and only refreshes when it expires, as long as the refresh token is valid.
  • Persist the refreshed token back to disk and build the client with oauth2.NewClient so new credentials survive restarts.
  • Treat a rejected token differently from an expired one — rejection isn't an expiry issue, so exit and fix it manually rather than looping on refresh.

Description

In this part, we'll set up our OAuth tokens to refresh automatically so I don't have to do it manually. :p Github: https://github.com/hassanaziz0012/drive-syncer-video

Transcript

All right, welcome back. No long intros on this one. Let's just quickly recap what we did last time. We built this `readGitIgnore` function over here, and what this basically does is it's going to read the `.gitignore` file of your project and basically just ignore all of the files inside this project so we don't upload them, right? That's what we did the last video. Now, this video, I want to basically build a feature that allows me to reconnect to the Google API without me having to manually connect every single time, right? So you know that our tokens, our access tokens, are going to expire after a certain period of time, right? I want to refresh them automatically without me having to reconnect manually, right? So that's what we're going to build in this video. So, to do this, let's go back to the `main.go` file over here because this is where we have all of the OAuth and authentication stuff, right? Now, over here, let's create an enum as well. Let's say `type APIAccessStatus`, and I'll just call this an integer for now, and we're going to create a bunch of statuses over here. So let's say `const`, let's say `AccessExpired`, and this will be a type of `APIAccessStatus`, and I will just call this `iota` so that we can just auto-assign all of the values over here, right? I'll create two more. We'll say `AccessGranted` and `AccessRejected`, let's say. And now that we have this, I want to create a function to test the access to the API to see whether we can actually access it or not, right? So let's create a function, call it `testAccess`. We'll pass in the drive as well over here, and this will just return an `APIAccessStatus`, right, what we created up here. And the way I'm going to access the API or test the access to the API is I'm just going to run a very simple, very small API call over here. I'll just say `drive.API.Files.List`, and I will only list a single file from the API by using `PageSize(1)`, and I'll just call `Do()` over here. So this will be a very small API call that's just going to list one file out of our Google Drive. And it'll be a fast call, and we'll know whether we can access the API or not, right? Now, if there is an error over here, let's filter it out. Let's use `strings.Contains` and then say `error.Error()`. They should return a string over here. And let's also convert it to lowercase just so we don't have any issues with matching the error, right? So let's say `strings.ToLower`. Then you can say something like "token expired", and that should work. Now, along with this one, you also have to add a check for another similar error. So let's say `strings.Contains`, and this will be "token has been expired". So you might be wondering, why do we need two of these, right? They both just mean the same thing. Well, for some reason, Google's API returns both of these errors, right, in different scenarios. I don't know which exact scenario returns which error, but both of these mean the same thing, and in both of these scenarios, we're just gonna have to refresh the token, right? So I don't know why they return different error messages when they both mean the same thing, but we need to handle both, right? So over here, we're going to return `AccessExpired` because both of these errors mean that the token is expired, right? Otherwise, if there's a different kind of error, we'll just say `AccessRejected`, right? And otherwise, if there is no error at all, then we can just return `AccessGranted`, and that's all we need to do, right? And now, to actually test this out, right, we can go over to the `Connect` function, the `drive.Connect` function, and we can run this function that we just built. Just say `testAccess` and pass in the drive, pass in `d`, that is the drive over here. And if this status equals, let's say, `AccessExpired`, and we haven't tried to force refresh this already, then we need to force refresh this, right? So basically, if the status of this token is expired and we haven't tried to force refresh it already, right, that's when we need to force refresh it again, right? By force over here, now we're gonna have to repeat all of this code over here. So I'm just going to move it to a different function up here. Let's just create a function, call it, let's say, `createDriveService`, and there we go. And this will return the service over here. Let's pass in a bunch of parameters as well. `forceRefresh`, which will be a Boolean, and this will return a pointer to `drive.Service`. There we go. And then we can just call this over here by saying `createDriveService`, passing `forceRefresh` over here, and there we go. Awesome! So now what we can do is copy these two lines and paste them over here. And if the status has expired and we haven't tried to force refresh it already, then we should create the drive service, and this time pass in `true` over here to manually force refresh the entire thing, right? And then assign that to the `drive.API` over here. We can also add a bunch of logs over here as well. We can say something like, I don't know, `log.Println("Token has expired")`, and a new line as well. And down here, we can say, "Token has been rejected", and down here we can say, "Token is valid", let's just say. So now let's actually test this out. And before we do that, let's go up here. In the previous video, in the `drive.Connect` function over here, I passed in `forceRefresh: true` over here, right? So let's change this back to `false` because we want to reuse our tokens as much as we can, right? But if we need to force refresh, then we built the functionality to do that automatically. So let's test this out. This is just going to upload my folders from the config for now, right? So let's run this and see what happens. Okay, it says, "Token is valid", so it's not going to refresh the token right now. All right, and as you can see, all of the uploading process works as well, just like we built in the last video. Okay, so now what I've done is I've basically just destroyed the entire token, malformed it over here, just removing a bunch of characters from over here. And this token is going to get rejected because it's just not valid anymore, right? I've just messed with it. And as you can see, I already ran the entire program, and the token was rejected. I'll do it again over here, and yeah, you can see token has been rejected, and it's not going to be able to upload any further, right? Okay, so all of this works. Now, there is one more thing I want to do over here, and that is to actually be able to refresh the tokens. So let me explain. All of this code we wrote is going to work when the access and the refresh tokens both have expired, right? And so we need to go over to the URL on Google, right, and manually, you know, allow access to our Google Drive, right? But we still have a refresh token, right? And we can use that refresh token to refresh our access tokens. And the way to do that is up here in the get client function. So for now, you can see that we're just grabbing the token and then just returning a client with it, right? What we can instead do is we can create something called a config.tokenSource, pass in the context over here, which I guess for now is just going to be context.background, and also pass in the token over here. What the token source object does is it basically automatically refreshes the access token if it expires, right? So by using this, we can, you know, make this whole program a lot better because it's just going to automatically refresh our access tokens as long as our refresh tokens are valid. So now that we've created this, what we can do is say that the token is ts.token, and this will also return an error. So let's just quickly handle that as well. Let's just say, if error does not equal to nil, log fatal, "Failed to generate a token," and print the error over here along with a new line. And there we go. And once we've generated that new token, let's also save it again over here, passing the token file path, passing the token as well. And instead of using config.client, let's use oauth2.newclient instead and passing the token source over here. So now, with all of these changes, we're going to be able to refresh our access tokens automatically, right? This token source object is going to automatically refresh our access tokens if they expire. And if it's still valid, if it hasn't expired yet, then it's just going to reuse it, right? So it's only going to refresh the token if it expires. All right, so this is super helpful for us. And that is all we're going to do for this video. We built the functionality to automatically refresh our tokens and everything so that we don't have to do it manually. You saw in the previous videos that I had to sometimes, you know, go manually and refresh the entire access token, allow access to my drive to the API again and again, right? I had to do that a couple times in the previous videos. We don't need to do that anymore, all right? Because with all of this code that we've written, it's just going to do all of that for us automatically, right? So now we can finally focus on the features instead of just constantly manually refreshing our tokens, right? And I've already did a quick test of the program as well. As you can see, it's going to first save the credential file. Can I zoom in over here? Let me see. Yeah, it's going to save the credential file. It's going to check whether the token is valid or not. If it is valid, then it's just going to continue. If it is, you know, expired, then it's going to allow us to refresh that token by giving us the URL to go authorize our accounts once again. And if the token is expired, oh, I'm sorry, if the token has been rejected, then it's just going to exit the program, right? Because then it's not an expiry issue. It's some other issue, and we need to deal with that manually, right? And then it's just going to, you know, do all of the uploads and stuff as long as everything else is valid, right? So that's that for this video. In the next one, I'm going to build a CLI. So you can see over here, if I go up all the way up to the main function, you can see there's not really much interactivity in this program, right? Like it's going to upload all the folders, right, in the config, but you can't really interact with the program in any way, right? The only interaction you have with this program is in the config file where you add a bunch of folders that you want to synchronize, right? What I want to do next is I want to create some sort of CLI so that we can pass in a bunch of commands and stuff and just make this whole thing work for us, right? So that's something we're going to do in the next video. Stay tuned for that, and thank you for watching this one. Thank you for following along with the project all this time, and yeah, I'll see you in the next video.

Share this article

Hassan Aziz pointing up

Dude, you’re at the bottom of my landing page.

Just book a call already if you’re that interested.

You scrolled all the way here.