Can anyone point me to an SST example of a React S...
# help
g
Can anyone point me to an SST example of a React SPA where the only authentication is done via federation to Google e.g. GSuite login?
g
@Frank that didn’t work for me because I need a user pool, not just the IDP. When I try to run the examples that have the Auth : {cognito: true} I am still blocked. For one, to set up the Google OAuth webflow, you need a cognito domain name to set up the back and forth. But when I look at the user pool that’s been configure by sst.Auth, there is no domain name.
Copy code
this.auth = new sst.Auth(this, 'auth', {
      cognito: {
        userPool: {
          userPoolName: this.scope.logicalPrefixedName('userPool'),
        }
      },
      google: {
        clientId:
          '<http://xxx-xxx.apps.googleusercontent.com|xxx-xxx.apps.googleusercontent.com>',
      },
    })
I see now that I probably have to set the pooloptions, but that’s why I was hoping to see a fully worked out example 😏
f
Oh I see. The
Auth
construct is meant to tie google into identity pool. I think your setup is different. You are looking to tie google into the user pool.
So you are not looking to authorize your Api endpoint with IAM right? You are using JWT?
g
I’m using static key for the API at the moment, but was more concerned about protecting the front end application using OAuth2 to the client’s domain. I ended up with the following:
Copy code
this.cognitoDomainPrefix = `${this.scope.stage}example`
    this.cognitoUserPool = new cognito.UserPool(this, 'userPool', {
      userPoolName: this.scope.logicalPrefixedName('userPool'),
      lambdaTriggers: {
        preAuthentication: emailDomainPreAuthorizer
      }
    })
    this.cognitoUserPool.addDomain('custom-domain-cognito',
      {
        cognitoDomain:
          {
            domainPrefix: this.cognitoDomainPrefix
          }
      }
    )

    this.UserPoolIdentityProviderGoogle = new cognito.UserPoolIdentityProviderGoogle(this, 'google-provider', {
      clientId: '<http://123123123123-123456abcdef.apps.googleusercontent.com|123123123123-123456abcdef.apps.googleusercontent.com>',
      clientSecret: 'XXXXXXX',
      userPool: this.cognitoUserPool,
      attributeMapping: {
        email: cognito.ProviderAttribute.GOOGLE_EMAIL
      },
      scopes: ['profile', 'email', 'openid']
    })
    const callbackUrls = this.scope.stage === 'dev' ? ['<http://localhost:3000>', '<https://dev.example.com>'] :  ['<https://staging.example.com>', '<https://www.example.com>']
    this.cognitoUserPoolClient = new cognito.UserPoolClient(this, 'userPoolClient', {
      userPool: this.cognitoUserPool,
      supportedIdentityProviders: [cognito.UserPoolClientIdentityProvider.GOOGLE],
      userPoolClientName: `${this.scope.stage}googleClient`,
      oAuth: {
        callbackUrls
      }
    })
    this.cognitoUserPoolClient.node.addDependency(this.UserPoolIdentityProviderGoogle)
Then passed the following to the SPA:
Copy code
<http://this.site|this.site> = new sst.ReactStaticSite(this, 'ReactSite', {
      path: 'frontend',
      // Pass in our environment variables
      environment: {
        REACT_APP_API_URL: this.customApiDomainUrl,
        REACT_APP_REGION: this.scope.region,
        REACT_APP_USER_POOL_ID: this.cognitoUserPool.userPoolId,
        REACT_APP_USER_POOL_CLIENT_ID: this.cognitoUserPoolClient.userPoolClientId,
        REACT_APP_COGNITO_DOMAIN: `${this.cognitoDomainPrefix}.auth.${this.scope.region}.<http://amazoncognito.com|amazoncognito.com>`,
        REACT_APP_CALLBACK: `https://${this.scope.stage}.${process.env.DOMAIN_NAME}`
      },
      customDomain: {
        hostedZone: process.env.DOMAIN_NAME,
        domainName: `${this.scope.stage === 'prod' ? 'www' : this.scope.stage}.${process.env.DOMAIN_NAME}`,
      }
    })
Where the email domain pre-authorizer for Cognito was defined as:
Copy code
const emailDomainPreAuthorizer = new sst.Function(this, 'Email-Domain-Authorizer', {
      handler: 'src/auth/preauth-domain.handler',
      runtime: lambda.Runtime.NODEJS_14_X,
      environment: {
        ALLOWED_USER_DOMAIN: process.env.ALLOWED_USER_DOMAIN
      },
      securityGroups: [this.securityGroup],
      vpcSubnets: this.subnets,
      vpc: this.vpc
    })
and for
preauth-domain.js
, I found an example on the interweb:
Copy code
exports.handler = async (event, context, callback) => {

  const userEmailDomain = event.request.userAttributes.email.split('@')[1]
  const allowedDomain = process.env.ALLOWED_USER_DOMAIN

  if (userEmailDomain === allowedDomain) {
    callback(null, event)
  } else {
    const error = new Error('Cannot authenticate users from domains different from ' + allowedDomain)
    callback(error, event)
  }
}
f
@Guy Shechter Oh nice! Thanks for sharing! We’re thinking of creating a UserPool construct to help with this https://github.com/serverless-stack/serverless-stack/issues/406
I added ur design to the issue description. I’m sure it will become handy when we get to work on it.