dev/OAuth2-Backend-Approach.md

8.3 KiB

OAuth2 purpose

A way for the user to tell google to give an access to xorismesiti.gr app


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

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

1. Front GET to Google

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.

2. Google 302 to Back

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

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

2. Google response to Back

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

3. Back response to Front

// 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

  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

  5. Google response to Back with profile data

{
  "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"
}
  1. Back response to Front with profile data
{
  "sub": "1234567890",
  "name": "John Doe",
  "picture": "https://lh3.googleusercontent.com/8bgVEOykQ6ykFSQ=s96",
  "email": "john.doe@example.com",
}
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 (
    <div className="App">
      <h1>Google OAuth2 with React</h1>

      {!isAuthenticated ? (
        <div>
          <p>Please log in with Google to access your profile:</p>
          <button onClick={() => window.location.href = 'http://localhost:3000/auth/google'}>
            Login with Google
          </button>
        </div>
      ) : (
        <div>
          <h2>Welcome, {user?.name}</h2>
          <p>Email: {user?.email}</p>
          <img src={user?.picture} alt="Profile" />
        </div>
      )}
    </div>
  );
};

export default App;

GET https://xorismesiti.gr/api/user-profile
Authorization: Bearer access-token-from-backend
{
    "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"
}
GET https://www.googleapis.com/oauth2/v3/userinfo
Authorization: Bearer ya29.a0AfH6SMC8Op6zXZkHi2XITkDoOVzYXt3hTY6sny54UlWlxrnKlX5Xv78is7BEHekVX-VoA
{
    "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"
}
{
    "access_token": "ya29.a0AfH6SMC8Op6zXZkHi2XITkDoOVzYXt3hTY6sny54UlWlxrnKlX5Xv78is7BEHekVX-VoA",
    "token_type": "Bearer",
    "expires_in": 3600
}