Is there a way for me to access the user's gender and birthday following their login using their Google account details?

I have successfully implemented a Google sign-in button in my Angular application following the example provided in Display the Sign In With Google button:

<div id="g_id_onload"
   class="mt-3"
   data-client_id="XXXXXXXXXXXX-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.apps.googleusercontent.com"
   data-login_uri="http://localhost:1337/login/google"
   data-auto_prompt="false">
</div>
<div class="g_id_signin"
   data-width="250"
   data-type="standard"
   data-size="large"
   data-theme="outline"
   data-text="continue_with"
   data-shape="rectangular"
   data-logo_alignment="center">
</div>

Upon user sign-in, I validate and decode the JWT token supplied by Google on my Express server using jsonwebtoken:

app.post('/login/google', express.urlencoded(), async(request, response, next) => {
    try {
        console.log(`${request.method} ${request.url} was called.`);
        let token: string = request.body.credential;
        let body: Response = await fetch('https://www.googleapis.com/oauth2/v1/certs', { method: 'GET', headers: { Accept: 'application/json' }});
        let json: any = await body.json();
        let certificates: string[] = Object.keys(json).map(key => json[key]);
        let decoded: any;
        let lastError: any;
        certificates.every(certificate => {
            try {
                decoded = jwt.verify(token, certificate, { algorithms: ['RS256'], ignoreExpiration: false });
            }
            catch (error) { 
                lastError = error;
            }
            return !decoded;
        });
        if (!decoded)
            throw lastError;
    }
    catch (error) {
        next(error);
    }
});

However, I'm facing an issue where the decoded token does not include the user's gender or birthday information. How can I retrieve this data?

I recently attempted to manually add the scopes

https://www.googleapis.com/auth/user.birthday.read
and
https://www.googleapis.com/auth/user.gender.read
to my application's OAuth Consent Screen at , but the prompts for these additional data fields were not shown when running the application. I also revoked permissions for my application from my Google account at accounts.google.com and hoped that it would trigger the prompt for these extra data. However, I'm unsure of the correct approach to obtain this additional information since there seems to be limited documentation available on how to achieve this. Additionally, the Gender and Birthday information in my test account is set as Private in . Is there a way to access these private scopes somehow?

To clarify, despite adding the scopes, the sign-in only displays the standard confirmation prompt:

