14 KiB
Google OAuth 2.1 Integration
Overview
This document describes the integration of Google OAuth 2.1 for user authentication within the application. This feature allows users to sign in using their Google accounts, providing a seamless and secure authentication experience. The process involves initiating an OAuth flow with Google, handling the callback, exchanging authorization codes for tokens, and ultimately generating a JSON Web Token (JWT) for the authenticated user.
Configuration
To enable Google OAuth 2.1 authentication, the following environment variables must be configured. These variables are loaded into the central Env struct in imphnen-libs.
GOOGLE_CLIENT_ID: Your Google OAuth 2.1 Client ID.GOOGLE_CLIENT_SECRET: Your Google OAuth 2.1 Client Secret.GOOGLE_REDIRECT_URL: The URL to which Google will redirect the user after successful authentication. This must match one of the authorized redirect URIs configured in your Google Cloud Console (e.g.,http://127.0.0.1:8080/api/v1/auth/google/callback).
Obtaining Credentials from Google Cloud Console
- Navigate to Google Cloud Console: Go to the Google Cloud Console.
- Select/Create a Project: Choose an existing project or create a new one.
- Enable Google People API: In the navigation menu, go to
APIs & Services>Libraryand search for "Google People API" and enable it. - Create OAuth Consent Screen: Go to
APIs & Services>OAuth consent screen.- Configure your consent screen, including application name, user support email, and developer contact information.
- Create Credentials: Go to
APIs & Services>Credentials.- Click
Create Credentials>OAuth client ID. - Select "Web application" as the application type.
- Provide a name for your OAuth 2.0 client.
- Under
Authorized redirect URIs, add theGOOGLE_REDIRECT_URLspecified in your environment variables (e.g.,http://127.0.0.1:8080/api/v1/auth/google/callback). - Click "Create". Your Client ID and Client Secret will be displayed. Copy these values and set them as
GOOGLE_CLIENT_IDandGOOGLE_CLIENT_SECRETin your environment.
- Click
API Endpoints
1. Initiate Google OAuth Flow
-
Endpoint:
/api/v1/auth/google/login -
Method:
GET -
Description: This endpoint initiates the Google OAuth 2.1 authentication flow. When accessed, it generates a Google authorization URL and redirects the user's browser to Google's authentication page. The user will be prompted to grant permissions to your application.
-
Example
curlcommand:curl -v http://127.0.0.1:8080/api/v1/auth/google/loginUpon successful execution, this command will return a
302 Foundstatus with aLocationheader containing the Google authorization URL. Your browser would typically follow this redirect.
2. Handle Google OAuth Callback
-
Endpoint:
/api/v1/auth/google/callback -
Method:
GET -
Description: This endpoint handles the redirect from Google after the user has authenticated and granted permissions. Google sends an authorization
codeand astateparameter to this URL. The application then uses thiscodeto exchange it for an access token and user information with Google. Upon successful validation and user creation/login, a JSON response is returned containing authentication tokens and user details, identical to the credential-based login endpoint. -
Query Parameters:
-
code(required): The authorization code provided by Google. -
state(required): The CSRF token generated during the login initiation. -
Response: Returns a JSON object containing authentication tokens and user information.
-
Example
curlcommand (conceptual, ascodeandstateare dynamic):# This curl command is illustrative. The `code` and `state` values are obtained dynamically # from Google's redirect after the user authorizes your application. # Replace <AUTHORIZATION_CODE> and <CSRF_STATE> with actual values from the Google redirect. curl -v "http://127.0.0.1:8080/api/v1/auth/google/callback?code=<AUTHORIZATION_CODE>&state=<CSRF_STATE>" -
Success Response (200 OK):
{ "token": { "access_token": "your_access_token_here", "refresh_token": "your_refresh_token_here" }, "user": { "id": "user_id_here", "role": { "id": "role_id_here", "name": "Role Name", "permissions": [], "created_at": "2023-01-01T12:00:00Z", "updated_at": "2023-01-01T12:00:00Z" }, "fullname": "User Fullname", "email": "user@example.com", "avatar": "http://example.com/avatar.jpg", "phone_number": "1234567890", "is_active": true, "gender": "Male", "birthdate": "2000-01-01T00:00:00Z", "created_at": "2023-01-01T12:00:00Z", "updated_at": "2023-01-01T12:00:00Z" } }
React Frontend Implementation
This section outlines how to integrate the Google OAuth 2.1 flow into a React application using the JSON API response approach.
1. Initiating the Login Flow
Users will click a button or link to initiate the Google OAuth process. You can implement this using either a popup window or a full page redirect approach.
Approach 1: Popup Window (Recommended)
// Example React Component for Google Login with Popup
import React, { useState } from 'react';
function GoogleLoginButton() {
const [isLoading, setIsLoading] = useState(false);
const handleLogin = () => {
setIsLoading(true);
// Open Google OAuth in popup window
const popup = window.open(
'http://127.0.0.1:8080/api/v1/auth/google/login',
'googleOAuth',
'width=500,height=600,scrollbars=yes,resizable=yes'
);
// Check if popup is closed manually
const checkClosed = setInterval(() => {
if (popup.closed) {
clearInterval(checkClosed);
setIsLoading(false);
console.log('OAuth popup was closed without completion');
}
}, 1000);
// Listen for the popup to navigate to the callback URL
const checkCallback = setInterval(async () => {
try {
if (popup.location.href.includes('/api/v1/auth/google/callback')) {
clearInterval(checkCallback);
clearInterval(checkClosed);
// Wait a moment for the request to complete, then get the response
setTimeout(async () => {
try {
// The popup now contains the JSON response from the callback
const response = await fetch(popup.location.href);
const data = await response.json();
if (response.ok) {
// Store tokens securely
localStorage.setItem('accessToken', data.token.access_token);
localStorage.setItem('refreshToken', data.token.refresh_token);
localStorage.setItem('user', JSON.stringify(data.user));
popup.close();
setIsLoading(false);
// Redirect to dashboard or update app state
window.location.href = '/dashboard';
} else {
throw new Error('Authentication failed');
}
} catch (error) {
console.error('OAuth callback error:', error);
popup.close();
setIsLoading(false);
alert('Google login failed. Please try again.');
}
}, 1000);
}
} catch (error) {
// Cross-origin error is expected until callback URL is reached
}
}, 1000);
};
return (
<button onClick={handleLogin} disabled={isLoading}>
{isLoading ? 'Logging in...' : 'Login with Google'}
</button>
);
}
export default GoogleLoginButton;
Approach 2: Full Page Redirect
// Example React Component for Google Login with Full Redirect
import React from 'react';
function GoogleLoginButton() {
const handleLogin = () => {
// Store current location to redirect back after auth
localStorage.setItem('preAuthLocation', window.location.pathname);
// Redirect to backend's Google OAuth login endpoint
window.location.href = 'http://127.0.0.1:8080/api/v1/auth/google/login';
};
return (
<button onClick={handleLogin}>
Login with Google
</button>
);
}
export default GoogleLoginButton;
2. Handling the OAuth Callback (For Full Redirect Approach)
If using the full page redirect approach, you'll need a callback component to handle the OAuth response.
// Example React Component for Google OAuth Callback Handler
import React, { useEffect, useState } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
function GoogleAuthCallback() {
const location = useLocation();
const navigate = useNavigate();
const [isProcessing, setIsProcessing] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const handleCallback = async () => {
try {
// Extract query parameters from current URL
const params = new URLSearchParams(location.search);
const code = params.get('code');
const state = params.get('state');
if (!code || !state) {
throw new Error('Missing required OAuth parameters');
}
// Make request to your backend callback endpoint
const response = await fetch(
`http://127.0.0.1:8080/api/v1/auth/google/callback${location.search}`,
{
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
}
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
// Store authentication data
localStorage.setItem('accessToken', data.token.access_token);
localStorage.setItem('refreshToken', data.token.refresh_token);
localStorage.setItem('user', JSON.stringify(data.user));
// Redirect to intended location or dashboard
const preAuthLocation = localStorage.getItem('preAuthLocation') || '/dashboard';
localStorage.removeItem('preAuthLocation');
navigate(preAuthLocation, { replace: true });
} catch (error) {
console.error('Google OAuth callback error:', error);
setError(error.message);
setIsProcessing(false);
}
};
handleCallback();
}, [location, navigate]);
if (error) {
return (
<div>
<h2>Authentication Failed</h2>
<p>Error: {error}</p>
<button onClick={() => navigate('/login')}>
Back to Login
</button>
</div>
);
}
return (
<div>
<h2>Processing Google Login...</h2>
<p>Please wait while we complete your authentication...</p>
</div>
);
}
export default GoogleAuthCallback;
3. Example React Router Setup
Ensure your React application's routing is set up to handle the callback URL (only needed if using the full redirect approach).
// Example App.js or main router file
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import GoogleLoginButton from './components/GoogleLoginButton';
import GoogleAuthCallback from './components/GoogleAuthCallback';
import Dashboard from './components/Dashboard';
import LoginPage from './components/LoginPage';
function App() {
return (
<Router>
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route path="/auth/google/callback" element={<GoogleAuthCallback />} />
<Route path="/dashboard" element={<Dashboard />} />
{/* Other routes */}
</Routes>
</Router>
);
}
export default App;
4. Using the Authentication Tokens
Once you have the tokens, you can use them to make authenticated requests to your API:
// Example of making authenticated API requests
const makeAuthenticatedRequest = async (url, options = {}) => {
const accessToken = localStorage.getItem('accessToken');
const response = await fetch(url, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
});
if (response.status === 401) {
// Token might be expired, try to refresh or redirect to login
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
localStorage.removeItem('user');
window.location.href = '/login';
return;
}
return response;
};
OAuth Flow Diagram
graph TD
A[User] --> B{Click Google Login Button}
B --> C[Frontend opens popup/redirects to /api/v1/auth/google/login]
C --> D[Backend redirects to Google Auth]
D --> E[User authenticates with Google]
E --> F[Google redirects to /api/v1/auth/google/callback]
F --> G[Backend exchanges code for Google token]
G --> H[Backend fetches user info from Google]
H --> I{User exists in database?}
I -- Yes --> J[Get existing user]
I -- No --> K[Create new user with Google info]
J --> L[Generate JWT access and refresh tokens]
K --> L
L --> M[Return JSON response with tokens and user data]
M --> N[Frontend stores tokens securely]
N --> O[Redirect user to dashboard/protected area]
Security Considerations
-
Token Storage: Store tokens securely in httpOnly cookies or secure storage mechanisms rather than localStorage in production.
-
HTTPS Only: Always use HTTPS in production to protect tokens in transit.
-
Token Expiration: Implement proper token refresh logic when access tokens expire.
-
CORS Configuration: Ensure your backend has proper CORS configuration for the frontend domain.
-
State Validation: The backend validates the CSRF state parameter to prevent CSRF attacks.
-
Scope Limitation: Only request necessary OAuth scopes from Google (email and profile in this case).