How to Authenticate with Google API Using OAuth2: Securely Send Emails via Gmail API

/blog/avatar-andrii-minchekov.png

August 26, 2024, written by

Andrii Minchekov

[object Object]

As of September 30, 2024, Google will turn off access to Less Secure Apps (LSA), including traditional user/password authentication methods for sending emails via Google SMTP servers. This shift necessitates moving towards OAuth2-based authentication, which provides a more secure and robust way to authenticate and authorize applications. This guide will walk you through the steps to authenticate to Google APIs using OAuth2, with a focus on sending an email via the Gmail API.

Why Switch from Google User/Password Authentication to OAuth2?

Google's decision to turn off access to Less Secure Apps] (LSA) means that any applications using basic authentication (username and password) will no longer be able to access Google services like Gmail. This change is designed to enhance security by preventing unauthorized access and reducing the risk of credential compromise.

OAuth2, on the other hand, is a token-based authentication protocol that provides secure delegated access. It allows applications to access user data without exposing user credentials, significantly enhancing security. OAuth2 is the recommended method for accessing Google APIs, including Gmail.

Step-by-Step Guide to Authenticating with OAuth2 and Sending Email via Gmail API

To implement OAuth2 authentication for the Gmail API to send emails, we will use a Server-to-Server approach using a Google Service Account. This method does not require user interaction, making it ideal for server-side applications.

Prerequisites

  1. A Google Cloud project with the Gmail API enabled.
  2. A service account with domain-wide delegation enabled.
  3. Admin access to your Google Workspace domain (if using domain-wide delegation).

1. Set Up a Google Cloud Project and Enable Gmail API

  1. Create a Google Cloud Project:
  2. Enable Gmail API:
    • Navigate to APIs & Services > Library.
    • Search for Gmail API and click Enable. enable-gmail-api

2. Create a Service Account and Configure Domain-Wide Delegation

  1. Create a Service Account:
    • Go to IAM & Admin > Service Accounts in the Google Cloud Console.
    • Click Create Service Account.
    • Provide a Name and Description, then click Create.
  2. Pay attention to clientId from Service Account. You need to copy it and later paste in Domain-Wide Delegation section. domain-wide-delegation-client-id
  3. Create and Download a JSON Key:
    • Click Keys > Add Key > Create New Key.
    • Choose JSON and download the file. This file contains your service account credentials, including the private key and service account email.

3. Configure Domain-Wide Delegation in Google Admin Console

  1. Go to Google Admin Console:

  2. Navigate to API Controls:

    • Go to Security > Access and Data Control > API controls.
    • Click Manage domain-wide delegation. google-api-domain-wide-delegation
  3. Add a New API Client:

    • Click Add new.
    • Enter the Client ID from your service account.
    • Add the required OAuth scopes for Gmail API access: https://www.googleapis.com/auth/gmail.send
    • Click Authorize. google-api-client

4. Write TypeScript Code to Authenticate and Send Email

For this example, we use Deno, a secure runtime for JavaScript and TypeScript, to write our server-side application.

import { create } from "https://deno.land/x/djwt@v2.8/mod.ts"; import { encodeBase64 } from "https://deno.land/std@0.203.0/encoding/base64.ts"; // Load Service Account credentials const serviceAccountKey = JSON.parse(await Deno.readTextFile("./send-email-gmail-api-oauth-e4a749c0e47b.json")); const { client_email, private_key } = serviceAccountKey; // JWT header and claims const header = { alg: "RS256", typ: "JWT" }; const iat = Math.floor(Date.now() / 1000); const exp = iat + 3600; // Token valid for 1 hour const scope = "https://www.googleapis.com/auth/gmail.send"; const userToImpersonate = "sender@example.com"; // The user to impersonate which will be the sender of email // Import the private key async function importPrivateKey(pem: string): Promise<CryptoKey> { const pemHeader = "-----BEGIN PRIVATE KEY-----"; const pemFooter = "-----END PRIVATE KEY-----\n"; const pemContents = pem.substring(pemHeader.length, pem.length - pemFooter.length); const binaryDerString = atob(pemContents); const binaryDer = Uint8Array.from(binaryDerString, c => c.charCodeAt(0)); return await crypto.subtle.importKey( "pkcs8", binaryDer.buffer, { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256", }, true, ["sign"], ); } // Generate JWT token for OAuth2 async function generateJwtToken() { const key = await importPrivateKey(private_key); const payload = { iss: client_email, scope, aud: "https://oauth2.googleapis.com/token", exp, iat, sub: userToImpersonate, // The user on behalf of whom you are acting }; const jwt = await create(header, payload, key); return jwt; } // Get OAuth2 Access Token using JWT async function getAccessToken() { const jwt = await generateJwtToken(); const response = await fetch("https://oauth2.googleapis.com/token", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer", assertion: jwt, }), }); if (!response.ok) { console.error("Failed to get access token:", await response.text()); throw new Error("Failed to get access token"); } const data = await response.json(); return data.access_token; } // Function to send an email using Gmail API async function sendEmail(accessToken: string, message: string) { const encodedMessage = encodeBase64(new TextEncoder().encode(message)) .replace(/\+/g, "-") .replace(/\//g, "_") .replace(/=+$/, ""); const response = await fetch("https://gmail.googleapis.com/gmail/v1/users/me/messages/send", { method: "POST", headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json", }, body: JSON.stringify({ raw: encodedMessage, }), }); if (!response.ok) { console.error("Failed to send email:", await response.text()); throw new Error(`Failed to send email: ${response.status}`); } console.log("Email sent successfully!"); } function createMimeMessage(from: string, to: string, subject: string, htmlBody: string): string { return [ `From: ${from}`, `To: ${to}`, `Subject: ${subject}`, `MIME-Version: 1.0`, `Content-Type: text/html; charset="UTF-8"`, `Content-Transfer-Encoding: 7bit`, ``, htmlBody ].join("\r\n"); } // Simple server to trigger email sending Deno.serve(async () => { try { const accessToken = await getAccessToken(); const from = "your-email@example.com"; // Replace with your email const to = "recipient@example.com"; const subject = "Test Email from Deno"; const htmlBody = `<p>This is a test email sent using <strong>Gmail API</strong> with OAuth2 and Deno.</p>`; const message = createMimeMessage(from, to, subject, htmlBody); await sendEmail(accessToken, message); return new Response("Email sent successfully!", { status: 200 }); } catch (error) { console.error("Error:", error); return new Response("Failed to send email", { status: 500 }); } });

5. Run the Deno Application

Run the script using Deno:

deno run --allow-net --allow-read send-email.ts

This script will:

  1. Generate a JWT token to authenticate using the service account.
  2. Send JWT token, exchanging it on access token from Google’s OAuth2 server.
  3. Send an email using the Gmail API with the obtained access token.

Conclusion

Moving from basic Google authentication (user/password) to OAuth2 is essential for security and compliance, especially with Google's deprecation of Less Secure Apps (LSA) starting September 30, 2024. Google OAuth2 offers a robust, token-based authentication mechanism that ensures secure and delegated access to Google APIs, including Gmail API.

By following the steps outlined in this guide, you can authenticate your server-side application to Google APIs using OAuth2, enabling secure operations such as sending emails via Gmail API. This approach not only complies with Google's new policies but also enhances the overall security of your application.


Gmail API Oauth2
OAuth 2.0 to access Google APIs
Google API access
Gmail API authentication
Send email via Gmail API
Google SMTP send email
Google Less Secure Apps deprecation
Google OAuth2