### OAuth2 purpose A way for the `user` to tell `google` to give an access to `xorismesiti.gr` app
### OAuth2 Frontend/Backend Flow: | When | What | How | |-------|--------------------------|--------------------------------------------------| | 1 | Get Code | ` Front ⇢ Google ⇢ Back `| | 2 | Exchange Code with Token | ` Back ⇢ Google ⇢ Back ⇢ Front `| | 4 | Use Token | ` Front ⇢ Back ⇢ Google ⇢ Back ⇢ Front `|
### OAuth2 Frontend/Backend Flow Details: 1. Get Code 1. Front **GET** to Google `https://accounts.google.com/o/oauth2` with callback url 2. Google **302** to Back `https://xorismesiti.gr/api/auth/callback` with authorization code 2. Exchange Code with Token 1. Back **POST** the `code` to Google `https://oauth2.googleapis.com/token` 2. Google **response** to Back with an `access_token` and a `refresh token` 3. Back **response** to Front with the `access_token` in a `cookie` 3. Use Token 1. Front **GET** profile data from Back `https://xorismesiti.gr/api/auth/profile` using the `cookie` 2. Back **GET** profile data from Google `https://www.googleapis.com/oauth2/v3/userinfo` using the `access_token` from Front `cookie` 3. Google **response** to Back with profile data 4. Back **response** to Front with profile data

# 1. Get Code 1. Front **GET** to Google `https://accounts.google.com/o/oauth2` with callback url 2. Google **302** to Back `https://xorismesiti.gr/api/auth/callback` with authorization code ### Front **GET** to Google ```sh GET https://accounts.google.com/o/oauth2/v2/auth? response_type=code& # This indicates you're using the "authorization code" flow. client_id=ABC34JHS9D& # Your Google API client ID created on the google API console. redirect_uri=https://xorismesiti.gr/api/auth/callback& # The URI Google will redirect to after the user consents. scope=email%20profile& # The permissions you're requesting (e.g., email, profile). state=xyz123 # A random string to protect against CSRF attacks. ``` ### Google **302** to Back ```bash HTTP/1.1 302 Found Location: https://xorismesiti.gr/api/auth/callback?code=4/0AX4XfWgyVyz-uT8k7WiyLg0Q&state=xyz123 Content-Type: text/html; charset=UTF-8 Content-Length: 0 ``` *Security: the state string should be validated upon receiving the response from Google, as it ensures that the response corresponds to the request.* # 2. Exchange Code with Token 1. Back **POST** the `code` to Google `https://oauth2.googleapis.com/token` 2. Google **response** to Back with an `access_token` and a `refresh token` 3. Back **response** to Front with the `access_token` in a `cookie` ### 1. Back **POST** the `code` to Google The Backend **POST** to Google ```sh POST https://oauth2.googleapis.com/token Content-Type: application/x-www-form-urlencoded grant_type=authorization_code& code=AAAABCX4XfWgyVyziyLg0QHHHHH& redirect_uri=https://xorismesiti.gr/callback& client_id=ABC34JHS9D& client_secret=PASS1234 ``` The Google **response** ```json { "access_token": "ya29.a0AfH6SMC8Op6zXZkHi2XITkDoOVzYXt3hTY6sny54UlWlxrnKlX5Xv78is7BEHekVX-VoA", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "1//04d5XHqmn6Hdy3wTf5OYDP1SyBa74zEFURjddQ2A1cFw78PY13pQyWhlD2A6XhDQtKlrjAqU4kS3vGdMvckw", "scope": "email profile" } ``` The Backend Code ```js // Backend callback URL: http://localhost:3000/auth/google/callback app.get('/auth/google/callback', async (req, res) => { const { code } = req.query; // Extract the code from the query string try { // Exchange the authorization code for access token and refresh token const response = await axios.post('https://oauth2.googleapis.com/token', null, { params: { code, client_id: clientId, client_secret: clientSecret, redirect_uri: redirectUri, // Same as in Step 1 grant_type: 'authorization_code', }, }); const { access_token, refresh_token, expires_in } = response.data; // Store tokens in session or cookies res.cookie('access_token', access_token, { httpOnly: true, secure: true, maxAge: expires_in * 1000 }); res.cookie('refresh_token', refresh_token, { httpOnly: true, secure: true }); // Respond to frontend or redirect as needed res.send('Authentication successful! You can now use the app.'); } catch (error) { console.error('Error exchanging code for tokens:', error); res.status(500).send('Error during authentication'); } }); ``` # 3. Use Token ```js import React, { useState, useEffect } from 'react'; import axios from 'axios'; const App = () => { const [user, setUser] = useState(null); const [isAuthenticated, setIsAuthenticated] = useState(false); // Fetch user data from the backend's /profile endpoint const fetchUserData = async () => { try { const response = await axios.get('http://localhost:3000/profile', { withCredentials: true, // Important: Ensure cookies are sent with the request }); setUser(response.data); // Set the user data from the response setIsAuthenticated(true); } catch (error) { console.error('Error fetching user profile:', error); } }; useEffect(() => { // Check if the user is authenticated by checking the cookie if (document.cookie.includes('access_token')) { fetchUserData(); // Fetch user profile if the access token is available in cookies } }, []); return (

