diff --git a/OAuth2-Front-Approach.md b/OAuth2-Front-Approach.md new file mode 100644 index 0000000..974da73 --- /dev/null +++ b/OAuth2-Front-Approach.md @@ -0,0 +1,373 @@ +### OAuth2 purpose + +A way for the `user` to tell `google` to give an access token to `xorismesiti.gr` app + +


+ +### OAuth2 Standar Flow: + +1. **User** clicks button "Login with Google" on your platform `xorismesiti.gr` +2. **Authorization Request**: Button redirects user to Google's authorization endpoint `accounts.google.com/o/oauth2` +3. **User Login and Consent**: User login to Google and grants permissions. +4. **Authorization Code Response**: Google redirects user back to your app `xorismesiti.gr/callback` with an authorization `code`. +5. **Access Token Request**: App exchanges the authorization `code` for an `access_token`. +6. **Access Protected Resources**: App uses the `access_token` to fetch the user's Google profile and email from `googleapis.com/oauth2` +7. **Token Refresh** (Optional): If the `access_token` expires, app uses the `refresh token` to get a new `access_token`. + +


+ +### OAuth2 Frontend/Backend Flow: + +**Frontend** +1. **Redirect** the user to Google's OAuth authorization endpoint `accounts.google.com/o/oauth2` +2. **Get** the authorization `code` after Google redirects back to the frontend `xorismesiti.gr/callback` +3. **Send** the authorization `code` to the backend for `token` exchange. + +**Backend** +1. **exchange** the authorization `code` for an `access_token` and `refresh token` +2. **fetch** user profile data from from `googleapis.com/oauth2` using the `access_token` +3. **Store** the `tokens` securely in session (front) or a database (back) +4. **Refresh** the `access_token` if it expires + +


+ + + + + +# 1. [Frontend] Request Authorization code + +1. A button "Login with Google" redirects the user to the Google's authorization endpoint `accounts.google.com/o/oauth2/v2/auth` +2. After the redirection, the user will log in to Google and grant permissions (if they haven’t already). +3. Google will redirect the user back to your redirect_uri `https://xorismesiti.gr/callback` with 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

+ +```sh +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 the `xorismesiti.gr` on 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

+ +```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 +``` + +
+ +
+

Frontend Code

+ +```js +// 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. [Frontend] Receive Authorization Code + +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

+ +```bash +POST https://xorismesiti.gr/api/auth/exchange-token +Content-Type: application/json + +{ + "code": "AAAABCX4XfWgyVyziyLg0QHHHHH" +} +``` + +
+ +#### Step 3 takes place here, Backend exchanges the Code with the Tokens + +
+

Backend HTTP Response to Frontend

+ +```json +{ + "access_token": "ya29.a0AfH6SMC8Op6zXZkHi2XITkDoOVzYXt3hTY6sny54UlWlxrnKlX5Xv78is7BEHekVX-VoA", + "token_type": "Bearer", + "expires_in": 3600, + "refresh_token": "1//04d5XHqmn6Hdy3wTf5OYDP1SyBa74zEFURjddQ2A1cFw78PY13pQyWhlD2A6XhDQtKlrjAqU4kS3vGdMvckw", + "scope": "email profile" +} +``` + +
+ +
+

Frontend Code

+ +```js +// 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
Loading...
; +}; + +export default Callback; +``` + +
+ +


+ + + + + +# 3. [Backend] Exchange Code with Token + +1. The backend **receives** the authorization `code` form frontend (Frontend POST at `xorismesiti.gr/api/auth/exchange-token`) +2. The backend **POST** Authorization `code` to Google API +3. The Google API respond to backend POST with the tokens `access_token` and `refresh_token` +4. 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

+ +```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 +``` + +- `HTTP Method`: POST +- `URL`: 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

+ +```json +{ + "access_token": "ACCESSTOKEN.6zXZkHi2XITkDoOV.ACCESSTOKEN", + "token_type": "Bearer", + "expires_in": 3600, + "refresh_token": "1//04d5XHqmn6Hdy3wTf5OYDP1SyBa74zEFURjddQ2A1cFw78PY13pQyWhlD2A6XhDQtKlrjAqU4kS3vGdMvckw", + "scope": "email profile" +} +``` + +
+ +
+

Backend 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 + +// 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'); +}); +``` + +
+ +


+ + + + + +# 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://www.googleapis.com/oauth2/v3/userinfo +Authorization: Bearer ACCESSTOKEN6zXZkHi2XITkDoOVACCESSTOKEN +``` + +
+ +
+

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 +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); + } +}; +``` + +
+ +


+ + + + + + + + + +