ASP.NET 6: Azure AD Authentication Infinite redirect loops with AWS Network LB and Fargate - amazon-ecs

I have a AWS Network Load balancer setup with a TLS (:443) Listener that forwards to a Target Group that is listening on port 8080.
The Target Group is an IP Type that points to a Fargate ECS instance.
My problem is that on that ECS instance my website is using Azure Ad for Auth. I got past the issue of the Redirect URI being HTTP instead of HTTPS, but now I am in a redirect loop that eventually ends in
We couldn't sign you in. Please try again.
I am using .NET 6 and Visual Studio 2022.
The Azure AD Auth was added via using the Connected Services in VS 2022.
The NLB URL has been added to Redirect URIs for the App in Azure AD.
Any help is appreciated.
appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "xxxxxxxxx.com",
"TenantId": "xxxxxxxxxx",
"ClientId": "xxxxxxxxxx",
"CallbackPath": "/signin-oidc"
},
"MicrosoftGraph": {
"BaseUrl": "https://graph.microsoft.com/v1.0",
"Scopes": "user.read"
}
}
program.cs
var builder = WebApplication.CreateBuilder(args);
var initialScopes = builder.Configuration["MicrosoftGraph:Scopes"]?.Split(' ');
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
.AddMicrosoftGraph(builder.Configuration.GetSection("MicrosoftGraph"))
.AddInMemoryTokenCaches();
builder.Services.AddAuthorization(options =>
{
options.FallbackPolicy = options.DefaultPolicy;
});
// Add services to the container.
builder.Services.AddRazorPages().AddMicrosoftIdentityUI();
builder.Services.AddScoped<IDynamoDBConnection, DynamoDBConnection>();
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
options.KnownNetworks.Clear();
options.KnownProxies.Clear();
});
builder.WebHost.UseUrls("http://*:8080");
var app = builder.Build();
//This is what fixes the Http redirect URI issue. Problem is it causes a redirect loop
app.Use((context, next) =>
{
context.Request.Scheme = "https";
return next(); //return next(context); //rewritten 8/19 8:23 no change
});
app.UseForwardedHeaders();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapRazorPages();
app.MapControllers();
app.Run();
I have tried multiple browsers and the issue is the same.

I ran into this same issue and managed to resolve by adding the client secret to the appsettings.json.
In the Azure portal, go to Active Directory -> App registrations -> your-app -> Certificates & secrets. Add a new client secret, copy the Value (not the Secret ID, I gave myself an extra headache making that mistake) and paste it into your appsettings Azure object like so:
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "xxxxxxxxx.com",
"TenantId": "xxxxxxxxxx",
"ClientId": "xxxxxxxxxx",
"CallbackPath": "/signin-oidc",
"ClientSecret": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}

Related

Azure AD B2C keeps providing v1 tokens not v2 tokens

