13 KiB
OAuth2 purpose
A way for the user to tell google to give an access token to xorismesiti.gr app
OAuth2 Standar Flow:
- User clicks button "Login with Google" on your platform
xorismesiti.gr - Authorization Request: Button redirects user to Google's authorization endpoint
accounts.google.com/o/oauth2 - User Login and Consent: User login to Google and grants permissions.
- Authorization Code Response: Google redirects user back to your app
xorismesiti.gr/callbackwith an authorizationcode. - Access Token Request: App exchanges the authorization
codefor anaccess_token. - Access Protected Resources: App uses the
access_tokento fetch the user's Google profile and email fromgoogleapis.com/oauth2 - Token Refresh (Optional): If the
access_tokenexpires, app uses therefresh tokento get a newaccess_token.
OAuth2 Frontend/Backend Flow:
Frontend
- Redirect the user to Google's OAuth authorization endpoint
accounts.google.com/o/oauth2 - Get the authorization
codeafter Google redirects back to the frontendxorismesiti.gr/callback - Send the authorization
codeto the backend fortokenexchange.
Backend
- exchange the authorization
codefor anaccess_tokenandrefresh token - fetch user profile data from from
googleapis.com/oauth2using theaccess_token - Store the
tokenssecurely in session (front) or a database (back) - Refresh the
access_tokenif it expires
1. Request 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=
Security: the state string should be validated upon receiving the response from Google, as it ensures that the response corresponds to the request.
Frontend HTTP GET Request 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.
Google HTTP Response 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 Code with Token
Frontend ⇢ Backend ⇢ Google ⇢ Backend ⇢ Frontend
2.1 Frontend
Now that the frontend has the Authorization code on th callback url https://xorismesiti.gr/api/auth/callback?code=AAAABCX4XfWgyVyziyLg0QHHHHH it can send it to the backend with POST to xorismesiti.gr/api/auth/exchange-token, in order the backend to exchange the code for an access_token and optionally an refresh_token
Frontend HTTP POST Request to Backend
POST https://xorismesiti.gr/api/auth/exchange-token
Content-Type: application/json
{
"code": "AAAABCX4XfWgyVyziyLg0QHHHHH"
}
2.2 Backend
- The backend receives the authorization
codeform frontend (Frontend POST atxorismesiti.gr/api/auth/exchange-token) - The backend POST Authorization
codeto Google API - The Google API respond to backend POST with the tokens
access_tokenandrefresh_token - The backend respond to frontend with the tokens (respond to frontend POST at
xorismesiti.gr/api/auth/exchange-token)
Security: The backend never expose the client_secret to the frontend. This step should always be handled on the backend.
Backend HTTP POST Request 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).
Google HTTP Response 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');
});
Backend HTTP Response to Frontend
{
"access_token": "ya29.a0AfH6SMC8Op6zXZkHi2XITkDoOVzYXt3hTY6sny54UlWlxrnKlX5Xv78is7BEHekVX-VoA",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "1//04d5XHqmn6Hdy3wTf5OYDP1SyBa74zEFURjddQ2A1cFw78PY13pQyWhlD2A6XhDQtKlrjAqU4kS3vGdMvckw",
"scope": "email profile"
}
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.3 Front
4. Use the Token
Frontend ⇢ Google ⇢ Frontend
- The frontend receives the tokens from the Backend response,
- Store them in the localStorage of the browser
- Make authenticated requests directly to Google API
Frontend HTTP GET Request to Backend
GET https://www.googleapis.com/oauth2/v3/userinfo
Authorization: Bearer ACCESSTOKEN6zXZkHi2XITkDoOVACCESSTOKEN
Backedn HTTP Response 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 an error response from Google when attempting to fetch user data
Frontend HTTP GET Request to Google
GET https://www.googleapis.com/oauth2/v3/userinfo
Authorization: Bearer ACCESSTOKEN6zXZkHi2XITkDoOVACCESSTOKEN
Google HTTP Response to Frontend
{
"error": "invalid_token",
"error_description": "The access token expired"
}
Frontend HTTP POST Refresh token to Backend
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