Unable to setup AWS-amplify MFA - issue with the Auth.confirmSignIn function - amazon-web-services

I'm having a problem with my adding MFA to my site which manages authentication with AWS Amplify.
I've followed the example here and created the code below to handle both users who have MFA enabled, and those that don't.
const Signin = props => {
const { classes, onStateChange, history } = props;
const [inputs, setInputs] = useState({
username: '',
password: '',
});
const [currentStep, setCurrentStep] = useState(1)
const [userObject, setUserObject] = useState({})
const [authCode, setauthCode] = useState({code: ''})
const onSignIn = async (e) => {
const username = inputs.username.toLowerCase()
await Auth.signIn({
username, // Required, the username
password: inputs.password, // Optional, the password
}).then(user => {
if (user.preferredMFA === 'SOFTWARE_TOKEN_MFA'){
setUserObject(user)
setCurrentStep(2)}
else{
onStateChange('signedIn', {})
history.push('/dashboard');
}
})
.catch(err => {
showAlert(err.message)
if (err.code === 'PasswordResetRequiredException') {
onStateChange('forgotPassword')
history.push('/forgotpassword');
}
})
}
const MfaSignIn = async (e) => {
const user=userObject
await Auth.confirmSignIn(
user,
authCode.code,
user.preferredMFA
).then(user => {
onStateChange('signedIn', {})
history.push('/dashboard');
})
.catch(err => {
showAlert(err.message)
if (err.code === 'PasswordResetRequiredException') {
onStateChange('forgotPassword')
history.push('/forgotpassword');
}
})
If MFA is not enabled the login page will call the onSignIn function then log them in. If MFA is enabled the user object returned from the signin function will return the value "SOFTWARE_TOKEN_MFA" for the preferredMFA key, and a second page will load which allows the user to enter their MFA code, which is handled by the MfaSignIn function.
No MFA works fine, however when attempting to use the MfaSignIn function as is I get "Missing required parameter Session"
the Session key of the user object returned by onSignIn has a value of null, and there's no mention of it needing to be added from the docs. Ive tried adjusting this function to
const MfaSignIn = async (e) => {
e.preventDefault(authCode.code);
const session= Auth.currentSession().then(data=> {return data})
const token = await session.then(data=>{return data.getAccessToken()})
const user=userObject
user.Session=token.jwtToken
await Auth.confirmSignIn(
user,
authCode.code,
user.preferredMFA
).then(user => {
onStateChange('signedIn', {})
history.push('/dashboard');
})
.catch(err => {
showAlert(err.message)
if (err.code === 'PasswordResetRequiredException') {
onStateChange('forgotPassword')
history.push('/upgrade');
}
})
}
Where I call the Auth.currentSession method and add the data from there to the Session value in the user object. Adding the whole object returns the error-
"Start of structure or map found where not expected."
- seems to need a string not a map object
I've tried adding each of the three JWT token strings that are found in the currentSession object, along with the result of the getAccessToken as in the example above. All return the error
"Invalid session provided"
I'm at a loss at what I need to give the Auth.confirmSignIn to let the user sign in- I appear to be doing everything correctly as per the docs.
Any ideas?

Related

How to solve "Error: [auth/invalid-tenant-id] The Auth instance's tenant ID is invalid." error

I'm running into trouble trying to log in with custom token to Firebase. Sign in attempt results in [Error: [auth/invalid-tenant-id] The Auth instance's tenant ID is invalid.]
There's definitely a tenant in Google Cloud Identity Platform with this id.
App (react-native):
const tenantId = 'tenant-id-from-gc'
const signInFunction = () => {
try {
await auth().setTenantId(tenantId)
const userCredential = await auth().signInWithCustomToken(customToken)
} catch (error) {
// Handle error
}
}
Backend (Express.js):
const tenantId = 'tenant-id-from-gc'
const tenantManager = getAuth().tenantManager()
const tenantAuth = tenantManager.authForTenant(tenantId)
const customToken = await tenantAuth.createCustomToken(uid, {
customClaim: true,
})
res.send({ customToken })

How to resolve the type error issue in nestjs unit test?

I'm trying to create a jest test for the below method. And I got errors for two scenarios.
So basically in checkKioskUserPhone method,
Find the user by the phone number( commonService.findKioskUserByPhone)
In findKioskUserByPhone method, we are gonna find the user by the phone number and send error messages if it's unregistered or already registered.
And then return user.
(back to checkKioskUserPhone) if the user doesn't have auth code and pin number we are gonna send him/her auth code and return jwt, and etc.
async checkKioskUserPhone(kioskLoginDto: KioskLoginDto): Promise<ResponseDto<UserAuthDto>> {
const user = await this.commonService.findKioskUserByPhone(kioskLoginDto);
const isConfirmedAuthCode = user.authCode === 'OK' ? true : false;
const isSetPin = user.pin ? true : false;
if (!isConfirmedAuthCode && !isSetPin) {
await this.userService.authenticatePhone(user.id, Builder(AuthorizePhoneDto).phone(user.phone).build());
}
const jwtInfo = await this.createToken(this.removeCredentialField(user));
return Builder<ResponseDto<UserAuthDto>>(ResponseDto)
.result(Builder(UserAuthDto).isConfirmedAuthCode(isConfirmedAuthCode).isSetPin(isSetPin).jwtInfo(jwtInfo).build())
.build();
}
async findKioskUserByPhone(kioskLoginDto: KioskLoginDto): Promise<User> {
const user = await this.userService.findOne({ where: { phone: kioskLoginDto.phone } });
// throw Error message when unregistered phone attempt to login
if (!user) {
throw new NotFoundException('User not found');
}
// throw Error message when registered phone by whatsapp attempt to login
if (user.provider !== Provider.KIOSK) {
throw new ConflictException('You are already joined by Whatsapp.');
}
return user;
}
Jest code
it('when unregistered phone attempt to login', async () => {
const phone = '2212223333';
const kioskLoginDto = Builder(KioskLoginDto).phone(phone).build();
service.commonService.findKioskUserByPhone = jest.fn().mockResolvedValue(null);
try {
await service.checkKioskUserPhone(kioskLoginDto);
expect('here').not.toBe('here');
} catch (error) {
expect(error).toBeInstanceOf(NotFoundException);
expect(error.message).toContain('User not found');
}
});
it('When registered phone by app attempt to login', async () => {
const phone = '2212223333';
const kioskLoginDto = Builder(KioskLoginDto).phone(phone).build();
const user = Builder(User).phone(phone).provider(Provider.WHATSAPP).build();
service.commonService.findKioskUserByPhone = jest.fn().mockResolvedValue(user);
try {
await service.checkKioskUserPhone(kioskLoginDto);
expect('here').not.toBe('here');
} catch (error) {
expect(error).toBeInstanceOf(ConflictException);
expect(error.message).toContain('You are already joined by Whatsapp.');
}
});
Jest Error screenshot
you're overriding the findKioskUserByPhone method to just return null:
service.commonService.findKioskUserByPhone = jest.fn().mockResolvedValue(null);
so findKioskUserByPhone simply is never running, a mock function is just returning null, and is thus never throwing the error you expect. instead, here:
const user = await this.commonService.findKioskUserByPhone(kioskLoginDto);
user is getting set to null and here:
const isConfirmedAuthCode = user.authCode === 'OK' ? true : false;
you're trying access some authCode property of null, which throws the TypeError you're getting.
you probably meant to override the findOne method on the user service:
service.userService.findOne = jest.fn().mockResolvedValue(null);
so the error you want will actually throw in findKioskUserByPhone
(note I don't know if this is actually where you have the user service to provide the mock, I'm just assuming)

AWS Cognito User Pool check if there is a user with a specific Email

I want for my backend server (node.js) make a call through aws-sdk library to see if exists a user with specific mail. Is there a proper method to do this or a work arround without using user's credentials to do this procedure?
Yes this can be done the listUsers() AWS Javascript CognitoIdentityServiceProvider() API.
Call listUsers() to check if user exists
const cognito = new AWS.CognitoIdentityServiceProvider();
async function isEmailRegistered(email) {
//Check if user email is registered
var params = {
UserPoolId: 'eu-west-1_3bqeRjkSu', /* required */
AttributesToGet: [
'email',
],
Filter: "email = \"" + email + "\"",
};
return cognito.listUsers(params).promise();
}
Call and handle result
await isEmailRegistered(qsp.email).then( data => {
if (data.Users.length === 0) {
//User does not exist
} else {
//User does exist
}
}).catch(err => {
console.log("error: " + JSON.stringify(err))
});

Prevent users from signing up on their own with federated identity providers (FIP) but allow sign in with a FIP if added by an administrator

I've set up a user pool in Amazon Cognito for my web application. The application is not meant to be public and only specific users are allowed to sign in. The policies of that user pool in the Amazon Console allow only administrators to create new users.
I've implemented sign in through Facebook and Google. Cognito does indeed let users sign into the application with these federated identity providers, which is great. However, it seems that anybody with a Facebook or Google account can sign themselves up now.
So, on one hand, people can not create their own user with regular Cognito credentials but, on the other hand, they can create a new user in Cognito if they use a federated identity provider.
Is there a way to restrict signing into my application with Facebook or Google to only users that already exist in the user pool? That way, administrators would still be able to control who exactly can access the application. I would like to use the email shared by the federated identity provider to check if they are allowed to sign in.
The application is set up with CloudFront. I've written a Lambda that intercepts origin requests to check for tokens in cookies and authorize access based on the validity of the access token.
I would like to avoid writing additional code to prevent users to sign themselves up with Facebook or Google but if there is no other way, I'll update the Lambda.
So, here is the pre sign-up Lambda trigger I ended up writing. I took the time to use async/await instead of Promises. It works nicely, except that there is a documented bug where Cognito forces users who use external identity providers for the first time to sign up and then sign in again (so they see the auth page twice) before they can access the application. I have an idea on how to fix this but in the meantime the Lambda below does what I wanted. Also, it turns out that the ID that comes from Login With Amazon is not using the correct case, so I had to re-format that ID by hand, which is unfortunate. Makes me feel like the implementation of the triggers for Cognito is a bit buggy.
const PROVIDER_MAP = new Map([
['facebook', 'Facebook'],
['google', 'Google'],
['loginwithamazon', 'LoginWithAmazon'],
['signinwithapple', 'SignInWithApple']
]);
async function getFirstCognitoUserWithSameEmail(event) {
const { region, userPoolId, request } = event;
const AWS = require('aws-sdk');
const cognito = new AWS.CognitoIdentityServiceProvider({
region
});
const parameters = {
UserPoolId: userPoolId,
AttributesToGet: ['sub', 'email'], // We don't really need these attributes
Filter: `email = "${request.userAttributes.email}"` // Unfortunately, only one filter can be applied at once
};
const listUserQuery = await cognito.listUsers(parameters).promise();
if (!listUserQuery || !listUserQuery.Users) {
return { error: 'Could not get list of users.' };
}
const { Users: users } = listUserQuery;
const cognitoUsers = users.filter(
user => user.UserStatus !== 'EXTERNAL_PROVIDER' && user.Enabled
);
if (cognitoUsers.length === 0) {
console.log('No existing enabled Cognito user with same email address found.');
return {
error: 'User is not allowed to sign up.'
};
}
if (cognitoUsers.length > 1) {
cognitoUsers.sort((a, b) =>
a.UserCreateDate > b.UserCreateDate ? 1 : -1
);
}
console.log(
`Found ${cognitoUsers.length} enabled Cognito user(s) with same email address.`
);
return { user: cognitoUsers[0], error: null };
}
// Only external users get linked with Cognito users by design
async function linkExternalUserToCognitoUser(event, existingUsername) {
const { userName, region, userPoolId } = event;
const [
externalIdentityProviderName,
externalIdentityUserId
] = userName.split('_');
if (!externalIdentityProviderName || !externalIdentityUserId) {
console.error(
'Invalid identity provider name or external user ID. Should look like facebook_123456789.'
);
return { error: 'Invalid external user data.' };
}
const providerName = PROVIDER_MAP.get(externalIdentityProviderName);
let userId = externalIdentityUserId;
if (providerName === PROVIDER_MAP.get('loginwithamazon')) {
// Amazon IDs look like amzn1.account.ABC123DEF456
const [part1, part2, amazonId] = userId.split('.');
const upperCaseAmazonId = amazonId.toUpperCase();
userId = `${part1}.${part2}.${upperCaseAmazonId}`;
}
const AWS = require('aws-sdk');
const cognito = new AWS.CognitoIdentityServiceProvider({
region
});
console.log(`Linking ${userName} (ID: ${userId}).`);
const parameters = {
// Existing user in the user pool to be linked to the external identity provider user account.
DestinationUser: {
ProviderAttributeValue: existingUsername,
ProviderName: 'Cognito'
},
// An external identity provider account for a user who does not currently exist yet in the user pool.
SourceUser: {
ProviderAttributeName: 'Cognito_Subject',
ProviderAttributeValue: userId,
ProviderName: providerName // Facebook, Google, Login with Amazon, Sign in with Apple
},
UserPoolId: userPoolId
};
// See https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_AdminLinkProviderForUser.html
await cognito.adminLinkProviderForUser(parameters).promise();
console.log('Successfully linked external identity to user.');
// TODO: Update the user created for the external identity and update the "email verified" flag to true. This should take care of the bug where users have to sign in twice when they sign up with an identity provider for the first time to access the website.
// Bug is documented here: https://forums.aws.amazon.com/thread.jspa?threadID=267154&start=25&tstart=0
return { error: null };
}
module.exports = async (event, context, callback) => {
// See event structure at https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-identity-pools-working-with-aws-lambda-triggers.html
const { triggerSource } = event;
switch (triggerSource) {
default: {
return callback(null, event);
}
case 'PreSignUp_ExternalProvider': {
try {
const {
user,
error: getUserError
} = await getFirstCognitoUserWithSameEmail(event);
if (getUserError) {
console.error(getUserError);
return callback(getUserError, null);
}
const {
error: linkUserError
} = await linkExternalUserToCognitoUser(event, user.Username);
if (linkUserError) {
console.error(linkUserError);
return callback(linkUserError, null);
}
return callback(null, event);
} catch (error) {
const errorMessage =
'An error occurred while signing up user from an external identity provider.';
console.error(errorMessage, error);
return callback(errorMessage, null);
}
}
}
};
There is a way to do this but you will need to write some code - there is no out-of-the-box solution.
You will need to write a lambda and connect it to the Cognito Pre-Signup trigger.
https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-pre-sign-up.html
The trigger has three different sources of event; PreSignUp_SignUp, PreSignUp_AdminCreateUser and PreSignUp_ExternalProvider.
Your lambda should check you have the PreSignUp_ExternalProvider event. For these events, use the Cognito SDK to look the user up in your existing pool. If the user exists, return the event. If the user does not exist, return a string (error message).
I will paste my own Pre-Signup trigger here. It does not do what you need it to, but all the main components you need are there. You can basically hack it into doing what you require.
const AWS = require("aws-sdk");
const cognito = new AWS.CognitoIdentityServiceProvider();
exports.handler = (event, context, callback) => {
function checkForExistingUsers(event, linkToExistingUser) {
console.log("Executing checkForExistingUsers");
var params = {
UserPoolId: event.userPoolId,
AttributesToGet: ['sub', 'email'],
Filter: "email = \"" + event.request.userAttributes.email + "\""
};
return new Promise((resolve, reject) =>
cognito.listUsers(params, (err, result) => {
if (err) {
reject(err);
return;
}
if (result && result.Users && result.Users[0] && result.Users[0].Username && linkToExistingUser) {
console.log("Found existing users: ", result.Users);
if (result.Users.length > 1){
result.Users.sort((a, b) => (a.UserCreateDate > b.UserCreateDate) ? 1 : -1);
console.log("Found more than one existing users. Ordered by createdDate: ", result.Users);
}
linkUser(result.Users[0].Username, event).then(result => {
resolve(result);
})
.catch(error => {
reject(err);
return;
});
} else {
resolve(result);
}
})
);
}
function linkUser(sub, event) {
console.log("Linking user accounts with target sub: " + sub + "and event: ", event);
//By default, assume the existing account is a Cognito username/password
var destinationProvider = "Cognito";
var destinationSub = sub;
//If the existing user is in fact an external user (Xero etc), override the the provider
if (sub.includes("_")) {
destinationProvider = sub.split("_")[0];
destinationSub = sub.split("_")[1];
}
var params = {
DestinationUser: {
ProviderAttributeValue: destinationSub,
ProviderName: destinationProvider
},
SourceUser: {
ProviderAttributeName: 'Cognito_Subject',
ProviderAttributeValue: event.userName.split("_")[1],
ProviderName: event.userName.split("_")[0]
},
UserPoolId: event.userPoolId
};
console.log("Parameters for adminLinkProviderForUser: ", params);
return new Promise((resolve, reject) =>
cognito.adminLinkProviderForUser(params, (err, result) => {
if (err) {
console.log("Error encountered whilst linking users: ", err);
reject(err);
return;
}
console.log("Successfully linked users.");
resolve(result);
})
);
}
console.log(JSON.stringify(event));
if (event.triggerSource == "PreSignUp_SignUp" || event.triggerSource == "PreSignUp_AdminCreateUser") {
checkForExistingUsers(event, false).then(result => {
if (result != null && result.Users != null && result.Users[0] != null) {
console.log("Found at least one existing account with that email address: ", result);
console.log("Rejecting sign-up");
//prevent sign-up
callback("An external provider account alreadys exists for that email address", null);
} else {
//proceed with sign-up
callback(null, event);
}
})
.catch(error => {
console.log("Error checking for existing users: ", error);
//proceed with sign-up
callback(null, event);
});
}
if (event.triggerSource == "PreSignUp_ExternalProvider") {
checkForExistingUsers(event, true).then(result => {
console.log("Completed looking up users and linking them: ", result);
callback(null, event);
})
.catch(error => {
console.log("Error checking for existing users: ", error);
//proceed with sign-up
callback(null, event);
});
}
};

google cloud authentication with bearer token via nodejs

My client has a GraphQL API running on Google cloud run.
I have recieved a service account for authentication as well as access to the gcloud command line tool.
When using gcloud command line like so:
gcloud auth print-identity-token
I can generate a token that can be used to make post requests to the api. This works and I can make successful post requests to the api from postman, insomnia and from my nodejs app.
However, when I use JWT authentication with "googleapis" or "google-auth" npm libraries like so :
var { google } = require('googleapis')
let privatekey = require('./auth/google/service-account.json')
let jwtClient = new google.auth.JWT(
privatekey.client_email,
null,
privatekey.private_key,
['https://www.googleapis.com/auth/cloud-platform']
)
jwtClient.authorize(function(err, _token) {
if (err) {
console.log(err)
return err
} else {
console.log('token obj:', _token)
}
})
This outputs a "bearer" token:
token obj: {
access_token: 'ya29.c.Ko8BvQcMD5zU-0raojM_u2FZooWMyhB9Ni0Yv2_dsGdjuIDeL1tftPg0O17uFrdtkCuJrupBBBK2IGfUW0HGtgkYk-DZiS1aKyeY9wpXTwvbinGe9sud0k1POA2vEKiGONRqFBSh9-xms3JhZVdCmpBi5EO5aGjkkJeFI_EBry0E12m2DTm0T_7izJTuGQ9hmyw',
token_type: 'Bearer',
expiry_date: 1581954138000,
id_token: undefined,
refresh_token: 'jwt-placeholder'
}
however this bearer token does not work as the one above and always gives an "unauthorised error 401" when making the same requests as with the gcloud command "gcloud auth print-identity-token".
Please help, I am not sure why the first bearer token works but the one generated with JWT does not.
EDIT
I have also tried to get an identity token instead of an access token like so :
let privatekey = require('./auth/google/service-account.json')
let jwtClient = new google.auth.JWT(
privatekey.client_email,
null,
privatekey.private_key,
[]
)
jwtClient
.fetchIdToken('https://my.audience.url')
.then((res) => console.log('res:', res))
.catch((err) => console.log('err', err))
This prints an identity token, however, using this also just gives a "401 unauthorised" message.
Edit to show how I am calling the endpoint
Just a side note, any of these methods below work with the command line identity token, however when generated via JWT, it returns a 401
Method 1:
const client = new GraphQLClient(baseUrl, {
headers: {
Authorization: 'Bearer ' + _token.id_token
}
})
const query = `{
... my graphql query goes here ...
}`
client
.request(query)
.then((data) => {
console.log('result from query:', data)
res.send({ data })
return 0
})
.catch((err) => {
res.send({ message: 'error ' + err })
return 0
})
}
Method 2 (using the "authorized" client I have created with google-auth):
const res = await client.request({
url: url,
method: 'post',
data: `{
My graphQL query goes here ...
}`
})
console.log(res.data)
}
Here is an example in node.js that correctly creates an Identity Token with the correct audience for calling a Cloud Run or Cloud Functions service.
Modify this example to fit the GraphQLClient. Don't forget to include the Authorization header in each call.
// This program creates an OIDC Identity Token from a service account
// and calls an HTTP endpoint with the Identity Token as the authorization
var { google } = require('googleapis')
const request = require('request')
// The service account JSON key file to use to create the Identity Token
let privatekey = require('/config/service-account.json')
// The HTTP endpoint to call with an Identity Token for authorization
// Note: This url is using a custom domain. Do not use the same domain for the audience
let url = 'https://example.jhanley.dev'
// The audience that this ID token is intended for (example Google Cloud Run service URL)
// Do not use a custom domain name, use the Assigned by Cloud Run url
let audience = 'https://example-ylabperdfq-uc.a.run.app'
let jwtClient = new google.auth.JWT(
privatekey.client_email,
null,
privatekey.private_key,
audience
)
jwtClient.authorize(function(err, _token) {
if (err) {
console.log(err)
return err
} else {
// console.log('token obj:', _token)
request(
{
url: url,
headers: {
"Authorization": "Bearer " + _token.id_token
}
},
function(err, response, body) {
if (err) {
console.log(err)
return err
} else {
// console.log('Response:', response)
console.log(body)
}
}
);
}
})
You can find the official documentation for node OAuth2
A complete OAuth2 example:
const {OAuth2Client} = require('google-auth-library');
const http = require('http');
const url = require('url');
const open = require('open');
const destroyer = require('server-destroy');
// Download your OAuth2 configuration from the Google
const keys = require('./oauth2.keys.json');
/**
* Start by acquiring a pre-authenticated oAuth2 client.
*/
async function main() {
const oAuth2Client = await getAuthenticatedClient();
// Make a simple request to the People API using our pre-authenticated client. The `request()` method
// takes an GaxiosOptions object. Visit https://github.com/JustinBeckwith/gaxios.
const url = 'https://people.googleapis.com/v1/people/me?personFields=names';
const res = await oAuth2Client.request({url});
console.log(res.data);
// After acquiring an access_token, you may want to check on the audience, expiration,
// or original scopes requested. You can do that with the `getTokenInfo` method.
const tokenInfo = await oAuth2Client.getTokenInfo(
oAuth2Client.credentials.access_token
);
console.log(tokenInfo);
}
/**
* Create a new OAuth2Client, and go through the OAuth2 content
* workflow. Return the full client to the callback.
*/
function getAuthenticatedClient() {
return new Promise((resolve, reject) => {
// create an oAuth client to authorize the API call. Secrets are kept in a `keys.json` file,
// which should be downloaded from the Google Developers Console.
const oAuth2Client = new OAuth2Client(
keys.web.client_id,
keys.web.client_secret,
keys.web.redirect_uris[0]
);
// Generate the url that will be used for the consent dialog.
const authorizeUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: 'https://www.googleapis.com/auth/userinfo.profile',
});
// Open an http server to accept the oauth callback. In this simple example, the
// only request to our webserver is to /oauth2callback?code=<code>
const server = http
.createServer(async (req, res) => {
try {
if (req.url.indexOf('/oauth2callback') > -1) {
// acquire the code from the querystring, and close the web server.
const qs = new url.URL(req.url, 'http://localhost:3000')
.searchParams;
const code = qs.get('code');
console.log(`Code is ${code}`);
res.end('Authentication successful! Please return to the console.');
server.destroy();
// Now that we have the code, use that to acquire tokens.
const r = await oAuth2Client.getToken(code);
// Make sure to set the credentials on the OAuth2 client.
oAuth2Client.setCredentials(r.tokens);
console.info('Tokens acquired.');
resolve(oAuth2Client);
}
} catch (e) {
reject(e);
}
})
.listen(3000, () => {
// open the browser to the authorize url to start the workflow
open(authorizeUrl, {wait: false}).then(cp => cp.unref());
});
destroyer(server);
});
}
main().catch(console.error);
Edit
Another example for cloud run.
// sample-metadata:
// title: ID Tokens for Cloud Run
// description: Requests a Cloud Run URL with an ID Token.
// usage: node idtokens-cloudrun.js <url> [<target-audience>]
'use strict';
function main(
url = 'https://service-1234-uc.a.run.app',
targetAudience = null
) {
// [START google_auth_idtoken_cloudrun]
/**
* TODO(developer): Uncomment these variables before running the sample.
*/
// const url = 'https://YOUR_CLOUD_RUN_URL.run.app';
const {GoogleAuth} = require('google-auth-library');
const auth = new GoogleAuth();
async function request() {
if (!targetAudience) {
// Use the request URL hostname as the target audience for Cloud Run requests
const {URL} = require('url');
targetAudience = new URL(url).origin;
}
console.info(
`request Cloud Run ${url} with target audience ${targetAudience}`
);
const client = await auth.getIdTokenClient(targetAudience);
const res = await client.request({url});
console.info(res.data);
}
request().catch(err => {
console.error(err.message);
process.exitCode = 1;
});
// [END google_auth_idtoken_cloudrun]
}
const args = process.argv.slice(2);
main(...args);
For those of you out there that do not want to waste a full days worth of work because of the lack of documentation. Here is the accepted answer in today's world since the JWT class does not accept an audience in the constructor anymore.
import { JWT } from "google-auth-library"
const client = new JWT({
forceRefreshOnFailure: true,
key: service_account.private_key,
email: service_account.client_email,
})
const token = await client.fetchIdToken("cloud run endpoint")
const { data } = await axios.post("cloud run endpoint"/path, payload, {
headers: {
Authorization: `Bearer ${token}`
}
})
return data