dev/OAuth2.md
2024-12-15 13:07:06 +02:00

14 KiB
Raw Blame History

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. The use clicks a "Login with Google" button with a URL to Google's OAuth 2.0 authorization endpoint and redirects the user there.
  2. After this redirection, the user will log in to Google and grant permissions (if they havent already).
  3. Google will redirect the user back to your specified redirect_uri with an authorization code.

HTTP Request

GET https://accounts.google.com/o/oauth2/v2/auth?
    response_type=code&
    client_id=Ab2i34JHS9D&
    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.

HTTP Response

HTTP/1.1 302 Found
Location: https://xorismesiti.gr/api/auth/callback?code=4/0AX4XfWgyVyz-uT8k7WiyLg0Q&state=random-state-value
Content-Type: text/html; charset=UTF-8
Content-Length: 0

Frontent Code

// Redirect to Google's OAuth 2.0 endpoint when user clicks login
const loginWithGoogle = () => {
  const clientId = 'YOUR_GOOGLE_CLIENT_ID';  // 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

  1. User grants permission
  2. Google redirect the user to the redirect_uri you specified in the previous request (https://xorismesiti.gr/api/auth/callback)
  3. The frontend must not directly exchange the code for an access_token. Instead, it sends the code to the backend via an API request.

HTTP Request

POST https://xorismesiti.gr/api/auth/exchange-token
Content-Type: application/json

{
  "code": "authorization-code-from-google"
}

HTTP Response

{
  "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;




3. [Backend] Exchange Code with Token

  1. The backend receives the authorization code from the frontend,
  2. The backend makes a POST request to Google token endpoint, to exchange the authorization code for the access_token and optionally a refresh token
  3. The backend ensures to never expose the client_secret to the frontend. This step should always be handled on the backend.
  4. The backend will exchange the code for an access_token and refresh_token, which are sent back to the frontend or stored securely for subsequent API calls.

HTTP Request

  • HTTP Method: POST
  • URL: https://oauth2.googleapis.com/token
  • Headers:
    • Content-Type: application/x-www-form-urlencoded
  • Body Parameters:
    • 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).
POST https://oauth2.googleapis.com/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&
code=4/0AX4XfWgNmGZVbV7Kdr8Q9yVyzIYBnbbBdLfX39ZaE8m0w8zT8jKRLl7w-uT8k7WiyLg0Q&
redirect_uri=https://xorismesiti.gr/callback&
client_id=YOUR_GOOGLE_CLIENT_ID&
client_secret=YOUR_GOOGLE_CLIENT_SECRET

HTTP Response

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

Example 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');
});




4. [Frontend] Use the Access Token

Once the backend exchanges the code for the access_token,

the frontend can use it to make authenticated requests to the backend or Google APIs

HTTP Request

GET https://xorismesiti.gr/api/user-profile
Authorization: Bearer access-token-from-backend

HTTP Response

{
    "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"
}

Example Frontend Code:

// 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 => response.json())
  .then(data => {
    // Handle user data
  })
  .catch(error => {
    console.error('Error fetching user profile:', error);
  });




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 Request

GET https://www.googleapis.com/oauth2/v3/userinfo
Authorization: Bearer ya29.a0AfH6SMC8Op6zXZkHi2XITkDoOVzYXt3hTY6sny54UlWlxrnKlX5Xv78is7BEHekVX-VoA

HTTP Response

{
    "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"
}

Example Backend Code:

app.get('/api/user-profile', async (req, res) => {
  const accessToken = req.headers['authorization'].split(' ')[1]; // Extract token from Authorization header
  
  try {
    const response = await axios.get('https://www.googleapis.com/oauth2/v3/userinfo', {
      headers: {
        Authorization: `Bearer ${accessToken}`,
      },
    });
    
    // Send the user profile data to the frontend
    res.json(response.data);
  } catch (error) {
    console.error('Error fetching user profile:', error);
    res.status(500).json({ error: 'Failed to fetch user profile' });
  }
});




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 Request

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

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

Example Backend Code:

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' });
  }
});