check MFA is enabled for a user using rest api - node.js

How do I check MFA is enabled for AD users using rest API loginWithServicePrincipalSecret
is there anyone who can help me out to do this....I want to do this using node sdk like this
require("isomorphic-fetch");
const { UserAgentApplication } = require("msal");
const { ImplicitMSALAuthenticationProvider } = require("#microsoft/microsoft-graph-client/lib/src/ImplicitMSALAuthenticationProvider");
const { MSALAuthenticationProviderOptions } = require("#microsoft/microsoft-graph-client/lib/src/MSALAuthenticationProviderOptions");
const msalConfig = {
auth: {
clientId: "bec52b71-dc94-4577-9f8d-b8536ed0e73d", // Client Id of the registered application
},
};
const graphScopes = ["user.read", "mail.send"]; // An array of graph scopes
const msalApplication = new UserAgentApplication(msalConfig);
const Options = new MSALAuthenticationProviderOptions(graphScopes);
const authProvider = new ImplicitMSALAuthenticationProvider(
msalApplication,
Options
);
const options = {
authProvider,
};
const Client = require("#microsoft/microsoft-graph-client");
const client = Client.init(options);
async function test() {
try {
let res = await client
.api("/reports/credentialUserRegistrationDetails")
.version("beta")
.get();
console.log("res: ", res);
} catch (error) {
throw error;
}
}
test();

This is possible with MS Graph API,
To Get information of users registered with MFA and hasn't, we can use isMfaRegistered property in credentialUserRegistrationDetails .
credentialUserRegistrationDetails help us to get the details of the
usage of self-service password reset and multi-factor authentication
(MFA) for all registered users. Details include user information,
status of registration, and the authentication method used.
This is possible programmatically with MS Graph where you will get a JSON reports an can be plugged into other reports or can be represented programmatically itself
Example:
GET https://graph.microsoft.com/beta/reports/credentialUserRegistrationDetails
sample output:
{
"id": "****************************",
"userPrincipalName": "NKS#nishantsingh.live",
"userDisplayName": "Nishant Singh",
"isRegistered": false,
"isEnabled": true,
"isCapable": false,
"isMfaRegistered": true,
"authMethods": [
"mobilePhone"
]
}
Sample code for your Node JS,
const options = {
authProvider,
};
const client = Client.init(options);
let res = await client.api('/reports/credentialUserRegistrationDetails')
.version('beta')
.get();
To implement your NodeJS code please go through step-by-step guide in MS Documentation

Related

How to use service account to authenticate google workspace admin api?

I obtained a service account JSON file and also attached domain wide delegation permissions to that service account. Next I set the service account file path using the GOOGLE_APPLICATION_CREDENTIALS env variable. After that I tried to access google groups of the domain like this:
import { google } from 'googleapis';
const admin = await google.admin({
version: 'directory_v1',
});
const groupsResponse = await admin.groups.list({
domain: process.env.GOOGLE_DOMAIN,
});
This gives me the following error:
Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.
What am I missing here?
You need to apply the client to the service object.
auth: client
you may want to check out using-the-keyfile-property
Try this
const google = require("googleapis").google;
const SRVC_ACCOUNT_CREDS = require('./keys.json');
const getClient = async (scopes: string[], user: string)=>{
const auth = new google.auth.GoogleAuth({
credentials: SRVC_ACCOUNT_CREDS,
scopes: scopes
});
const client = await auth.getClient();
client.subject = user;
return client;
};
const listUsers = async (query = "", limit = 500, pageToken = null, user, fields, getAll = false)=>{
const scopes = ["https://www.googleapis.com/auth/admin.directory.user"];
const client = await getClient(scopes, user);
const service = google.admin({version: "directory_v1", auth: client});
const result = {
users: [],
nextPageToken: ""
};
if(!fields) {
fields = "users(name.fullName,primaryEmail,organizations(department,primary,title),thumbnailPhotoUrl),nextPageToken";
}
do{
const request = await service.users.list({
customer: "my_customer",
fields: fields,
orderBy: "givenName",
maxResults: limit,
pageToken: pageToken,
query: query,
viewType: "admin_view"
});
pageToken = getAll ? request.data.nextPageToken : null;
const users = request.data.users;
if(users && users.length){
result.users.push(...users);
result.nextPageToken = request.data.nextPageToken;
}
} while(pageToken);
return result;
};

Setting IAM Policy on a function using projects.locations.functions.setIamPolicy Gives error

