handling OAuth access tokens and scopes in your backend
OAuth access token usage, scope definition, scope validation, token storage for API calls, token expiry handling, refreshing OAuth tokens, minimal scope principle
OAuth Access Tokens and Scopes
The access token your server receives after OAuth login is for calling the provider's APIs, not for your own app's auth. Scopes define which resources that token can access on the provider's side.
Request only the scopes you need. Users see the permission request — asking for https://www.googleapis.com/auth/gmail.modify on a login button will lose trust. Request email profile openid for identity and ask for additional scopes only when the user triggers that feature.
Storing OAuth tokens to make ongoing API calls on the user's behalf:
// After OAuth callback, store tokens encrypted
const user = await User.findOrCreate({ googleId: profile.id });
await user.update({
googleAccessToken: encrypt(accessToken),
googleRefreshToken: refreshToken ? encrypt(refreshToken) : user.googleRefreshToken,
googleTokenExpiry: Date.now() + 3600 * 1000
});
// Before making an API call
async function getValidAccessToken(user) {
if (Date.now() < user.googleTokenExpiry - 60000) {
return decrypt(user.googleAccessToken);
}
// Refresh
const { tokens } = await oauth2Client.refreshAccessToken();
await user.update({ googleAccessToken: encrypt(tokens.access_token), googleTokenExpiry: tokens.expiry_date });
return tokens.access_token;
}Never store OAuth access tokens in plaintext in your database. Use symmetric encryption (AES-256-GCM) with a key stored in your secrets manager. Refresh tokens especially — they are long-lived and high-value.
