13 KiB
OAuth2 purpose
A way for the user to tell google to give an access token to xorismesiti.gr app
OAuth2 Frontend/Backend Flow:
| When | What | How |
|---|---|---|
| 1 | Get Code | Front ⇢ Google ⇢ Front |
| 2 | Exchange Code with Token | Front ⇢ Back ⇢ Google ⇢ Back ⇢ Front |
| 4 | Use Token | Front ⇢ Google ⇢ Front |
OAuth2 Frontend/Backend Flow Details:
-
Get Code
- Frontend Redirect the user to Google's OAuth authorization endpoint
accounts.google.com/o/oauth2 - User Login in to Google and grant permissions
- Google Redirect the user back to
xorismesiti.gr/callbackincluding the authorizationcode
- Frontend Redirect the user to Google's OAuth authorization endpoint
-
Exchange Code with Token
- Frontend Send the authorization
codeto the Backend - Backend Exchange the authorization
codefor anaccess_tokenandrefresh token - Backend Send
access_tokenandrefresh tokento Frontend
- Frontend Send the authorization
-
Use Token
- Frontend Use
access_tokento get user data from Google
- Frontend Use
1. Get Authorization code
Frontend ⇢ Google ⇢ Frontend
- A button "Login with Google" redirects the user to the Google's authorization endpoint
accounts.google.com/o/oauth2/v2/auth - After the redirection, the user will log in to Google and grant permissions (if they haven’t already).
- Google will redirect the user back to your redirect_uri
https://xorismesiti.gr/callbackwith an authorization code?code=ABC123
Security: the state string should be validated upon receiving the response from Google, as it ensures that the response corresponds to the request.
HTTP GET Request from Frontend to Google
GET https://accounts.google.com/o/oauth2/v2/auth?
response_type=code&
client_id=ABC34JHS9D&
redirect_uri=https://xorismesiti.gr/callback&
scope=email%20profile&
state=xyz123
response_type=code: This indicates you're using the "authorization code" flow.client_id: Your Google API client ID. Created by the developers of thexorismesiti.gron the google API console.redirect_uri: The URI Google will redirect to after the user consents.scope: The permissions you're requesting (e.g., email, profile).state: A random string to protect against CSRF attacks.
HTTP GET Response from Google to Frontend
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
Frontend Code
// Redirect to Google's OAuth 2.0 endpoint when user clicks login
const loginWithGoogle = () => {
const clientId = 'ABC34JHS9D'; // Replace with your actual Google Client ID
const redirectUri = 'https://xorismesiti.gr/api/auth/callback'; // Backend URL where Google will send the user after login
const scope = 'email profile'; // Scopes you're requesting (email, profile, etc.)
const state = 'random-state-value'; // For CSRF protection
const googleAuthUrl = `https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=${clientId}&redirect_uri=${redirectUri}&scope=${scope}&state=${state}`;
window.location.href = googleAuthUrl; // Redirect user to Google
};
2. Exchange Authorization Code with Tokens
Frontend ⇢ Backend ⇢ Google ⇢ Backend ⇢ Frontend
2.1 Frontend
Now that the frontend has the Authorization code on the callback url, it can send it to the backend
HTTP POST Request from Frontend to Backend
POST https://xorismesiti.gr/api/auth/exchange-token
Content-Type: application/json
{
"code": "AAAABCX4XfWgyVyziyLg0QHHHHH"
}
Frontend Code
// On the backend callback URL, the frontend receives the authorization code
import { useEffect } from 'react';
import { useRouter } from 'next/router';
const Callback = () => {
const router = useRouter();
useEffect(() => {
const { code, state } = router.query;
// Send the authorization code to the backend for token exchange
fetch('/api/auth/exchange-token', {
method: 'POST',
body: JSON.stringify({ code }),
headers: {
'Content-Type': 'application/json',
},
})
.then(response => response.json())
.then(data => {
// Handle success (store token, update UI, etc.)
console.log(data); // Typically, you'll store the access token here or manage the user session.
router.push('/dashboard'); // Redirect the user to their dashboard or home page.
})
.catch(error => {
console.error('Error exchanging token:', error);
});
}, []);
return <div>Loading...</div>;
};
export default Callback;
2.2 Backend
- The Backend receives the authorization
codeform the Frontend POST atxorismesiti.gr/api/auth/exchange-token - The Backend POST the Authorization
codeto Google API - The Google response to Backend POST with the
token - The Backend response to Frontend POST with the the
token
Security: The backend never expose the client_secret to the frontend. This step should always be handled on the backend.
HTTP POST Request from Backend to Google
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
HTTP Method: POSTURL: https://oauth2.googleapis.com/token- Headers
Content-Type: application/x-www-form-urlencoded
- Body
grant_type=authorization_code: This specifies the grant type.code: The authorization code you received in the previous step.redirect_uri: The same redirect URI used in the authorization request.client_id: Your Google API client ID.client_secret: Your Google API client secret (which should be kept secure).
HTTP POST Response from Google to Backend
{
"access_token": "ACCESSTOKEN.6zXZkHi2XITkDoOV.ACCESSTOKEN",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "1//04d5XHqmn6Hdy3wTf5OYDP1SyBa74zEFURjddQ2A1cFw78PY13pQyWhlD2A6XhDQtKlrjAqU4kS3vGdMvckw",
"scope": "email profile"
}
Backend Code:
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
// Handle token exchange
app.post('/api/auth/exchange-token', async (req, res) => {
const { code } = req.body;
try {
const response = await axios.post('https://oauth2.googleapis.com/token', null, {
params: {
code,
client_id: clientId,
client_secret: clientSecret,
redirect_uri: redirectUri,
grant_type: 'authorization_code',
},
});
const { access_token, refresh_token, expires_in } = response.data;
// Optionally, store the tokens in a secure location (e.g., session, database)
// For now, send them back to the frontend (not recommended for production)
res.json({ access_token, refresh_token, expires_in });
} catch (error) {
console.error('Error exchanging authorization code for token:', error);
res.status(500).json({ error: 'Failed to exchange authorization code for access token' });
}
});
// Start the server
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});
2.3 Frontend
The frontend gets the tokens from the backend response,
and saves them in a cookie marked as HTTP-only and Secure
Now its ready to use the tokens to get the user data from Google
HTTP Response from Backend to Frontend
{
"access_token": "ya29.a0AfH6SMC8Op6zXZkHi2XITkDoOVzYXt3hTY6sny54UlWlxrnKlX5Xv78is7BEHekVX-VoA",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "1//04d5XHqmn6Hdy3wTf5OYDP1SyBa74zEFURjddQ2A1cFw78PY13pQyWhlD2A6XhDQtKlrjAqU4kS3vGdMvckw",
"scope": "email profile"
}
Frontend Code:
// Set an HTTP-only, Secure cookie on the backend
res.cookie('access_token', accessToken, {
httpOnly: true,
secure: true,
maxAge: 3600000 // 1 hour expiry
});
4. Use the Token
Frontend ⇢ Google ⇢ Frontend
Make authenticated requests directly to Google API using the token stored in a secure cookie
HTTP GET Request from Frontend to Google
GET https://www.googleapis.com/oauth2/v3/userinfo
Authorization: Bearer ACCESSTOKEN6zXZkHi2XITkDoOVACCESSTOKEN
HTTP GET Response from Google to Frontend
{
"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
const fetchUserData = async (accessToken) => {
try {
const response = await fetch('https://www.googleapis.com/oauth2/v3/userinfo', {
method: 'GET',
headers: {
'Authorization': `Bearer ${accessToken}`,
},
});
if (!response.ok) {
throw new Error('Failed to fetch user data from Google');
}
// Parse the response to get the user data
const userData = await response.json();
console.log('User Data:', userData);
// Example user data:
// {
// "sub": "1234567890123456789012345678901234567890",
// "name": "John Doe",
// "given_name": "John",
// "family_name": "Doe",
// "email": "john.doe@example.com",
// "picture": "https://example.com/profile.jpg"
// }
} catch (error) {
console.error('Error fetching user data:', error);
}
};
5. Refresh the Token
Frontend ⇢ Backend ⇢ Google ⇢ Backend ⇢ Frontend
If the access token is expired, the frontend will receive a response error from Google when attempting to fetch user data
HTTP GET Request from Frontend to Google (with expires token)
GET https://www.googleapis.com/oauth2/v3/userinfo
Authorization: Bearer ACCESSTOKEN6zXZkHi2XITkDoOVACCESSTOKEN
HTTP GET Response from Google to Frontend (for expired token)
{
"error": "invalid_token",
"error_description": "The access token expired"
}
HTTP POST Request from Frontend to Backend (with refresh token)
POST /api/refresh-token HTTP/1.1
Host: your-backend-domain.com
Content-Type: application/json
Authorization: Bearer <access_token> (optional, depends on the backend)
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36
Accept: application/json
Origin: https://your-frontend-domain.com
Connection: keep-alive
Content-Length: 57
{
"refresh_token": "REFRESHTOKEN6zXZkHi2XITkDoOVREFRESHTOKEN"
}
HTTP POST Response from Backend to Frontend (with new access token)
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 123
Cache-Control: no-store
Pragma: no-cache
{
"access_token": "new-access-token-here",
"expires_in": 3600,
"token_type": "bearer"
}
Frontend Code
const refreshAccessToken = async (refreshToken) => {
try {
const response = await fetch('/api/refresh-token', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ refresh_token: refreshToken }),
});
if (!response.ok) {
throw new Error('Failed to refresh access token');
}
const data = await response.json();
return data.access_token; // New access token
} catch (error) {
console.error('Error refreshing access token:', error);
}
};
Frontend Code
const response = await fetch('https://oauth2.googleapis.com/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
client_id: 'YOUR_GOOGLE_CLIENT_ID',
client_secret: 'YOUR_GOOGLE_CLIENT_SECRET',
refresh_token: refreshToken,
grant_type: 'refresh_token',
}),
});
const data = await response.json();
const newAccessToken = data.access_token; // New access token