I'm trying to set IAM policy using projects.locations.functions.setIamPolicy, on a function which I created and deployed, but I keep receiving the following error
GaxiosError: Permission 'cloudfunctions.functions.setIamPolicy' denied on resource '
Here is my code in Node.js
const { auth } = require('google-auth-library');
const { google } = require('googleapis');
const fs = require('fs');
const path = require('path');
function getCredentials() {
const filePath = path.join(__dirname, 'mykeyfile.json');
console.log(filePath);
if (fs.existsSync(filePath)) {
let rawdata = fs.readFileSync(filePath);
let jsonData = JSON.parse(rawdata);
return jsonData;
}
return null;
}
async function setPolicy() {
try {
const credentials = getCredentials();
if (!credentials)
return;
const client = auth.fromJSON(credentials);
client.scopes = ['https://www.googleapis.com/auth/cloud-platform'];
const cloudfunctions = await google.cloudfunctions({
version: 'v1',
auth: client
});
const request = {
// REQUIRED: The resource for which the policy is being specified.
// See the operation documentation for the appropriate value for this field.
resource_: "projects/{myprorjectid}/locations/{zone}/functions/function-1",
resource: {
"policy": {
"etag": "BwWP3fXnMuQ=",
"version": 1,
"bindings": [
{
"members": [
"allUsers"
],
"role": "roles/cloudfunctions.invoker"
}
]
}
},
auth: client,
};
const response = (await cloudfunctions.projects.locations.functions.setIamPolicy(request)).data;
console.log(JSON.stringify(response, null, 2));
} catch (err) {
console.log(err);
}
}
setIamPolicy();
Any help is much appreciated. Not interested in command like options. Must be done in Node.
It seems that the service account you're using on your code doesn't have the right permissions. The error is similar to this doc and the solution is to add Project Owner or Cloud Functions Admin role to your service account, as both contain the cloudfunctions.functions.setIamPolicy permission.

Access azure billing API

I would like to create a dashboard with graphs about costs of my azure resources (as detailed as possible). Meaning, a list of monthly invoices is not enough (but I would already be very happy if I a could achieve that!!)
Anyway, the first thing I noticed is that if you find an example the endpoint urls look like this
https://management.azure.com/subscriptions/${subscriptionId}/resourcegroups?api-version=2016-09-01
Check the end of the url 2016-09-01, doesn't look very up2date. This medium post was the best article I could find, but it also uses these urls.
Furthermore, I was not able to follow the steps described, first it uses postman to retrieve an access_token (not very useful for me because I need it automated) and second, somewhere in the middle an access_token is retrieved but never used.
So, I found a npm packages like [azure-arm-billing][2] from which I was able to write the following program (mostly copy-paste):
const msRestAzure = require('ms-rest-azure');
const BillingManagement = require('azure-arm-billing')
const clientId = process.env['CLIENT_ID'];
const secret = process.env['APPLICATION_SECRET'];
const domain = process.env['DOMAIN'];
const subscriptionId = process.env['AZURE_SUBSCRIPTION_ID'];
// Retrieve access_token
const app = new msRestAzure.ApplicationTokenCredentials(clientId, domain, secret);
app.getToken((err, token) => {
console.log(token.accessToken);
});
// =======
msRestAzure
.interactiveLogin( { domain }) // The argument here is nowhere documented
.then(credentials => {
console.log(credentials);
let client = new BillingManagement(credentials, subscriptionId);
return client.invoices.list();
})
.then(invoices => {
console.log('List of invoices:');
console.dir(invoices, { depth: null, colors: true });
});
Running this shows a nice access_token and invoices
...
List of invoices:
[
{
id: '/subscriptions/../providers/Microsoft.Billing/invoices/....',
name: '...',
type: 'Microsoft.Billing/invoices',
invoicePeriodStartDate: 2019-08-25T00:00:00.000Z,
invoicePeriodEndDate: 2019-09-24T00:00:00.000Z,
billingPeriodIds: [
'/subscriptions/.../pr..s/Micro..ing/bill..ods/201910-1'
]
},
{
id: '/subscriptions/9ea...3d/providers/Microsoft.Billing/invoices/201909-...',
name: '....',
type: 'Microsoft.Billing/invoices',
invoicePeriodStartDate: 2019-07-25T00:00:00.000Z,
invoicePeriodEndDate: 2019-08-24T00:00:00.000Z,
billingPeriodIds: [
'/subscriptions/..../providers/Microsoft.Billing/billingPeriods/201909-1...'
]
}
]
Although I have my invoices, there are no numbers. And I would like to retrieve costs for every resources.
So the documentation seems to be outdated up to not existing for what I want (as it seems). My question is if someone was able to retrieve information like this? I would really like to know how!!
UPDATE
It seems to be a permission issue. So, below I share some screenshots showing what I have right now. Maybe from these it is clear what I miss or have setup incorrectly. So first, here is my latest nodejs app:
const msRestAzure = require("ms-rest-azure");
const ConsumptionManagementClient = require("azure-arm-consumption");
const clientId = '76d79....'; // App registration ID
const secret = '****...'; // App registration secret
const domain = 'dc36...'; // tenantId
const subscriptionId = '9ea2d...'; // subscription ID
const AzureServiceClient = msRestAzure.AzureServiceClient;
//an example to list resource groups in a subscription
msRestAzure.loginWithServicePrincipalSecret(clientId, secret, domain).then((creds) => {
const client = new ConsumptionManagementClient(creds, subscriptionId);
const expand = '';
const filter = '';
const skiptoken = '';
const top = 1000;
const apply = '';
return client.usageDetails.list(expand, filter, skiptoken, top, apply).then(result => {
console.log('The result is:', result);
});
}).catch((err) => {
console.log('An error occurred:');
console.dir(err, { depth: null, colors: true });
});
Which outputs a statusCode 401
Error: Unauthorized. Request ID: e6b127...
...
So, I have in AD an App registration
Its API permissions are
Finally, I have just one subscription
With the following IAM settings
Any suspicious?
If you're looking for resource costs, I would suggest that you take a look at Consumption API - List Usage Details. That will give you the consumption for all the resources.
You will need to install azure-arm-consumption package.
Here's the sample code:
const msRestAzure = require("ms-rest-azure");
const ConsumptionManagementClient = require("azure-arm-consumption");
msRestAzure.interactiveLogin().then((creds) => {
const subscriptionId = "<your subscription id>";
const client = new ConsumptionManagementClient(creds, subscriptionId);
const expand = "";
const filter = "";
const skiptoken = "";
const top = 1000;
const apply = "";
return client.usageDetails.list(expand, filter, skiptoken, top, apply).then((result) => {
console.log("The result is:");
console.log(result);
});
}).catch((err) => {
console.log('An error occurred:');
console.dir(err, {depth: null, colors: true});
});
This is taken from here: https://github.com/Azure/azure-sdk-for-node/tree/master/lib/services/consumptionManagement.

