Search code examples
ruby-on-railsgoogle-apigoogle-calendar-apigoogle-oauth

Google API OAuth 2.0 get user permission on mobile device, use on server side API


I have a mobile app and I want to add events on the user's google calendar from my server. I've added the consent flow on iOs and I get an idToken from the response. I can also get an accessToken using getTokens on the client. However I am not able to use these in order to get an appropriate refresh token on the server so I can call the Calendar API at a later stage. For a web app it requires a client secret. The ios client does not have a secret and when I use my web client id and secret I get insufficientPermissions: Insufficient Permission: Request had insufficient authentication scopes. .

To clarify, my steps are:

  1. get consent and idToken on the phone
  2. send idToken to the server
  3. use idToken to get refresh token on the server (this is where things fall apart)
  4. use refresh token to create events on the user's calendar

Here's my ios React Native code:

signInWithGoogle() {
    GoogleSignin.configure({scopes: ['https://www.googleapis.com/auth/calendar', 'https://www.googleapis.com/auth/calendar.events']});

    GoogleSignin.signIn()
    .then(result => {
      // idToken
      console.log(result);
      GoogleSignin.getTokens()
      .then(tokens => {
        //this gets me an accessToken
        console.log(tokens);
      })
    }).catch(error => {
      console.log('google auth error: ', error);
    });
  }

And my server side code:

secrets = Google::APIClient::ClientSecrets.new({
      "web" => {
        "refresh_token" => ENV['GOOGLE_REFRESH_TOKEN'],
        "client_id" => ENV["GOOGLE_CLIENT_ID"],
        "client_secret" => ENV["GOOGLE_CLIENT_SECRET"]
      }
    })
auth_code = idToken_from_client
auth_client = secrets.to_authorization
auth_client.update!(scope: 'https://www.googleapis.com/auth/calendar.events')
auth_client.code = auth_code
auth_client.fetch_access_token!

I know the server code doesn't make sense because I already have an access token, however I need a refresh token. I will need to create events for the user as long as they use the app and access tokens expire. And this is what they recommended for getting a refresh token in the server side auth docs: https://developers.google.com/identity/protocols/oauth2/web-server#exchange-authorization-code

Thanks for your help!


Solution

  • What I ended up doing is using the auth flow for installed apps opening a system browser window, using the web client id and secret. I use the redirect uri pointing to my server endpoint where I use the google-api-client to obtain a refresh and access token with the provided code.

    ReactNative code:

    const scope = 'scope=the_google_scope';
    const redirect = 'redirect_uri=redirect_for_google_auth'; // Your serverside endpoint
    const url = `https://accounts.google.com/o/oauth2/v2/auth?${scope}&prompt=consent&access_type=offline&response_type=code&${redirect}&client_id=${googleClientID}`
    
    Linking.openURL(url);
    

    Rails code:

    require "google/api_client/client_secrets.rb"
    ...
    secrets = Google::APIClient::ClientSecrets.new({
          web: {
            client_id: googleClientID, # Same one used on the client
            client_secret: googleClientSecret,
            grant_type: "authorization_code",
            redirect_uri: "redirect_for_google_auth"
          }
        })
    auth_client = secrets.to_authorization
    auth_client.code = code_retrieved_from_google
    auth_result = auth_client.fetch_access_token!
    

    Hopes this helps other people with this scenario.