The following piece of code works locally (i.e. on my computer, while logged in on gcloud with a personal account):
import { initializeApp, applicationDefault } from 'firebase-admin/app'
initializeApp({
credential: applicationDefault(),
projectId: project,
})
But it fails when running from the CI. For the CI login, I'm using oidc authentication with a service account. Running gcloud auth list from the CI shows that it's properly logged in. I have credentials file set up, and can get a oauth2 token. When running the above code, however, I got the following error:
{
...
errorInfo: {
code: 'app/invalid-credential',
message: 'Invalid contents in the credentials file'
},
codePrefix: 'app',
}
I tried using the refreshToken function istead of applicationDefault, but it seems the credentials that I got from oidc-login do not match the requirements. They look like
{
"type": "external_account",
"audience": "//iam.googleapis.com/projects/xxx/locations/global/workloadIdentityPools/xxx/providers/xxx",
"subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
"token_url": "https://sts.googleapis.com/v1/token",
"service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/xxx#xxx.iam.gserviceaccount.com:generateAccessToken",
"credential_source": {
"url": "https://pipelines.actions.githubusercontent.com/xxx",
"headers": { "Authorization": "xxx" },
"format": { "type": "json", "subject_token_field_name": "value" }
}
}
I also tried to create a Credential object manually, by passing an access token (gcloud auth print-access-token does generate an oauth2 token), but I couldn't make anything work so far.
It looks to me that I have credentials that work with gcloud cli, but I can't do anything with them in nodejs. Any Idea on how to do this properly ?
Related
I'm trying to verify Azure AD token with my NestJS backend application. I'm logging to Azure AD using React frontend application and, for now, grab access_token from the response manually. Then I have this guard in NestJS:
#Injectable()
export class AzureADStrategy extends PassportStrategy(
BearerStrategy,
'azure-ad',
) {
constructor() {
super({
identityMetadata: `https://login.microsoftonline.com/${tenantID}/v2.0/.well-known/openid-configuration`,
clientID,
clientSecret,
loggingLevel: 'debug',
loggingNoPII: false
});
}
async validate(response: any) {
console.log(response);
}
}
export const AzureGuard = AuthGuard('azure-ad');
When i apply it on some endpoint i'm trying to fetch this URL, like:
curl localhost:9000/test --header 'Authorization: Bearer xyz'
But i'm not able to authenticate and i get this error log:
{"name":"AzureAD: Bearer Strategy","hostname":"<hostname>","pid":1713974,"level":30,"msg":"authentication failed due to: invalid signature","time":"2022-11-03T13:00:51.213Z","v":0}
How should i configure it to make it work?
I'm assuming you've been able to login ok and then pass the details to the API and you're user/s are registered under the APP.
On validate, this is what i have and works fine.
async validate(data) {
return data;
}
This is the example i followed to get it working - https://medium.com/adidoescode/azure-ad-for-user-authentication-with-vue-and-nestjs-4dab3e96d240
I have an Azure app registered . I am trying to authenticate to that app . I am able to do that and successfully get the accesstoken and idtoken.
However, when I use that token and try to make a request to list subscriptions API (https://management.azure.com/subscriptions?api-version=2020-01-01) , the request fails and give response "AuthenticationFailed". I have also tried changing the scope to https://management.azure.com/.default but the same error is there. Below is the nodejs code and I am also attaching the API permissions of app
const config = {
auth: {
clientId: 'xxx',
authority: 'https://login.microsoftonline.com/organizations',
clientSecret: 'yyy',
},
system: {
loggerOptions: {
loggerCallback(loglevel, message, containsPii) {
console.log(message);
},
piiLoggingEnabled: false,
logLevel: msal.LogLevel.Verbose,
},
},
};
// Create msal application object
const pca = new msal.ConfidentialClientApplication(config);
// Create Express App and Routes
const app = express();
app.get('/', (req, res) => {
const authCodeUrlParameters = {
scopes: ['user.read','https://management.azure.com/user_impersonation'],
redirectUri: REDIRECT_URI,
};
// get url to sign user in and consent to scopes needed for application
pca
.getAuthCodeUrl(authCodeUrlParameters)
.then((response) => {
res.redirect(response);
})
.catch((error) => console.log(JSON.stringify(error)));
});
The response I am getting is
{
"error": {
"code": "AuthenticationFailed",
"message": "Authentication failed."
}
}
The error "AuthenticationFailed" usually occurs if you are using different scope token to call the API.
I tried to generate access token with the same scope as you
via Postman and got the same error while calling the query like below:
Please note that,
user.read audience is Microsoft Graph API
https://management.azure.com/user_impersonation audience is Azure Service Management.
As you have given two different scopes with different audiences, it will consider the first scope (user.read) to generate the token as mentioned in this SO Thread which was solved by me.
When you call the query https://management.azure.com/subscriptions?api-version=2020-01-01 with the above token, you will get the error as it is intended for MS Graph audience.
I tried to generate the token with scope https://management.azure.com/user_impersonation only, removing user.read like below:
With the above generated token, I am able to call the API successfully like below:
If you want token with different scopes, then you have to generate two access tokens separately.
We are currently switching to an OAuth based authorization solution based on the #azure/msal-node package to authorize our API's with the ones provided by Dynamics 365 Business Central (19.5, Cloud). Somehow I can not get it to work. Our tenant instance just returns a 401 when requesting any resource using the retrieved token.
Really reached a dead end here, any help would be greatly appreciated.
Permissions set in Azure's "App Registrations" section
And yes, I have consented all of those Permissions on behalf of our tenant (admin consent)
API.ReadWrite.All
app_access
Automation.ReadWrite.All
The token even contains following scopes when decoded over at jwt.ms:
{
"scopes": [
"Automation.ReadWrite.All",
"app_access",
"API.ReadWrite.All"
]
}
Example of our implementation
/**
* Uses `msal-node` to authenticate against the microsoft servers
* to gain access to Dynamics 365 Business Central
*/
async getClientCredentialsToken() {
try {
const validity = await this.validateClientCredentialsToken();
if (validity) return;
const authOptions = {
clientId: process.env.AAD_CLIENT_ID,
authority: process.env.AAD_AUTHORITY,
clientSecret: process.env.AAD_CLIENT_SECRET,
};
const cacheOptions = {
Account: {},
IdToken: {},
AccessToken: {},
RefreshToken: {},
AppMetadata: {},
};
const cca = new msal.ConfidentialClientApplication({
cache: cacheOptions,
auth: authOptions,
});
const response = await cca.acquireTokenByClientCredential({
azureRegion: null,
skipCache: true,
scopes: ["https://api.businesscentral.dynamics.com/.default"],
});
return response;
} catch (err) {
console.log(err);
}
},
Example of a response when requesting an existing endpoint
No matter what endpoint is hit. This is the response I get for it:
{
"error": {
"code": "Authentication_InvalidCredentials",
"message": "The server has rejected the client credentials. CorrelationId: b004d293-f576-40c9-bbc6-3fb32533a65b."
}
}
Solution found inside the Docs
So for anybody stumbling upon the same scenario I have found something that finally worked! You need to register the application inside the Dynamics 365 Business Central client as described inside the official documentation.
Apologies for my previous badly formulated question.
I am trying to write a standalone NodeJS app, that retrieves activity data from the Google Fit REST API and writes it locally as a json file. The app will run unattended on a headless raspberry pi, so there are no "browser pop-up" windows in order to authenticate with a Google account.
I activated my Google Fit API and created service account credentials. My key is stored as MY_KEY.json in this example. Now using the googleapis library, I create a JWT token signed with MY_KEY.json, and authenticate with it when sending my request.
I get a response from the API, but the data is empty. (I know there is data, since doing a similar request in a browser with a traditional oauth2 flow returns all my activity data sessions. I wonder if authentication with JWT tokens using a service account is allowed for fitness data ?
Here is my code :
'use strict';
const {google, fitness_v1} = require('googleapis');
const path = require('path');
const fs = require('fs');
async function runSample() {
// Create a new JWT client using the key file downloaded from the Google Developer Console
const auth = new google.auth.GoogleAuth({
keyFile: path.join(__dirname, 'MY_KEY.json'),
scopes: 'https://www.googleapis.com/auth/fitness.activity.read',
});
const client = await auth.getClient();
// Obtain a new fitness client, making sure you pass along the auth client
const fitness_v1 = google.fitness({
version: 'v1',
auth: client
});
//console.log(client);
const res = await fitness_v1.users.sessions.list({
"userId": "me"
});
fs.writeFile('session.json', JSON.stringify(res.data, null, 4), (err) => {
if (err) {
throw err;
}
console.log("Retrieved sessions are saved as JSON.");
});
console.log(res.data);
return res.data;
}
if (module === require.main) {
runSample().catch(console.error);
}
// Exports for unit testing purposes
module.exports = {runSample};
The response is :
{
session: [],
deletedSession: [],
nextPageToken: 'XmRh96blablablan4yFjZQ'
}
It should be :
{
"session": [
{
"id": "healthkit-D7B3AC93-3739-4E04-978A-C53599D8401E",
"name": "Uni",
"description": "",
"startTimeMillis": "1645651234280",
"endTimeMillis": "1645676584280",
"modifiedTimeMillis": "1645676989684",
"application": {
"packageName": "com.apple.workout",
"version": "",
"detailsUrl": ""
},
"activityType": 72
},
{
"id": "healthkit-3691B45B-9D51-4C14-ACC6-EC9DB48B5F23",
"name": "Juoksu",
"description": "",
"startTimeMillis": "1647073527249",
"endTimeMillis": "1647075778248",
"modifiedTimeMillis": "1647076769108",
"application": {
"packageName": "runkeeperpro",
"version": "",
"detailsUrl": ""
},
"activityType": 56
}
],
"deletedSession": [],
"nextPageToken": "XmRh96blablablan4yFjZQ"
}
Any suggestions ? Thanks in advance.
According to Addendum: Service account authorization without OAuth:
With some Google APIs, you can make authorized API calls using a signed JWT directly as a bearer token, rather than an OAuth 2.0 access token. (snip)
If the API you want to call has a service definition published in the Google APIs GitHub repository, you can make authorized API calls using a JWT instead of an access token.
There is no such service definition for Fit, so, unfortunately, the answer is that you can't use JWT.
I use NodeJS to communicate with Firebase's Realtime Database.
The rules are defined so only an admin account can read and write:
{
"rules": {
".read": "auth.uid === 'ADMIN_UID'",
".write": "auth.uid === 'ADMIN_UID'"
}
}
When the NodeJS Express server initializes I log in with the admin credentials.
On the web application I use Firebase's "Login with facebook" option:
const provider = new firebase.auth.FacebookAuthProvider();
provider.setCustomParameters({
display: "popup",
});
firebase
.auth()
.signInWithPopup(provider)
Everything works perfectly.
Now, I am trying to extract the User Id (uid) of the user making the requests from the Web Application. When I run the following line:
const user = firebase.auth().currentUser;
I get the admin user rather than the user making the requests. This is expected I guess. My question is how do I get the UserId without sending it explicitly to avoid a security concern?
I've managed to accomplish this with Firebase Admin SDK.
As described in this guide, you'd need to:
Generate a private key for your service account
In the Firebase console, open Settings > Service Accounts.
Click Generate New Private Key, then confirm by clicking Generate Key.
Securely store the JSON file containing the key.
Set the environment variable
Add to your .bashrc or .zshrc:
export GOOGLE_APPLICATION_CREDENTIALS="/home/user/Downloads/service-account-file.json"
- In NodeJS initialize the SDK:
admin.initializeApp({
credential: admin.credential.applicationDefault(),
databaseURL: 'https://<DATABASE_NAME>.firebaseio.com'
});
In your client send the JWT token in the header:
const token = await firebase.auth().currentUser.getIdToken()
fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: token
},
body: ...
});
In your server decrypt the token and get the UID:
return admin
.auth()
.verifyIdToken(token)
.then(function(decodedToken) {
var uid = decodedToken.uid;
console.log("uid ->", uid);
return uid;
})
.catch(function(error) {
console.log("error ->", error);
// Handle error
});
That's it. Apparently working with Firebase via your own NodeJS domain is called "using your custom backend". This means the default usage for them is without a NodeJS or other backend middleman.
Usually with firebase you can include access the context of the request being made and the userId is available.
Something like the following:
context.params.userId