Azure's AD B2C keeps issuing v1 tokens even though v2 tokens are configured in the manifest of the SPA app that's registered:
{
"id": "XXX",
"acceptMappedClaims": null,
"accessTokenAcceptedVersion": 2,
"addIns": [],
"allowPublicClient": null,
...
}
The client uses #azure/msal-angular v2.0.5 (along with #azure/msal-browser v2.19.0) to request the token via a plain MSAL Interceptor:
export const protectedResourceMap: Map<string, Array<string>> = new Map([
[
urlJoin(configs.apiUri, 'screen'),
[configs.authConfig.scope.screen_access],
],
]);
#NgModule({
imports: [
MsalModule.forRoot(
new PublicClientApplication({
auth: {
clientId: '...',
authority: 'https://login.microsoftonline.com/XXX.onmicrosoft.com',
postLogoutRedirectUri: '.../logout',
navigateToLoginRequestUrl: true,
redirectUri: '.../auth',
},
cache: {
cacheLocation: 'sessionStorage',
},
}),
{
interactionType: InteractionType.Redirect, // Popup or Redirect
loginFailedRoute: '/login-failed'
},
{
interactionType: InteractionType.Redirect, // Popup or Redirect
protectedResourceMap,
})
...
This seems to look OK, especially the "accessTokenAcceptedVersion": 2.
What might be the root cause of the token still being of v1?
{
"aud": "00000003-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
"iss": "https://sts.windows.net/7dcXX-XXXXX.../",
...
"ver": "1.0",
...
}
Pointers would be much appreciated.
Azure AD B2C only ever used the endpoint when making the OIDC Authentication requests with v2.0, a v1.0 never existed. So it always has issued v1.0 tokens (v2 is the first and only version). This is completely normal.
Only Azure AD had v1.0 and v2.0 OIDC endpoint, and therefore maps based off of accessTokenAcceptedVersion.
You don't need to mess with this property in AAD B2C application registrations unless you have a SAML relying party.

Ocelot api gateway - kubernetes - error: "namespace:serviceservice:managementservice Unable to use ,it is invalid. Address must contain host only...."

The problem that i am facing is that the ocelot kubernetservicediscorverProvider does not seem to find the other services on the name space in kubernetes.My goal is to use api gateway to call apis in the other services in the same namespace. I currently get a http 404 Not Found error. And the api gateway pod, logs the following:
Ocelot.Provider.Kubernetes.KubernetesServiceDiscoveryProvider[0]
requestId: 0HM93C93DL2T0:00000003, previousRequestId: no previous request id, message: namespace:serviceservice:managementservice Unable to use ,it is invalid. Address must contain host only e.g. localhost and port must be greater than 0
warn: Ocelot.Responder.Middleware.ResponderMiddleware[0]
requestId: 0HM93C93DL2T0:00000003, previousRequestId: no previous request id, message: Error Code: ServicesAreEmptyError Message: There were no services in NoLoadBalancer errors found in ResponderMiddleware. Setting error response for request path:/api/management/User/3910, request method: GET
I suspect that i have mis configured something. I first tried using the Ocelot documentation, regarding kubernetes, but the documentation is out dated. (an example is the Type the sugest value does not work for more info go this github issue Docs/Kubernetes provider are wrong)
Then i went on searching online through github issues, stack overflow posts and even the source code. But i do not see have what i am lacking in my config.
I currently have kubernetes running localy, with minikube. The only things that i have seen online is that others have misconfigured the ocelot.json. But i do not see what i have done incorrectly in my config.
(Before trying ocelot on kubernetes i first try it with local hosts, to try out if it works and to see what it lacks. It apparantly lacked a middleware that could control jwt with different roles which had right to acces the end point. I have now written the middleware my self and it works on the local host config for ocelot)
My ocelot.json config file looks like this for kubernetes:
{
"Routes": [
{
"UpstreamPathTemplate": "/api/management/User/{everything}",
"UpstreamHttpMethod": [ "POST", "PUT", "GET" ],
"DownstreamPathTemplate": "/api/management/User/{everything}",
"DownstreamScheme": "http",
"ServiceName": "managementservice",
"AuthenticationOptions": {
"AuthenticationProviderKey": "Bearer",
"AllowedScopes": [ "CompanyId", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier" ]
},
"RouteClaimsRequirement": { "role": "1,2,3" },
"AddHeadersToRequest": {
"CompanyId": "Claims[CompanyId] > value",
"UserId": "Claims[http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier] > value"
}
}
],
"GlobalConfiguration": {
"ServiceDiscoveryProvider": {
"Host": "127.0.0.1",
"Port": 8083,
"Namespace": "service",
"Type": "KubernetesServiceDiscoveryProvider"
}
}
}
my startup.cs ConfigureServices method looks like this
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(c =>
{
c.AddPolicy("AllowOrigin", options => options.WithOrigins(Configuration["Cors:AllowOrigins"])
.AllowAnyHeader()
.AllowAnyMethod().AllowCredentials());
});
#region Authication settings
TokenValidationParameters tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Configuration["Jwt:Key"])),
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
services.AddSingleton(tokenValidationParameters);
services.AddAuthentication(
x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}
)
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = tokenValidationParameters;
});
#endregion
//Some more code
services.AddOcelot().AddKubernetes();
}
my startup.cs Configure method looks like this
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// some more code
app.UseOcelot(configuration);
}
my program.cs CreateHostBuilder method looks like this
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.AddJsonFile("secrets/appsettings.kubernetes.json", optional: true)
.AddJsonFile("ocelot.json");
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
It turns out that the real problem that i was having was in the permissions in kubernetes. The ocelot documentation also mentions this. But the command in the documentation is incorrect (most likely out dated).
This is the command that i used. Be warned the kubernetes documenation strongly disrecommend usage of this command permissive-rbac-permissions. But it is at least a way for you to test your api gateway in ocelot locally.
kubectl create clusterrolebinding permissive-binding --clusterrole=cluster-admin --user=admin --user=kubelet --group=system:serviceaccounts

