Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

G Suite identity provider for an AWS driven browser based App

Tags:

I'm aware of how to create a Google authenticated app via with google-signin-client_id 3089273xx-xxxxxxxxxxxx.apps.googleusercontent.com & <script src="https://apis.google.com/js/platform.js" async defer></script>, but the problem here is that, I have not been able to LIMIT the login to just my company's G Suite instance.

The app I have is a "serverless" JS bundle hosted on S3. The logged in Google token is tied to an AWS role that accesses sensitive resources.

So typical solutions to check the email of googleUser.getBasicProfile() or pass a hd parameter don't make any security sense since they can be manipulated with browser dev tools IIUC.

Is there some other Google API I could be using or strategy I could apply? I imagine the solution would come in the form of a special google-signin-client_id for my company's domain which is hosted by G Suite. This is how it's tied to the role at AWS:

AWS IAM Google auth

I'm aware I could setup duplicate my users in AWS "user pools" and use Cognito, but I am trying to have a "single source of truth" for the company's employees & ease the administration burden.

like image 586
hendry Avatar asked Oct 18 '16 02:10

hendry


2 Answers

UPDATE: This answer is insecure as if you simply remove hosted_domain, you can authenticate with any Google login.

After straying upon https://developers.google.com/identity/work/it-apps & using GAPI directly I found I could do a

    GAPI.auth2.init({
        client_id: CLIENT_ID,
        hosted_domain: 'example.com'
    })

And then as the documentation advises, you setup Manage API client access

Authorized API clients

So now only users of @example.com on Gsuite can access this JS app! This took weeks to figure out. So just to conclude, how to authenticate using Google on a AWS powered serverless app:

  1. Setup a client ID via OAuth client ID with your whitelisted origin URLs from https://console.developers.google.com/apis/credentials
  2. In AWS IAM setup a Role with Google as the (web) Identity provider with the client ID
  3. Add your client ID https://admin.google.com/AdminHome?chromeless=1#OGX:ManageOauthClients as documented here https://developers.google.com/identity/work/it-apps to crucially limit your application to your company's domain.

So now we have a statically hosted App limited to only company employees to access sensitive paid AWS APIs.

like image 189
hendry Avatar answered Sep 22 '22 16:09

hendry


I tried 3 different options, the first one worked for my scenario:

First Option - Validating Google Id Token on each call on lambda side

I always pass the id_token as a header on the client calls(web and mobile apps).

"acceptableHds" Is the list of allowed domains.

const oauth = new Auth.OAuth2(CLIENT_ID_WEB, CLIENT_SECRET);
    oauth.verifyIdToken(token, null, (err, ticket) => {
      if (err) {
        return reject(err);
      }
      const payload = ticket.getPayload();

      const tokenIsOK = payload &&
        payload.aud === CLIENT_ID &&
        new Date(payload.exp * 1000) > new Date() &&
        acceptableISSs.has(payload.iss) &&
        acceptableHds.has(payload.hd)

      return tokenIsOK ? resolve(payload.hd) : reject();
    });

Second Option - Validating Google Id Token once on lambda side

I started this alternative way but I didn't finished because the first solutions fitted to my needs and the milestones was close(it needs a indentity pool):

1)Send the id_token to the lambda function and validate it on Google API(here is where you can check the domain using the code above)

2)Call the cognitoidentity.getOpenIdTokenForDeveloperIdentity on the lambda side using the id_token coming from the browser

3) On the client, call any of the Cognito or STS functions like assumeWebIdentity, AssumeRole using the tokens returned from getOpenIdToken.

function getCognitoToken(id_token) {
  var param = {
    IdentityPoolId: 'us-east-1:f7b3d55f-6b63-4097-be8f-3dc22ddec1a4',
    Logins: { 'accounts.google.com': id_token }
  }
  return check_company(id_token).then(function (valid) {
    return cognitoidentity.getOpenIdTokenForDeveloperIdentity(param).promise()
  })

I couldn't finish the third step. You need use the tokens received on the second step without revealing the 'identity pool id'. If you do that and assure that the role can't list identity pool ids, it will work as intended and It will be secure.

Third Option - SAML provider

You can create a SAML provider and use SAML assertions to validate the user domain.

http://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_saml_assertions.html

I failed miserably trying to do it.

P.S.: Google Admin let you create private apps, limiting to you company domains, but It works only for mobile as far as I know

https://support.google.com/a/answer/2494992?hl=en

Hope it helps someone!

like image 31
Carlos Alberto Schneider Avatar answered Sep 22 '22 16:09

Carlos Alberto Schneider