Confirm you want to sign in to [Application Name] with [User's Name].

To create your account, Google will share your name, email address, and profile picture with [Application Name].

I also experimented with https://developers.google.com/oauthplayground/ by inputting the scopes

https://www.googleapis.com/auth/userinfo.email,https://www.googleapis.com/auth/userinfo.profile,https://www.googleapis.com/auth/user.birthday.read,https://www.googleapis.com/auth/user.gender.read
, authorizing them, and accessing the People API endpoint to fetch the required data. Although the endpoint functions correctly, I am uncertain about the authorization parameters needed for this endpoint based on the data received from the POST request to my Express server. I also enabled the People API from Enabled APIs & services.

Answer №1

When using the signin feature, it returns an id token which contains limited claims and does not include gender information.

To access the full user profile info, you need to utilize the people api as mentioned.

You can try out a working example and generate a sample for yourself using the try me link.

<script src="https://apis.google.com/js/api.js"></script>
<script>
  // JavaScript code for interacting with Google People API

  function authenticate() {
    // Code for authentication
  }

  function loadClient() {
    // Code for loading client
  }

  function execute() {
    // Code for executing API call
  }

  // Load necessary library and initialize API client
  gapi.load("client:auth2", function() {
    gapi.auth2.init({client_id: "YOUR_CLIENT_ID"});
  });
</script>
<button onclick="authenticate().then(loadClient)">Authorize and Load</button>
<button onclick="execute()">Execute</button>

If your implementation uses Oauth2 instead of open id connect (signin), you will need an access token for the above code to work. Ensure that your signin process provides an access token to feed into the code snippet above to avoid reauthorization.

I have not seen anyone successfully integrate the new signin system with the old oauth2 system yet. If you manage to do so, please share your findings.

Html

An access_token is required to call this API. Note that a Google access token is different from a JWT or id_token.

GET https://people.googleapis.com/v1/people/me?personFields=genders&key=[YOUR_API_KEY] HTTP/1.1

Authorization: Bearer [YOUR_ACCESS_TOKEN]
Accept: application/json

Answer №2

After following the instructions in this helpful guide, I was able to successfully get things up and running.

Instead of using the Google sign-in button due to limitations on extended scopes like birthday and gender, I turned to the OAuth API which supports these features. To create my own Google sign-in button, I utilized the googleapis package.

The process involved a few key steps:

  1. Utilize the googleapis package to generate a URI that prompts users to consent to accessing gender and birthday information.

For instance:

app.get('/login/google/uri', async(request, response, next) => {
    try {
        // code snippet here
    }
    catch (error) {
        // error handling
    }
});
  1. Ensure that your redirect URI (e.g.,
    http://localhost:4200/login/google/redirect
    ) is added as an authorized redirect URI under your OAuth 2.0 Client ID Credentials in the Google Cloud Console.
  2. Upon redirection by Google to your specified URI, extract the code parameter from the URL and exchange it for an access token.

Here's an example:

// code snippet here
  1. Use the obtained access token when making requests to the People API and include it in the Authorization header as a bearer token.

This can be done as shown in this curl request format:

// curl command here

You should receive a response containing user details such as genders and birthdays.

In addition, here is a more API-friendly approach using the googleapis package:

First, generate the URI and direct the user accordingly:

// code snippet here

Secondly, after receiving the user's code post-sign-in, parse it and use it to retrieve extra user data like birthdays, genders, and emails:

// server-side POST method snippet goes here

The fetched data should now be available in the output.

Answer №3

If you're working with NestJS using typescript, here's a solution that worked for me

@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
  constructor(configService: ConfigService) {
    super({
      clientID: configService.get('GOOGLE_CLIENT_ID'),
      clientSecret: configService.get('GOOGLE_SECRET'),
      callbackURL: configService.get('GOOGLE_REDIRECT_URL'),
      scope: [
        'https://www.googleapis.com/auth/userinfo.profile',
        'https://www.googleapis.com/auth/userinfo.email',
        'https://www.googleapis.com/auth/plus.login',
        'https://www.googleapis.com/auth/user.birthday.read',
        'https://www.googleapis.com/auth/user.phonenumbers.read',
        'https://www.googleapis.com/auth/user.gender.read',
      ],
    });
  }

  async validate(
    accessToken: string,
    refreshToken: string,
    profile: any,
    done: VerifyCallback,
  ): Promise<any> {
    const { name, emails, photos, sub, birthday, phoneNumber, gender } =
      profile;
    const user = {
      sub,
      email: emails[0].value,
      firstName: name.givenName,
      lastName: name.familyName,
      picture: photos[0].value,
      dob: birthday,
      phoneNumber,
      gender,
      refreshToken,
      accessToken,
    };

    done(null, user);
  }
}

Next step is to include your GoogleStrategy in your provider setup. And remember to set your keys in the .env file.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the most effective way to extract information from a .txt file and showcase a random line of it using HTML?

As a newbie in HTML, my knowledge is limited to finding a solution in C#. I am attempting to extract each line from a .txt file so that I can randomly display them when a button is clicked. Instead of using a typical submit button, I plan to create a custo ...

React higher order component (HOC) DOM attributes are causing the error message 'Unknown event handler property' to be triggered

I recently created a Higher Order Component (HOC) using recompose, but I'm encountering a React Warning every time the props are passed down. Warning: Unknown event handler property `onSaveChanges`. It will be ignored. All my properties with a speci ...

Creating a private variable in Javascript is possible by using getter and setter functions to manage access to the