Google OAuth2 with React

{!isAuthenticated ? (

Please log in with Google to access your profile:

) : (

Welcome, {user?.name}

Email: {user?.email}

Profile
)}
); }; export default App; ``` ------------------------- # 4. [Frontend] Use the Token 1. The frontend receives the tokens from the Backend response, 2. Store them in the localStorage of the browser 3. Make authenticated requests directly to Google API

Frontend HTTP GET Request to Backend

```bash GET https://xorismesiti.gr/api/user-profile Authorization: Bearer access-token-from-backend ```
**↴** Step 5 takes place here: The Backend request the user data from Google using the Tokens

Backedn HTTP Response to Frontend

```json { "sub": "1234567890", "name": "John Doe", "given_name": "John", "family_name": "Doe", "profile": "https://plus.google.com/1234567890", "picture": "https://lh3.googleusercontent.com/a-/AOh14GgIXXl5JXzW0c1Szbl-e1Jch1vhl5rHhH65vlK6J5g5PqkGjj1O0p3t8bgVEOykQ6ykFSQ=s96", "email": "john.doe@example.com", "email_verified": true, "locale": "en" } ```

Frontend Code

```js // Store tokens and expiration time after receiving them from the backend const storeTokens = (access_token, refresh_token, expires_in) => { const expirationTime = Date.now() + expires_in * 1000; // expires_in is in seconds, so multiply by 1000 to get ms localStorage.setItem('access_token', access_token); localStorage.setItem('refresh_token', refresh_token); localStorage.setItem('token_expiration', expirationTime); }; // Function to check if the access token has expired const isTokenExpired = () => { const expirationTime = localStorage.getItem('token_expiration'); return Date.now() > expirationTime; }; // Function to refresh the access token using the refresh token const refreshAccessToken = async () => { const refresh_token = localStorage.getItem('refresh_token'); try { const response = await fetch('/api/auth/refresh-token', { method: 'POST', body: JSON.stringify({ refresh_token }), headers: { 'Content-Type': 'application/json', }, }); const data = await response.json(); if (data.access_token) { // Store the new access token and expiration time storeTokens(data.access_token, data.refresh_token, data.expires_in); return data.access_token; } } catch (error) { console.error('Error refreshing token:', error); return null; // Handle error appropriately (e.g., logout user or prompt to log in again) } }; // Function to get the access token, either from localStorage or by refreshing it if expired const getAccessToken = async () => { if (isTokenExpired()) { console.log('Access token expired. Refreshing...'); const newAccessToken = await refreshAccessToken(); return newAccessToken; } else { return localStorage.getItem('access_token'); } }; // Example usage: Get the access token and use it for an authenticated API request const fetchUserProfile = async () => { const access_token = await getAccessToken(); if (!access_token) { console.error('No valid access token found. User might need to log in.'); return; } // Now you can use the access token to make an authenticated API request try { const response = await fetch('/api/user-profile', { headers: { 'Authorization': `Bearer ${access_token}`, }, }); const userData = await response.json(); console.log(userData); } catch (error) { console.error('Error fetching user profile:', error); } }; // Example call to fetch user profile fetchUserProfile(); ```



# 5. [Backend] Fetch User Data With the access token obtained in the previous step, your platform can now use it to fetch the user's Google profile and email information. The token is included in the Authorization header of the request.

HTTP GET Request

```sh GET https://www.googleapis.com/oauth2/v3/userinfo Authorization: Bearer ya29.a0AfH6SMC8Op6zXZkHi2XITkDoOVzYXt3hTY6sny54UlWlxrnKlX5Xv78is7BEHekVX-VoA ``` - URL: https://www.googleapis.com/oauth2/v3/userinfo - HTTP Method: GET - Headers: - Authorization: Bearer {access_token}: The access token obtained in step 5.