Firestore rules not picking up custom claims

I can't get custom claims to work in the firestore rules.
I'm using nodeJS (local) to set the custom claims and initialize with the service-account from firebase. The user token is automatically added to the request headers and validates fine on node.
// Initialize
admin.initializeApp({
credential: admin.credential.cert(serviceAccount as admin.ServiceAccount), // Typing is wrong google!
databaseURL: `https://${serviceAccount.project_id}.firebaseio.com`
});
// Add custom claims for additional privileges.
const payload = await admin.auth().setCustomUserClaims(decodedToken.sub, {
customClaims })
.then(() => ({ ...decodedToken, customClaims }))
.catch(() => void 0);
if (!payload) { res.status(401).json({ error: 'Error setting custom claims on token' }); return; }
Custom claims object:
// Define custom claims
const customClaims: CustomClaims = {
serverAuth: true,
domain: domainOfUser,
developer: isDeveloper,
admin: isAdmin,
};
Angular Fire 2: User logs in with google redirect then refresh the token:
if (!this.firebaseAuth.auth.currentUser) { return Promise.reject('User object not found in fireAuth service'); }
return this.firebaseAuth.auth.currentUser.getIdToken(true);
When that's al done I do: (the fireAuthService is a custom service that handles some auth stuff)
// On user change
this.fireAuthService.user$.pipe(
map(userAuth => { if (!userAuth) { this.userSource.next(null); } return userAuth; }),
filter(notNullOrUndefined),
switchMap(async userAuth => {
const userDoc = this.userCollection.doc<UserDb>(userAuth.uid);
const exists = await userDoc.get().toPromise().then(user => user.exists)
.catch(() => this.fireAuthService.signOut());
if (!exists) {
const res = await this.serverService.createNewUser(userAuth).catch(() => void 0);
if (!res) { this.fireAuthService.signOut(); }
}
return userAuth;
}),
switchMap(userAuth => this.userCollection.doc<UserDb>(userAuth.uid).valueChanges())
).subscribe(async userDb => {
await this.fireAuthService.getAuthToken();
const isAdmin = await this.fireAuthService
.getTokenPayload()
.then(payload => (payload.claims.customClaims as CustomClaims).admin);
this.userSource.next(new CurrentUser(userDb, this.serverService, isAdmin));
runAngularFire();
});
On the payload are all my custom claims at this point. The firestore calls on the user doc firestore calls are secured by only checking the uid in the firestore rules and this works.
At this point I set up my listeners. They fail with the error:
Missing or insufficient permissions.
The firestore rules are setup as followed:
service cloud.firestore {
match /databases/{database}/documents {
// Allow users to read documents in the user's collection
match /users/{userId} {
allow read: if request.auth.token.sub == userId;
}
// Allow only reads to the db
match /{document=**} {
allow read: if request.auth.token.serverAuth == true;
}
}
I've tried just about anything and I'm at a loss. Any suggestion?
Many thanks in advance!
Edit: I also checked the token send out on channel?database=... This token has the custom claims...
After a night of sleep I noticed my error:
const payload = await admin.auth().setCustomUserClaims(decodedToken.sub, { customClaims });
To:
const payload = await admin.auth().setCustomUserClaims(decodedToken.sub, customClaims);
Also I did test the rules on a object. Objects probably don't work in rules.

Pass user claims to downstream (secondary) APIs?

I have tried searching, but surprisingly cannot find an answer to my question.
I am designing a web app, that will have a front end interface via Angular, with multiple down stream APIs. Like below:
[API - A Client] -> [API - A] -> [API - B]
I am using IdentityServer4 for authentication / authorization. Some users will have a particular claim, lets call it "Foo," and that claim gets correctly passed from the auth server to API A (using Implicit flow) when interacting with API A via the SPA client.
However, I can't get that claim to be passed along from API A to API B, which is using Client Credentials. From what I have read / research, this seems to be correct behavior, since its Client Credential flow.
So my question is, how can I pass a User claim ("Foo"), downstream to a second layer API (API-B)? do i need to use a different flow? Should API-A manually pass it along the request to API-B?
This is my first time using IdentityServer / OpenID connect / OAuth, I am open to changes.
IdentityServer4 Config
public class Config
{
public static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource("API-B", "API B")
{
UserClaims = { "Foo" }
},
new ApiResource("API-A", "API A")
{
ApiSecrets = {new Secret("Secret") },
UserClaims = { "Foo", },
}
};
}
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
new Client
{
ClientName = "API-A Client",
ClientId = "API-A_client",
AllowedGrantTypes = GrantTypes.Implicit,
RedirectUris = { "http://localhost:7900/swagger/oauth2-redirect.html" },
PostLogoutRedirectUris = { "http://localhost:7900/" },
RequireConsent = false,
AllowAccessTokensViaBrowser = true,
AllowedScopes = new List<string>(){
"API-A",
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile
}
},
new Client
{
ClientName = "API-A Backend",
ClientId = "API-A_backend",
AllowedGrantTypes = GrantTypes.ClientCredentials,
ClientSecrets = {new Secret("Secret".Sha256()) },
AllowedScopes = new List<string>()
{
"API-B",
"custom_resource",
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile
},
AlwaysIncludeUserClaimsInIdToken = true,
AlwaysSendClientClaims = true,
}
};
}
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResource("custom_resource", new [] { "Foo" }),
};
}
}
API A Auth Config
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:6900";
options.ApiName = "API-A";
options.RequireHttpsMetadata = false; // dev only!
});
services.AddTransient<AccessTokenDelegatingHandler>((service) => new AccessTokenDelegatingHandler(tokenEndpoint: $"http://localhost:6900/connect/token", clientId: "API-A", clientSecret: "Secret", scope: "API-B"));
services.AddHttpClient<ApiBHttpClient>(client =>
{
client.BaseAddress = new Uri(Configuration["ApiBUri"]);
client.DefaultRequestHeaders.Add("Accept", "application/json");
})
.AddHttpMessageHandler<AccessTokenDelegatingHandler>();
API B Auth Config
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:6900";
options.ApiName = "API-B"; // required audience of access tokens
options.RequireHttpsMetadata = false; // dev only!
options.ApiSecret = "Secret";
});
The result above is API-A correctly gets access to "Foo" via IdentityClaims, however API-B does not (although the call is successful).
Any help is appreciated!
Finally found this GitHub page, asking the same question: https://github.com/IdentityServer/IdentityServer4/issues/1679
Which leads here, about extension grants, http://docs.identityserver.io/en/release/topics/extension_grants.html which is my exact scenario.

Resources