Is this the correct way to simulate a private variable? var C = function(a) { var _private = a + 1; // more code... Object.defineProperties(this, { 'privateProp': { get: function() { return _privat ...

The request to login at the specified API endpoint on localhost:3000 was not successful, resulting in a

As I continue to develop my programming skills, I have been working on configuring a database connected with nodejs in the same folder. However, when trying to make an ajax request to the database, I encountered an error indicating that the database may be ...

The integration of Laravel (Homestead) Sanctum is malfunctioning when combined with a standalone Vue application

After running the command php artisan serve my Laravel application successfully resolves on localhost:8000. I have configured Laravel Sanctum as follows: SESSION_DRIVER=cookie SESSION_DOMAIN=localhost SANCTUM_STATEFUL_DOMAINS=localhost:8080 As for m ...

Detecting the presence of a file on a local PC using JavaScript

In the process of creating a Django web application, I am exploring methods to determine if a particular application is installed on the user's local machine. One idea I have considered is checking for the existence of a specific folder, such as C:&bs ...

Building a dropdown menu component in react native

Looking to implement a dropdown menu in React Native using TypeScript. Any suggestions on how to achieve this for both iOS and Android platforms? Check out this example of a dropdown menu ...

Passing the value of each table row using Ajax

On my webpage, I have a list of orders displayed. I'm facing an issue where the value of each row in the table is not being passed correctly to my controller and database when a user clicks on a button - it always shows null. Can someone please assist ...

What is the best way to initiate a saga for an API request while another call is currently in progress?

During the execution of the first action in saga, the second action is also being called. While I receive the response from the first action, I am not getting a response from the second one. this.props.actions.FetchRequest(payload1) this.props.actions.F ...

Customize hoverIntent to support touch events on mobile devices

Hello everyone. I've encountered an issue with hoverintent.js, a jQuery plugin that handles mouseOver events differently than usual. I am facing constraints where I can only modify the JavaScript of this plugin, but I need it to be compatible with to ...

Next.js triggers the onClick event before routing to the href link

Scenario In my current setup with Next.js 13, I am utilizing Apollo Client to manage some client side variables. Objective I aim to trigger the onClick function before navigating to the href location. The Code I'm Using <Link href={`/sess ...

Pass data from controller using Ajax in CodeIgniter

Here is my JavaScript code: $(document).ready(function(){ $("input[type='checkbox']").change(function(){ var menu_id = this.value; if(this.checked) var statusvalue = "On"; else var statusvalue = "Off"; $.ajax( ...

Tips for triggering an error using promise.all in the absence of any returned data?

I'm dealing with an issue in my project where I need to handle errors if the API response returns no data. How can I accomplish this using Promise.all? export const fruitsColor = async () : Promise => { const response = await fetch(`....`); if( ...

Creating an object type that includes boolean values, ensuring that at least one of them is true

To ensure both AgeDivisions and EventStyles have at least one true value, I need to create a unique type for each. These are the types: type AgeDivisions = { youth: boolean; middleSchool: boolean; highSchool: boolean; college: boolean; open: bo ...

Error message stating 'compression is not defined' encountered while attempting to deploy a Node.js application on Heroku

Why is Heroku indicating that compression is undefined? Strangely, when I manually set process.env.NODE_ENV = 'production' and run the app with node server, everything works perfectly... Error log can be found here: https://gist.github.com/anony ...

Issue with Backbone Event Dropping Functionality

I am facing an issue with my dashboard that has two backbone Views. One of the views consists of various drop zones while the other contains items with draggable="true". Surprisingly, the drop event is not being triggered in these drop zones; however, they ...

I am looking to dynamically insert a text field into an HTML form using jQuery or JavaScript when a specific button is clicked

This is my code snippet: <div class="rButtons"> <input type="radio" name="numbers" value="10" />10 <input type="radio" name="numbers" value="20" />20 <input type="radio" name="numbers" value="other" />other </div> ...

Endless Scroll Feature in Javascript

A brief tale: I am in search of a way to extract data from a website that features infinite scroll without actually implementing the infinite scroll feature myself. My plan is to create a script that will auto-scroll the page from the browser console, wai ...

Stay dry - Invoke the class method if there is no instance available, otherwise execute the instance method

Situation When the input is identified as a "start", the program will automatically calculate the corresponding "end" value and update the page with it. If the input is an "end", simply display this value on the page without any modifications. I am in th ...

Guide on utilizing automatic intellisense in a standard TextArea within a web application

I have successfully created an Online compiler web application that is currently running smoothly. However, I am now looking to enhance my application by implementing intellisense in the TextArea where the program is being typed. For instance, if I type "S ...