HTTP Response

```json { "sub": "1234567890", "name": "John Doe", "given_name": "John", "family_name": "Doe", "profile": "https://plus.google.com/1234567890", "picture": "https://lh3.googleusercontent.com/a-/AOh14GgIXXl5JXzW0c1Szbl-e1Jch1vhl5rHhH65vlK6J5g5PqkGjj1O0p3t8bgVEOykQ6ykFSQ=s96", "email": "john.doe@example.com", "email_verified": true, "locale": "en" } ```

Backend GET Code

```js const express = require('express'); const axios = require('axios'); const app = express(); app.use(express.json()); // To parse JSON bodies const clientId = 'YOUR_GOOGLE_CLIENT_ID'; // Google Client ID const clientSecret = 'YOUR_GOOGLE_CLIENT_SECRET'; // Google Client Secret const redirectUri = 'https://xorismesiti.gr/api/auth/callback'; // Must match the one used in frontend // Function to validate the access token by making a request to the Google user info endpoint const validateAccessToken = async (accessToken) => { try { const response = await axios.get('https://www.googleapis.com/oauth2/v3/userinfo', { headers: { Authorization: `Bearer ${accessToken}`, }, }); // If this succeeds, the token is valid return response.data; // Return the user data } catch (error) { // If the request fails (token is invalid or expired), throw an error throw new Error('Invalid or expired access token'); } }; // Example route for fetching the user profile app.get('/api/user-profile', async (req, res) => { const accessToken = req.headers['authorization']?.split(' ')[1]; // Extract token from Authorization header if (!accessToken) { return res.status(400).json({ error: 'No access token provided' }); } try { // Validate the access token by fetching user info from Google const userData = await validateAccessToken(accessToken); // Send the user profile data to the frontend res.json(userData); } catch (error) { // Handle invalid or expired token console.error('Error validating access token:', error); res.status(401).json({ error: 'Invalid or expired access token' }); } }); // Start the server app.listen(3000, () => { console.log('Server running on http://localhost:3000'); }); ```

Front GET Code

```js // After receiving the token, store it in the frontend (e.g., localStorage or context) localStorage.setItem('access_token', response.access_token); // Use it to make authenticated API requests to the backend fetch('/api/user-profile', { headers: { 'Authorization': `Bearer ${localStorage.getItem('access_token')}`, }, }) .then(response => { if (response.status === 401) { // Handle token expiration or invalid token console.error('Access token expired or invalid. Please log in again.'); // Optionally, redirect to login page or refresh token } else { return response.json(); } }) .then(data => { // Handle user data console.log(data); }) .catch(error => { console.error('Error fetching user profile:', error); }); ```



# 6. [Backend] Token Expiry and Refresh (Optional) If the access token expires, your platform can use the refresh token (if provided) to obtain a new access token without requiring the user to log in again.

HTTP POST Request

```bash POST https://oauth2.googleapis.com/token Content-Type: application/x-www-form-urlencoded grant_type=refresh_token& refresh_token=refresh-token-from-backend& client_id=YOUR_GOOGLE_CLIENT_ID& client_secret=YOUR_GOOGLE_CLIENT_SECRET ``` - `URL`: https://oauth2.googleapis.com/token - `HTTP` Method: POST - `Headers`: - `Content`-Type: application/x-www-form-urlencoded - `Body` Parameters: - `grant_type`=refresh_token: This indicates the refresh token flow. - `refresh_token`: The refresh token obtained in step 5. - `client_id`: Your Google API client ID. - `client_secret`: Your Google API client secret.

HTTP Response

```json { "access_token": "ya29.a0AfH6SMC8Op6zXZkHi2XITkDoOVzYXt3hTY6sny54UlWlxrnKlX5Xv78is7BEHekVX-VoA", "token_type": "Bearer", "expires_in": 3600 } ```

Example Backend Code:

```js app.post('/api/auth/refresh-token', async (req, res) => { const { refresh_token } = req.body; try { const response = await axios.post('https://oauth2.googleapis.com/token', null, { params: { refresh_token, client_id: clientId, client_secret: clientSecret, grant_type: 'refresh_token', }, }); res.json(response.data); // Return new access token and refresh token } catch (error) { console.error('Error refreshing access token:', error); res.status(500).json({ error: 'Failed to refresh access token' }); } }); ```