Google Actions SDK Sign-In implicit flow

EDIT: On phone assistant its working now problem just exist in google action simulator
I just try to setup Google Actions SDK account Linking with implicit grant and try to test it in Simulator.
First question is this even possible in Simulator?
To Do so I added at the action console account linking with the type
implicit grant to my action.
The url I used is working.
Now I added a signup request to my action. For testing so if I write signup in simulator the server response with:
{
conversationToken: JSON.stringify(state),
expectUserResponse: true,
expectedInputs: [
{
inputPrompt: {
initialPrompts: [
{
textToSpeech: "PLACEHOLDER_FOR_SIGN_IN"
}
],
noInputPrompts: []
},
possibleIntents: [
{
"intent": "actions.intent.SIGN_IN",
"inputValueData": {}
}
],
speechBiasingHints: []
}
]
}
After this the server didn't request the sign in page route (the address is correct!). It just responds with SignIN intent ERROR :
{
"isInSandbox'": false,
"surface": {
"capabilities": [
{
"name": "actions.capability.AUDIO_OUTPUT"
},
{
"name": "actions.capability.SCREEN_OUTPUT"
}
]
},
"inputs": [
{
"rawInputs": [
{
"query": "i think so",
"inputType": "VOICE"
}
],
"arguments": [
{
"name": "SIGN_IN",
'extension': {
"#type": "type.googleapis.com/google.actions.v2.SignInValue",
"status": "Error"
}
}
],
"intent': "actions.intent.SIGN_IN"
}
],
"device": {
"locale": "en-US"
},
"conversation": {
"conversationId": "1494606917128",
"type": "ACTIVE",
"conversationToken": "[\"_actions_on_google_\"]"
}
}
Why? Where is the problem? Can I see a error message somewhere?
Here is what happen in the simulator between 3 and 4:
Is it same when you use the phone app? For me it opens an embedded browser with my /auth endpoint, which the simulator doesn’t do.
I am able to make it WORKING after a long time.
We have to enable the webhook first and we can see how to enable the webhook in the dialog flow fulfillment docs
If we are going to use Google Assistant, then we have to enable the Google Assistant Integration in the integrations first.
Then follow the steps mentioned below for the Account Linking in actions on google:-
Go to google cloud console -> APIsand Services -> Credentials -> OAuth 2.0 client IDs -> Web client -> Note the client ID, client secret from there
-> Download JSON - from json note down the project id, auth_uri, token_uri
-> Authorised Redirect URIs -> White list our app's URL -> in this URL fixed part is https://oauth-redirect.googleusercontent.com/r/ and append the project id in the URL
-> Save the changes
Actions on Google -> Account linking setup
1. Grant type = Authorisation code
2. Client info
1. Fill up client id,client secrtet, auth_uri, token_uri
2. Enter the auth uri as https://www.googleapis.com/auth and token_uri as https://www.googleapis.com/token
3. Save and run
4. It will show an error while running on the google assistant, but dont worry
5. Come back to the account linking section in the assistant settings and enter auth_uri as https://accounts.google.com/o/oauth2/auth
and token_uri as https://accounts.google.com/o/oauth2/token
6. Put the scopes as https://www.googleapis.com/auth/userinfo.profile and https://www.googleapis.com/auth/userinfo.email
and weare good to go.
7. Save the changes.
In the hosting server logs, we can see the access token value and through access token, we can get the details regarding the email address.
Append the access token to this link "https://www.googleapis.com/oauth2/v1/userinfo?access_token=" and we can get the required details in the resulting json page.
accessToken = req.get("originalRequest").get("data").get("user").get("accessToken")
r = requests.get(link)
print("Email Id= " + r.json()["email"])
print("Name= " + r.json()["name"])
P.S. You can use the Grant Type as Implicit also instead of Authorisation code.

How to use Ionic proxy in conjunction with AWS SDK

