Google Login on AWS Cognito Without Hosted UI (Work-around)

Setup

Registration

<GoogleLogin
clientId={process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID}
responseType={'id_token'}
buttonText="Register with Google"
onSuccess={googleRegisterSuccess}
onFailure={googleRegisterFailure}
/>
const googleRegisterSuccess = (googleResponse) => {
fetch('/api/register/google', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ id_token: googleResponse?.tokenId })
}).then(res => {
if (!res.ok) throw res
router.push({
pathname: '/login'
})
}).catch(err => {
console.error(err)
})
}

const googleRegisterFailure = (googleResponse) => {
console.error(googleResponse)
}
import { CognitoIdentityProviderClient, SignUpCommand } from '@aws-sdk/client-cognito-identity-provider'
import { OAuth2Client } from 'google-auth-library'

const {
COGNITO_REGION,
COGNITO_APP_CLIENT_ID,
GOOGLE_TOKEN_ISSUER,
NEXT_PUBLIC_GOOGLE_CLIENT_ID,
} = process.env

export default async function handler(req, res) {
if (req.method !== 'POST') return res.status(405).send()

let googlePayload

try {
// Verify the id token from google
const oauthClient = new OAuth2Client(NEXT_PUBLIC_GOOGLE_CLIENT_ID)
const ticket = await oauthClient.verifyIdToken({
idToken: req.body.id_token,
audience: NEXT_PUBLIC_GOOGLE_CLIENT_ID
})
googlePayload = ticket.getPayload()
if (
!googlePayload?.iss === GOOGLE_TOKEN_ISSUER ||
!googlePayload?.aud === NEXT_PUBLIC_GOOGLE_CLIENT_ID
) {
throw new Error("Token issuer or audience invalid.")
}
} catch (err) {
return res.status(422).json({ message: err.toString() })
}

// Register the user
try {
const params = {
ClientId: COGNITO_APP_CLIENT_ID,
Username: googlePayload.email.split("@")[0], // Username extracted from email address
Password: googlePayload.sub,
UserAttributes: [
{
Name: 'email',
Value: googlePayload.email
},
{
Name: 'custom:RegistrationMethod',
Value: 'google'
}
],
ClientMetadata: {
'EmailVerified': googlePayload.email_verified.toString()
}
}
const cognitoClient = new CognitoIdentityProviderClient({
region: COGNITO_REGION
})
const signUpCommand = new SignUpCommand(params)
const response = await cognitoClient.send(signUpCommand)
return res.status(response['$metadata'].httpStatusCode).send()
} catch (err) {
console.log(err)
return res.status(err['$metadata'].httpStatusCode).json({ message: err.toString() })
}
}
  1. We need to verify the idToken by using the google-auth-library package. After verifying the token, we get a "payload" that contains some useful information about the user and about the token.
  2. We need to check that the token issuer is accounts.google.com or https://accounts.google.com and that the audience matches our Google client id.
  3. Next, we register the user with the SignUpCommand. We use a substring of the email as the username and the sub as the password. You'll want to be careful, you may need to find a more creative way to set the password.

PreSignup Trigger

module.exports.handler = async (event, context, callback) => {
// Check if the provided email address is already in use

// Verify user's email address if it's already verified with Google.
if (event.request.userAttributes['custom:RegistrationMethod'] === "google") {
let userEmailVerified = event.request.clientMetadata['EmailVerified'] === 'true'
event.response.autoVerifyEmail = userEmailVerified
event.response.autoConfirmUser = userEmailVerified
}

callback(null, event);
};

Sign in

<GoogleLogin 
clientId={process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID}
buttonText="Login with Google"
responseType={'id_token'}
onSuccess={googleSignInSuccess}
onFailure={googleSignInFailure}
/>
const googleSignInSuccess = (googleResponse) => {
fetch('/api/login/google', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ id_token: googleResponse?.tokenId })
}).then(res => {
if (!res.ok) throw res
return res.json()
}).then(data => {
console.log(data)
}).catch(err => {
console.error(err)
})
}

const googleSignInFailure = (googleResponse) => {
console.error(googleResponse)
}
import { CognitoIdentityProviderClient, AdminInitiateAuthCommand } from "@aws-sdk/client-cognito-identity-provider";
import { OAuth2Client } from "google-auth-library";

const {
COGNITO_REGION,
COGNITO_APP_CLIENT_ID,
COGNITO_USER_POOL_ID,
GOOGLE_TOKEN_ISSUER,
NEXT_PUBLIC_GOOGLE_CLIENT_ID
} = process.env

export default async function handler(req, res){
if (!req.method === 'POST') return res.status(405).send()

let googlePayload

try {
const oauthClient = new OAuth2Client(NEXT_PUBLIC_GOOGLE_CLIENT_ID)
const ticket = await oauthClient.verifyIdToken({
idToken: req.body.id_token,
audience: NEXT_PUBLIC_GOOGLE_CLIENT_ID
})
googlePayload = ticket.getPayload()
if (
!googlePayload?.iss === GOOGLE_TOKEN_ISSUER ||
!googlePayload?.aud === NEXT_PUBLIC_GOOGLE_CLIENT_ID
) {
throw new Error("Token issuer or audience invalid.")
}
} catch (err) {
return res.status(422).json({ message: err.toString() })
}

// Sign the user in
try {
const params = {
AuthFlow: 'ADMIN_USER_PASSWORD_AUTH',
ClientId: COGNITO_APP_CLIENT_ID,
UserPoolId: COGNITO_USER_POOL_ID,
AuthParameters: {
USERNAME: googlePayload?.email,
PASSWORD: googlePayload?.sub
}
}
const cognitoClient = new CognitoIdentityProviderClient({
region: COGNITO_REGION
})
const adminInitiateAuthCommand = new AdminInitiateAuthCommand(params)
const response = await cognitoClient.send(adminInitiateAuthCommand)
return res.status(response['$metadata'].httpStatusCode).json({
...response.AuthenticationResult
})
} catch (err) {
console.log(err)
return res.status(err['$metadata'].httpStatusCode).json({ message: err.toString() })
}
}

Conclusion

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store