I have created a static web application in Azure. This is just a plain site with only HTML pages. I am able to deploy and access the application.
The issue is when I try to integrate Azure Active Directory with this service. My staticwebapp.config.json is as follows.
{
"routes":[
{
"route": "/login",
"rewrite": "/.auth/login/aad"
},
{
"route": "/*",
"allowedRoles": ["authenticated"]
}
],
"auth": {
"identityProviders": {
"azureActiveDirectory": {
"registration": {
"openIdIssuer": "https://login.microsoftonline.com/<tenant id removed>",
"clientIdSettingName": "<client id value>",
"clientSecretSettingName": "<client secret value>"
}
}
}
},
"responseOverrides": {
"401": {
"redirect": "/login",
"statusCode": 302
}
}
}
After this, when I try to access any page, it gets redirected to /login and I get a 404 page. I expected it to go to azure authentication page (due to the redirect rule). Are there any other configurations that needs to be done for authenticating a static web application?
One thing to double check here would be the clientIdSettingName and clientSecretSettingName fields.
These are not the actual values, but rather references to SWA settings. Reason for doing it this way is to ensure no plain-text secrets are committed to source control.
So, given literal values
"clientIdSettingName": "AZURE_CLIENT_ID",
"clientSecretSettingName": "AZURE_CLIENT_SECRET"
Azure portal should be set up with something like this:
Documentation clearly mentions this step, but I missed the referential nature of this setting upon first couple of readthroughs. Ultimately, variable names gave it away.
Related
I have a workflow in a standard logic app, that have HTTP trigger. When the workflow is trigged, the workflow, retrieve some data from a CosmosDB. Something like:
The previous method will require to have an API connection. I have already created and deployed a 'V2' API connection. Let's call it myCosmosCon
Also in the ARM template for my logic app I have already added a connectionRuntimeUrl of my connection API (to myCosmosCon) to appSettings (configuration):
....
"siteConfig": {
"appSettings": [
{
"name": "subscriptionId",
"value": "[subscription().subscriptionId]"
},
{
"name": "resourceGroup_name",
"value": "[resourceGroup().name]"
},
{
"name": "location_name",
"value": "[resourceGroup().location]"
},
{
"name": "connectionRuntimeUrl",
"value": "[reference(resourceId('Microsoft.Web/connections', parameters('connection_name')),'2016-06-01', 'full').properties.connectionRuntimeUrl]"
},
.....
]
},
Then I wrote the following in the connections.json:
{
"managedApiConnections": {
"documentdb": {
"api": {
"id": "/subscriptions/#appsetting('subscriptionId')/providers/Microsoft.Web/locations/#appsetting('location_name')/managedApis/documentdb"
},
"connection": {
"id": "/subscriptions/#appsetting('subscriptionId')/resourceGroups/#appsetting('resourceGroup_name')/providers/Microsoft.Web/connections/myCosmosCon"
},
"connectionRuntimeUrl": "#appsetting('connection_runtimeUrl')",
"authentication": {
"type": "ManagedServiceIdentity"
}
}
}
}
Now, when I deploy the ARM template of my Logic app, workflow, ... etc. I see no errors, the workflow looks also good. The only problem is the URL link to the HTTP trigger is not generated, I can't run the program.
However, if I change the connection_runtimeUrl in the connections.json file to have the actual value; to look something like:
"connectionRuntimeUrl": "https://xxxxxxxxxxxxx.xx.common.logic-norwayeast.azure-apihub.net/apim/myCosmosCon/xxxxxxxxxxxxxxxxxxxxxxxx/",
The URL is generated directly and I can simply run the workflow. AFTER that, if I return the connection_runtimeUrl as it was (a call to appsettings()), it still working!! the link also stay there.
It looks like the when I deploy the Logic app and the workflow that the connections.json, do not compile or make the call, so Azure think that there is an error and do not generate the link.
Any idea about how to solve the problem??
Thanks!
Not sure but could be the issue:
When you create a connection api for a logic app standard, you also need to create an access policy at the connection api level for the system assigned identity running the logic app standard.
param location string = resourceGroup().location
param cosmosDbAccountName string
param connectorName string = '${cosmosDbAccountName}-connector'
// The principalid of the logic app standard system assigned identity
param principalId string
// get a reference to the cosmos db account
resource cosmosDbAccount 'Microsoft.DocumentDB/databaseAccounts#2021-06-15' existing = {
name: cosmosDbAccountName
}
// create the related connection api
resource cosmosDbConnector 'Microsoft.Web/connections#2016-06-01' = {
name: connectorName
location: location
kind: 'V2'
properties: {
displayName: connectorName
parameterValues: {
databaseAccount: cosmosDbAccount.name
accessKey: listKeys(cosmosDbAccount.id, cosmosDbAccount.apiVersion).primaryMasterKey
}
api: {
id: 'subscriptions/${subscription().subscriptionId}/providers/Microsoft.Web/locations/${location}/managedApis/documentdb'
}
}
}
// Grant permission to the logic app standard to access the connection api
resource cosmosDbConnectorAccessPolicy 'Microsoft.Web/connections/accessPolicies#2016-06-01' = {
name: '${cosmosDbConnector.name}/${principalId}'
location: location
properties: {
principal: {
type: 'ActiveDirectory'
identity: {
tenantId: subscription().tenantId
objectId: principalId
}
}
}
}
output connectionRuntimeUrl string = reference(cosmosDbConnector.id, cosmosDbConnector.apiVersion, 'full').properties.connectionRuntimeUrl
I'm having trouble with the exact same issue/bug. The only work around as I see it is to deploy the workflow twice. First time with an actual URL pointing to a dummy connection and the second time with the appsetting reference.
Whenever a new release pipeline is ran in Azure DevOps, the URL Is changed.. currently my ARM template has a hard-coded URL which can be annoying to keep on adding in manually.
"cors": {
"allowedOrigins": [
"[concat('https://',parameters('storage_account_name'),'.z10.web.core.windows.net')]"
}
The only thing that changes is the 10 part in the z10 so essentially i want it to be something like
[concat('https://',parameters('storage_account_name'),'.z', '*', '.web.core.windows.net')] I dont know if something like that is valid but essentially its so that the cors policy will accept the URL regardless of the z number.
Basically speaking this is not possible, because of the CORS standard (see docs).
which allows only for exact origins, wildcard, or null.
For instance, ARM for Azure Storage is also following this pattern allowing you to put a list of exact origins or a wildcard (see ARM docs)
However, if you know your website name, in your ARM you can receive the full host and use it in your CORS:
"[reference(resourceId('Microsoft.Web/sites', parameters('SiteName')), '2018-02-01').defaultHostName]"
The same with a static website (which is your case I guess) if you know the storage account name:
"[reference(concat('Microsoft.Storage/storageAccounts/', variables('storageAccountName')), '2019-06-01', 'Full').properties.primaryEndpoints.web]"
Advance reference output manipulation
Answering on comment - if you would like to replace some characters in the output from the reference function the easiest way is to use build-in replace function (see docs)
In case you need a more advanced scenario I am pasting my solution by introducing a custom function which is removing https:// and / from the end so https://contonso.com/ is transformed to contonso.com:
"functions": [
{
"namespace": "lmc",
"members": {
"replaceUri": {
"parameters": [
{
"name": "uriString",
"type": "string"
}
],
"output": {
"type": "string",
"value": "[replace(replace(parameters('uriString'), 'https://',''), '/','')]"
}
}
}
}
],
# ...(some code)...
"resources": [
# ... (some resource)...:
"properties": {
"hostName": "[lmc.replaceUri(reference(variables('storageNameCdn')).primaryEndpoints.blob)]"
}
]
As of now, from what I see, the only way to use Azure Function Proxy is to re-route an existing api is to call upon that api directly. For example:
Backend URL
https://gateway-api.com/api/getSomething
Route Template
/api
Proxy URL
https://gateway.azurewebsites.net/api
What I want is to have the Backend URL pass through any endpoint relative to the main endpoint.
Effectively this:
Backend URL
https://gateway-api.com/* or i even tried this https://gateway-api.com/{*restOfPath}
This way, any api's that follow the core domain URL will still work as expected.
Here is a re-write of the example above:
Backend URL 2
https://gateway-api.com/*
Route Template 2
/*
Proxy URL 2
https://gateway.azurewebsites.net/api/getSomething
When I do this I can't get it to work or even reach the debuger to log anything.
Is this possible and if not would this be something Azure API Management would be able to accomplish?
Can you provide your configuration file? this is mine:
Proxies.json:
{
"$schema": "http://json.schemastore.org/proxies",
"proxies": {
"proxy1": {
"matchCondition": {
"methods": [ "GET" ],
"route": "/{test}"
},
"backendUri": "http://localhost:7071/abc/Function1"
}
}
}
host.json:
{
"version": "2.0",
"extensions": {
"http": {
"routePrefix": "abc"
}
},
"logging": {
"applicationInsights": {
"samplingExcludedTypes": "Request",
"samplingSettings": {
"isEnabled": true
}
}
}
}
This is the backend url and proxy of my function:
Both of them works fine.
If you change route template, I think the backend url will not have /api unless you give the /api to routePrefix.
Any way, please show the file about how to configure proxy and route template.
I followed this tutorial and managed to use api with Azure Active Directory
authentication & authorization.
However I would like to consume the api from behind the Ocelot Api Gateway.
I could use ocelot with custom basic authorization but could not accomplish to use with Azure Active Directory.
I have added Ocelot api gateway url to my api redirect url list already.
How should I set ReRoutes values in config.json and Ocelot Api Gateway project StartUp.cs ?
Any help will be appreciated.
Eventually I could.
First of all thanks to ocelot library because it supports Azure Active Directory authorization.
I assume that you can already completed this tutorial.
1-Create an ocelot api gateway project as usual.
2-Add Microsoft.Identity.Web class library to ocelot project as reference
3-Add ocelot.json and it should be like below
{
"ReRoutes": [
{
"DownstreamPathTemplate": "/api/{catchAll}",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 44351
}
],
"UpstreamPathTemplate": "/to-do-service/api/{catchAll}",
"AuthenticationOptions": {
"AuthenticationProviderKey": "AzureADJwtBearer",
"AllowedScopes": []
}
}
],
"GlobalConfiguration": {
"BaseUrl": "http://localhost:7070",
"RequestIdKey": "OcRequestId",
"AdministrationPath": "/administration"
}
}
4-Edit CreateWebHostBuilder method in Program.cs so that ocelot.json is used as additional config source.
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.AddJsonFile("ocelot.json", false, false);
})
.UseStartup<Startup>();
5-Edit ConfigureServices and Configure methods in Startup.cs like below
public void ConfigureServices(IServiceCollection services)
{
services.AddProtectWebApiWithMicrosoftIdentityPlatformV2(Configuration); //this extension comes from Microsoft.Identity.Web class library
services.AddOcelot(Configuration);
//services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public async void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
await app.UseOcelot();
}
6-Last but not least you should add your AzureAd configuration to ocelot api gateway project. (It should be same as ToDoListService for reference tutorial)
Her you can see an example appsettings.json .
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"ClientId": "client-id-guid-from-azure-ad",
/*
You need specify the TenantId only if you want to accept access tokens from a single tenant (line of business app)
Otherwise you can leave them set to common
*/
"Domain": "blablabla.onmicrosoft.com", // for instance contoso.onmicrosoft.com. Not used in the ASP.NET core template
"TenantId": "tenant-id-guid-from-azure-ad" // A guid (Tenant ID = Directory ID) or 'common' or 'organizations' or 'consumers'
},
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*"
}
I hope this answer save someones time and make their life happier :)
Happy coding!
I was unable to get this working with the "Microsoft.Identity.Web" library. I received a host of errors such as:
AuthenticationScheme: AzureADCookie was not authenticated...
-- and --
Signature validation failed...
Instead, I managed to get the Azure B2C token validation, as well as the scopes, working as follows:
1) ConfigureServices method (Startup.cs):
services.AddAuthentication(options =>
{
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(jwtOptions =>
{
jwtOptions.Authority = $"{Configuration["AzureAdB2C:Instance"]}/tfp/{Configuration["AzureAdB2C:TenantId"]}/{Configuration["AzureAdB2C:SignUpSignInPolicyId"]}";
jwtOptions.Audience = Configuration["AzureAdB2C:ClientId"];
jwtOptions.TokenValidationParameters.ValidateIssuer = true;
jwtOptions.TokenValidationParameters.ValidIssuer = $"{Configuration["AzureAdB2C:Instance"]}/{Configuration["AzureAdB2C:TenantId"]}/v2.0/";
});
// Map scp to scope claims instead of http://schemas.microsoft.com/identity/claims/scope to allow ocelot to read/verify them
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("scp");
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("scp", "scope");
2) Ocelot re-routing configuration:
{
"DownstreamPathTemplate": "/{everything}",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "master-api",
"Port": 5000
}
],
"UpstreamPathTemplate": "/master-api/{everything}",
"UpstreamHttpMethod": [ "POST", "PUT", "GET", "DELETE" ],
"ReRoutesCaseSensitive": false,
"AuthenticationOptions": {
"AuthenticationProviderKey": "Bearer",
"AllowedScopes": [ "master" ]
}
}
3) Azure AD B2C configuration (appsettings.json):
"AzureAdB2C": {
"Instance": "https://yourdomain.b2clogin.com",
"TenantId": "{tenantId}",
"SignUpSignInPolicyId": "your_signin_policy",
"ClientId": "{clientId}"
}
Hope this helps! :)
When you create an Http triggered API, Azure function hosts it on
https://[function-app-name].azurewebsites.net/api/[Route-configured-in-application]
Is there any way of getting rid of the term api from the URL and make it look like:
https://[function-app-name].azurewebsites.net/[Route-configured-in-application]
The Azure Functions v2 solution is covered in this answer, the http bit needs to be wrapped in an extensions property.
{
"version": "2.0",
"extensions": {
"http": {
"routePrefix": "customPrefix"
}
}
}
Edit the host.json file and set routePrefix to empty string:
{
"http": {
"routePrefix": ""
}
}
The accepted answer no longer works if you're using version 2 functions, instead you need to put the http settings in an extensions property:
"extensions": {
"http": {
"routePrefix": ""
}
}
You can get caught out looking at the hosts.json reference because if you only look at the http section it shows just the http properties so make sure to check the start of the doc for the top level hosts.json format.
You could also leverage the power of Azure Function Proxies for this, which might be better if you want to be explicit about which methods or routes you want to access.
Just create a proxy.json file and add the following piece of JSON to it.
{
"$schema": "http://json.schemastore.org/proxies",
"proxies": {
"myazurefunctionproxy": {
"matchCondition": {
"methods": ["GET"],
"route": "/{slug}"
},
"backendUri": "https://%WEBSITE_HOSTNAME%/api/{slug}"
},
}
}
This sample will redirect all GET-requests to a route with /api/ prefixed.