Using Ionic 4.4.0 and aws-sdk 2.157.0. I'm trying to create an S3 bucket from my local web browser, but am running into CORS problems when attempting to run the following code, method createBucketByCompanyKey():
import { Injectable } from '#angular/core';
import * as AWS from 'aws-sdk';
#Injectable()
export class AwsProvider {
private accessKeyId:string = 'myAccessKey';
private secretAccessKey:string = 'mySuperSecret';
private region:string = 'us-east-1';
constructor() {
AWS.config.update({accessKeyId: this.accessKeyId, secretAccessKey: this.secretAccessKey, region: this.region});
}
createBucketByCompanyKey(companyKey){
let s3 = new AWS.S3();
let params = {
Bucket: companyKey,
CreateBucketConfiguration: {
LocationConstraint: this.region
}
};
s3.createBucket(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // successful response
});
}
}
This gives me the error
Failed to load https://s3.amazonaws.com/-KwzdjmyrHiMBCqHH1ZC: Response
to preflight request doesn't pass access control check: No
'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'http://localhost:8100' is therefore not allowed
access. The response had HTTP status code 403.
Which led me to this post here after several hours of googling. It appears I need to run ionic through a proxy. I've also tried changing my "path" to http://localhost:8100, but stuck I remain.
{
"name": "MyApp",
"app_id": "",
"type": "ionic-angular",
"integrations": {},
"proxies": [
{
"path": "/",
"proxyUrl": "https://s3.amazonaws.com/"
}
]
}
I've also come across posts telling my to download a Chrome extension that disables CORS, but that didn't work either.
Any ideas on how to setup this proxy to work with AWS' SDK?
Forget the proxies. For Mac, enter in the following in the terminal to open a Google Chrome browser with CORS disabled.
open -a Google\ Chrome --args --disable-web-security --user-data-dir
Compliments of this post.

Reset Local Admin User on VM using AzureRM Rest api

I'm using the AzureRM rest api to communicate to the hypervisor. One of the things I need to do is to reset the local admin password on a VM, however I can't figure out how to reset it.
We could use the Virtual Machine Extensions REST API to do that. It works correctly for me. Following is my detail test info.
1.We need to get the authorization in the request header
Authorization:Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUz.......
Content-Type:application/json
2.Add the following info in the request body
{
"properties": {
"publisher": "Microsoft.Compute",
"type": "VMAccessAgent",
"typeHandlerVersion": "2.0",
"autoUpgradeMinorVersion": true,
"settings": {
"UserName": "local admin" //your local admin
},
"protectedSettings": {
"Password": "your reset passord" //match the password policy
}
},
"location": "East Asia"
}
Send the http request with Fiddler.
4 . Successfully remote the VM with reset password.
We also can reset our local admin password in the Azure portal.
You can also use the Azure .NET SDK, which has a wrapper for the create or update call.
I tried and ran into an issue where the VM extension request went through fine and was installed successfully, but the password was not updated.
After attaching fiddler, I saw that my dynamic objects for settings and protectedSettings were not getting properly serialized by the library. The solution was to pass a dictionary down to the VirtualMachineExtensions() constructor instead.
Before:
proxy.VirtualMachineExtensions.BeginCreateOrUpdateWithHttpMessagesAsync(
"<resource group>",
"<vm name>",
"<you name it>",
new Microsoft.Azure.Management.Compute.Models.VirtualMachineExtension(
location: "westus",
publisher: "Microsoft.Compute",
virtualMachineExtensionType: "VMAccessAgent",
typeHandlerVersion: "2.0",
autoUpgradeMinorVersion: true,
settings: new
{
UserName: "<username>"
},
protectedSettings: new
{
Password: "<password>"
}));
After:
proxy.VirtualMachineExtensions.BeginCreateOrUpdateWithHttpMessagesAsync(
"<resource group>",
"<vm name>",
"<you name it>",
new Microsoft.Azure.Management.Compute.Models.VirtualMachineExtension(
location: "westus",
publisher: "Microsoft.Compute",
virtualMachineExtensionType: "VMAccessAgent",
typeHandlerVersion: "2.0",
autoUpgradeMinorVersion: true,
settings: new Dictionary<string, string>()
{
{ "UserName", "<username>" }
},
protectedSettings: new Dictionary<string, string>()
{
{"PassWord", "<password>" }
}));