I've made an attempt to get Azure Static Web Apps with customized Roles function to work in my environment just as specified by: https://learn.microsoft.com/en-us/azure/static-web-apps/assign-roles-microsoft-graph
Everything seems to work as expected but when visiting the page restricted by a specific role the API doesn't seem to be assigning the expected role.
I've modified the API and removed all the logic to assign a role to everyone logging in and still doesn't work. Here's the modified code:
const fetch = require('node-fetch').default;
module.exports = async function (context, req) {
const user = req.body || {};
const roles = [];
roles.push('superuser');
context.res.json({
roles
});
}
Here's my staticwebapp.config.json file:
{
"auth": {
"rolesSource": "/api/GetRoles",
"identityProviders": {
"azureActiveDirectory": {
"userDetailsClaim": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name",
"registration": {
"openIdIssuer": "https://login.microsoftonline.com/44263d43-a2f0-45a8-8f55-9b100ecfb4dc",
"clientIdSettingName": "AAD_CLIENT_ID",
"clientSecretSettingName": "AAD_CLIENT_SECRET"
},
"login": {
"loginParameters": ["resource=https://graph.microsoft.com"]
}
}
}
},
"routes": [
{
"route": "/secured/*",
"allowedRoles": ["superuser"]
},
{
"route": "/admin/*",
"allowedRoles": ["administrator"]
},
{
"route": "/contributors/*",
"allowedRoles": ["contributor", "Contributor"]
}
],
"responseOverrides": {
"401": {
"redirect": "/.auth/login/aad?post_login_redirect_uri=.referrer",
"statusCode": 302
}
}
}
I've tried changing the order of the config file. My last attempt before posting was to remove all logic and just assign everyone the 'superuser' role.
Everyone can login successfully & pre-defined roles work like a charm but no one ever gets the 'superuser' role.
I'm trying to figure out what I'm doing wrong or has Azure Static Web Apps changed so that this code just won't work like it did a year ago?
Thank you help in advance.
Related
I could authenticate swagger on Azure AD B2C. It works fine on http://localhost:5078/ . But as soon as I push my website to azure I can not authenticate swagger. It still uses localhost:5078 as redirect URL and not azure website URL.
My code:
builder.Services.AddSwaggerGen(c => {
var OATH2 = "oauth2";
// Enabled OAuth security in Swagger. From here: https://stackoverflow.com/questions/66894523/swagger-not-able-to-authenticate-to-azure-ad-b2c
c.AddSecurityRequirement(new OpenApiSecurityRequirement() {
{
new OpenApiSecurityScheme {
Reference = new OpenApiReference {
Type = ReferenceType.SecurityScheme,
Id = OATH2
}
},
new List<string>()
}
});
c.AddSecurityDefinition(OATH2, new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OAuth2,
Flows = new OpenApiOAuthFlows
{
Implicit = new OpenApiOAuthFlow()
{
AuthorizationUrl = new Uri($"{builder.Configuration["AzureAdB2C:Instance"]}/{builder.Configuration["AzureAdB2C:Domain"]}/{builder.Configuration["AzureAdB2C:SignUpSignInPolicyId"]}/oauth2/v2.0/authorize"),
TokenUrl = new Uri($"{builder.Configuration["AzureAdB2C:Instance"]}/{builder.Configuration["AzureAdB2C:Domain"]}/{builder.Configuration["AzureAdB2C:SignUpSignInPolicyId"]}/oauth2/v2.0/token"),
Scopes = new Dictionary<string, string>() { { builder.Configuration["AzureAdB2C:Scope"], "backend all" } }
}
}
});
});
....
app.UseSwagger();
app.UseSwaggerUI(c => {
c.OAuthClientId(builder.Configuration["AzureAdB2C:SwaggerClientId"]);
c.OAuthScopes(builder.Configuration["AzureAdB2C:Scope"]);
c.OAuthUsePkce();
});
OK on localhost:
Not OK on azure:
https://anna-carat-auth-test.azurewebsites.net/swagger/index.html
Both redirect URLs are registered in app:
How to set a proper redirect URL for swagger?
Created a sample Web Api application.
In Visual Studio click on connected services and add a dependency of Microsoft Identity platform as shown in below snippet.
Once the dependencies are added then in Program.cs file is updated with Azure AD related settings.
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));
// It added the Authentication and Authorization methods.
app.UseAuthentication();
app.UseAuthorization();
In Program.cs file made the code changes as shown below.
builder.Services.AddSwaggerGen(
x =>
{
x.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo
{
Title = "Swagger Azure AD Demo",
Version = "v1"
});
x.AddSecurityDefinition("oauth2", new Microsoft.OpenApi.Models.OpenApiSecurityScheme
{
Description = "Oauth2.0 which uses AuthorizationCode flow",
Name = "oauth2.0",
Type = SecuritySchemeType.OAuth2,
Flows = new OpenApiOAuthFlows
{
AuthorizationCode = new OpenApiOAuthFlow
{
AuthorizationUrl = new Uri(builder.Configuration["SwaggerAzureAD:AuthorizationUrl"]),
TokenUrl = new Uri(builder.Configuration["SwaggerAzureAD:TokenUrl"]),
Scopes = new Dictionary<string, string>
{
{builder.Configuration["SwaggerAzureAD:Scope"], "Access API as User" }
}
}
}
});
x.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference= new OpenApiReference{Type=ReferenceType.SecurityScheme,Id="oauth2"}
},
new[]{builder.Configuration["SwaggerAzureAD:Scope"]}
}
});
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI(x=>
{
x.OAuthClientId(builder.Configuration["SwaggerAzureAD:ClientId"]);
x.OAuthUsePkce();
});
}
And the AppSettings.Json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "Something.onmicrosoft.com",
"TenantId": "TenantID",
"ClientId": "ClientID",
"CallbackPath": "/signin-oidc",
"Scopes": "access_as_user"
},
// These values need to be copied from App registartion
"SwaggerAzureAD": {
"AuthorizationUrl": "",
"TokenUrl": "",
"ClientId": "",
"Scope": ""
}
}
Expose API from App registration.
API Permissions need to be given in the App registrations of Azure.
And has to be copied the value and need to use in the scope attribute of AppSettings.json
And redirection urls to be mentioned and to be used in AppSettings.json form App registration -> Authentication as shown in below snippet.
And able to get the response without any issues.
Response:
I'm trying to get the list of firewall rules on a Postgres Instance in Azure, using the azure-js-sdk. Locally it works like a charm, I've got the list of firewall rules from my selected postgres instance.
...
const azureCredential = new DefaultAzureCredential();
const subscriptionClient = new SubscriptionClient(azureCredential);
const postgresCli = new PostgreSQLManagementClient(azureCredential, "mySubscriptionId");
const fwRules = postgresCli.firewallRules.listByServer("myResourceGroup", "myServerName");
for await (const fwRule of fwRules) {
context.log.info("in the for of fwRules");
context.log.info("DELETING ", fwRule.name, " in ", azResourceGroup);
...
}
However, when I run this code in a serverless function, the fwRules object is empty and it does'nt display my firewall rules. And there is no exception.
index.ts
const httpTrigger: AzureFunction = async function (context: Context, req: HttpRequest): Promise<void> {
try {
const azureCredential = new DefaultAzureCredential();
const subscriptionClient = new SubscriptionClient(azureCredential);
const postgresCli = new PostgreSQLManagementClient(azureCredential, "mySubscriptionId");
const fwRules = postgresCli.firewallRules.listByServer("myResourceGroup", "myServerName");
for await (const fwRule of fwRules) {
context.log.info("in the for of fwRules");
context.log.info("DELETING ", fwRule.name, " in ", azResourceGroup);
...
}
}
catch (e) {
context.log.error(e);
context.res = { status: 500, body: 'Internal Server Error' };
}
};
function.json
"bindings": [
{
"authLevel": "function",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": [
"get"
]
},
{
"type": "http",
"direction": "out",
"name": "res"
}
],
"scriptFile": "../target/foobar/index.js"
Do you something wrong with this code ? Or did you already faced this problem ? I don't know how I can debug further this code to understand the reason why it does not enter the loop.
Regards,
Blured.
You can try another approach to connect with Azure PostgreSQL DB from Azure Function App and try to get the firewall rules adterwords.
Step 1: Configure Azure AD Authentication for PostgreSQL
Step 2: Enable managed identity for the Function app
Step 3: Use the managed identity ID to create a user in Postgres
Step 4: Write code for function app
Step 5: Test the function app and connect to Postgres
Check the connection and get the firewall rules.
Refer: Connect from Function app with managed identity to Azure Database for PostgreSQL
Goal
Assign a role dialogflow.admin to a service account I created for a project using the Node.js Client library for Google APIs.
Issue
When I try to update my service accounts IAM Policy and add a role to the service account. I get an error that the role is not supported for this resource type.
I am trying to give my service account the Dialogflow API Admin Role roles/dialogflow.admin
The method in the Node.js client library I am using is iam.projects.serviceAccounts.setIamPolicy.
I have already managed to create the service account with this Node.js client library with a function shown here.
async function createServiceAccount(projectID, serviceAccountID){
const authClient = await auth.getClient();
var request = {
name: "projects/"+projectID,
resource: {
"accountId": serviceAccountID,
"serviceAccount": {
"description" : "Service Account for project: "+projectID+" for DialogFlow authentication with VA",
"displayName": "VA Dialogflow Service Account "+projectID
}
},
auth: authClient,
};
await iam.projects.serviceAccounts.create(request, function(err, response) {
if (err) {
console.error(err);
return;
}
console.log(JSON.stringify(response, null, 2));
});
}
after this function runs, and I am sure the service account is created, I run my function that is meant to set the roles of this service account.
async function setServiceAccountRoles(projectID, serviceAccountID){
const authClient = await auth.getClient();
var request = {
resource_: "projects/"+projectID+"/serviceAccounts/"+serviceAccountID,
resource: {
policy: {
bindings: [
{
// role: "projects/"+projectID+"roles/dialogflow.admin",
role: "roles/dialogflow.admin",
"members": [
"serviceAccount:"+serviceAccountID
]
}
],
version: 1
}
},
auth: authClient,
};
await iam.projects.serviceAccounts.setIamPolicy(request, function(err, response) {
if (err) {
console.error(err);
return;
}
console.log(JSON.stringify(response, null, 2));
});
}
Error
When I run this function I am give this error:
code: 400,
errors: [
{
message: 'Role roles/dialogflow.admin is not supported for this resource.',
domain: 'global',
reason: 'badRequest'
}
]
I have used these following resources to get this far:
https://cloud.google.com/iam/docs/reference/rest/v1/projects.serviceAccounts/setIamPolicy
https://cloud.google.com/iam/docs/reference/rest/v1/Policy
https://cloud.google.com/iam/docs/granting-changing-revoking-access#granting_access_to_a_service_account_for_a_resource
Alternative methods.
I have tried changing the role to a project specific path like this:
async function setServiceAccountRoles(projectID, serviceAccountID){
const authClient = await auth.getClient();
var request = {
resource_: "projects/"+projectID+"/serviceAccounts/"+serviceAccountID,
resource: {
policy: {
bindings: [
{
role: "projects/"+projectID+"roles/dialogflow.admin",
"members": [
"serviceAccount:"+serviceAccountID
]
}
],
version: 1
}
},
auth: authClient,
};
await iam.projects.serviceAccounts.setIamPolicy(request, function(err, response) {
if (err) {
console.error(err);
return;
}
console.log(JSON.stringify(response, null, 2));
});
}
however, with this I get the error : message: "Role (projects/va-9986d601/roles/dialogflow.admin) does not exist in the resource's hierarchy.",
Is it possible that the only way to update my service account's roles and permission is through the console or gcloud commands? If so, is there any recommended ways of running said gcloud commands through the node.js client library or from a node application itself?
You are trying to set an IAM policy on the service account. That is used to grant other identities access to the service account itself.
You should modify the IAM binding for the project and not for the service account.
Use the getIamPolicy and setIamPolicy. Examples are included with the documentation.
WARNING: be very careful writing code that modifies a project's bindings. If you overwrite the bindings you can easily lock yourself out of your project. Then you would need to open a paid support ticket with Google Cloud Support. Practice with a throw away project.
I got this sample code from the docs of actions on google account linking with google account. The signin.status is always "ERROR". I have tried on actions console simulator, google assistant app on my phone and on a google home mini with personal results on. But the result is the same in all cases.
const express = require('express');
const bodyParser = require('body-parser');
const {actionssdk, SignIn} = require('actions-on-google');
const app = actionssdk({
// REPLACE THE PLACEHOLDER WITH THE CLIENT_ID OF YOUR ACTIONS PROJECT
clientId: <client_id>,
});
// Intent that starts the account linking flow.
app.intent('actions.intent.MAIN', (conv) => {
conv.ask(new SignIn('To get your account details'));
});
// Create an Actions SDK intent with the `actions_intent_SIGN_IN` event.
app.intent('actions.intent.SIGN_IN', (conv, params, signin) => {
console.log(signin)
if (signin.status === 'OK') {
const payload = conv.user.profile.payload;
conv.ask(`I got your account details, ${payload.name}. What do you want to do next?`);
} else {
conv.ask(`I won't be able to save your data, but what do you want to do next?`);
}
});
app.intent('actions.intent.TEXT', (conv) => {
conv.close("bye");
})
//Run server
const expressApp = express().use(bodyParser.json());
expressApp.post('/', function(req,res){
app(req,res);
});
expressApp.listen(8080,() => {console.log("listening")});
This is the signin object I'm being returned
{ '#type': 'type.googleapis.com/google.actions.v2.SignInValue',
status: 'ERROR' }
EDIT
My actions.json is as follows
{
"actions": [
{
"description": "Default Welcome Intent",
"name": "MAIN",
"fulfillment": {
"conversationName": "fulfilment function"
},
"intent": {
"name": "actions.intent.MAIN",
"trigger": {
"queryPatterns": [
"talk to Care Cat"
]
}
}
},
{
"description": "Everything Else Intent",
"name": "allElse",
"fulfillment": {
"conversationName": "fulfilment function"
},
"intent": {
"name": "actions.intent.TEXT"
}
}
],
"conversations": {
"fulfilment function": {
"name": "fulfilment function",
"url": <url>
}
},
"locale": "en"
}
Could it be because it is still a test app which is not published yet?
Can someone help me with this?
In your Google Cloud Platform Account, check your IAM settings and enable the Dialogflow API Admin
Documentation for more details: https://cloud.google.com/dialogflow/docs/access-control
I am trying to create tables for a google map in the maps engine. I have added the service account to the access list in the mapsengine admin panel for the map and gave it "can edit" permissions. I also gave it edit permissions in the developer console for the project.
this is where I am sending off for the access token which is sending back a token:
var googleapis = require('googleapis');
function connect() {
var authClient = new googleapis.auth.JWT(
'216755859529-1s2o9qofhd9ea65ang9clpd1936ldfcr#developer.gserviceaccount.com',
'../bin/googleoauth2.pem',
'notasecret',
['https://www.googleapis.com/auth/mapsengine'],
// User to impersonate (leave empty if no impersonation needed)
''
);
authClient.authorize(function(err, tokens) {
if (err) {
console.log(err);
return;
}
else {
console.log(tokens);
}
googleapis.discover('mapsengine', 'v1').execute(function (err, client) {
if (err) {
console.log('Problem during the client discovery.', err);
return;
}
createNewTable();
client.mapsengine.tables.create(mapengine_table_body).withAuthClient(authClient).execute(function (err, response) {
if (err) {
console.log(err);
return;
} else {
console.log(response);
return;
}
});
});
});
}
This is where I am creating the table and sending it:
function createNewTable() {
mapengine_table_body = {
"projectId": projectID,
"name": "World Famous Mountains",
"description": "A partial list of famous mountains in the world.",
"draftAccessList": "Map Editors",
"tags": [
"mountain",
"high places"
],
"schema": {
"columns": [
{
"name": "geometry",
"type": "points"
},
{
"name": "mountain_name",
"type": "string"
},
{
"name": "height",
"type": "integer"
}
]
}
}
}
function start() {
'use strict';
var pck, program;
pck = require('../package.json');
program = require('commander');
program
.version(pck.version)
.option('-r, --run', 'Run')
.parse(process.argv);
console.log('running:');
if (program.run) {
connect();
}
}
The ProjectId I am getting from the url when viewing the project. I have looked through as much documentation as I can find, but I haven't been able to figure this one out. Thanks.
Without your error response I can't give you an exact answer, but here are a couple of pointers.
Check the contents of err in the execute block. The API will return something in err.errors that should explain what the problem is, if it's occurring on the API side.
It's not clear what the scope of the mapengine_table_body variable is, try logging it right in the connect() call right after calling createNewTable() to make sure you have the actual data.
The third argument in the googleapis.auth.JWT constructor (at least according to this) is not the key password, it's an alternative way to provide the actual key inline (as opposed to using the file location in argument #2).
Project IDs in Maps Engine may look like large numbers but treating them as large numbers in JavaScript will very likely result in rounding, meaning that you give the API a different number than is in your code. To prevent this, make sure your project ID is "quoted", so that its treated as a string.
Hopefully something here helps! If not, please share the output from suggestion #1.