How to use cookie authentication for Ocelot apigatway - asp.net-core

I am using IdentitySever which issue id_token. Their is another website which uses cookie authentication with openidconnect. Code is as below
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
// Allow sign in via an OpenId Connect provider like OneLogin
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromHours(12);
options.SlidingExpiration = false;
options.Cookie.Name = "TestApp";
})
.AddOpenIdConnect(options =>
{
options.RequireHttpsMetadata = false;
options.SignInScheme = "Cookies";
options.ClientId = "Test";
options.ClientSecret = "client_sceret";
options.Authority = "http://localhost:57744/identity";
options.ResponseType = "id_token";
options.GetClaimsFromUserInfoEndpoint = true;
options.Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProvider = redirectToIdentityProvider
};
}
);
services.Configure<OidcOptions>(Configuration.GetSection("oidc"));
}
I am creating another Apigateway. If user is logged in website than should be able to make api request through gateway. Both website and gateway will be hosted under same sub-domain While making request to apigateway I will pass cookie created by website in header and want to utilize it at gateway to get claims from identityserver.
Is their way to integrate cookie authentication and identity sever with ocelot gateway?

Related

openid connect: asp.net core: logging back in after a successful logout

I have an asp.net core web application that uses keycloak openidconnect for authentication. I have configured a client in keycloak for standard and implicit flow and have specified valid redirect uris. When the app is tun, it prompts me with a keycloak login page which is correct and then redirects me to my application page. Logout button logs the user out(I can see the cookie being cleared). But when I click on the login button again, instead of prompting me with a keycloak login page, it directly takes me back to my application's home page. In fiddler, I can see it hitting the keycloak server; a new token is issued. I think I'm missing some configuration in keycloak server. Any help is appreciated.
Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(options =>
{
// Store the session to cookies
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
// OpenId authentication
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(cookie =>
{
cookie.Cookie.Name = "keycloak.cookie";
cookie.Cookie.MaxAge = TimeSpan.FromMinutes(60);
cookie.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
cookie.SlidingExpiration = true;
})
.AddOpenIdConnect(options =>
{
options.Authority = Configuration.GetSection("Keycloak")["Authority"];
//Keycloak client ID
options.ClientId = Configuration.GetSection("Keycloak")["ClientId"];
//Keycloak client secret
options.ClientSecret = Configuration.GetSection("Keycloak")["ClientSecret"];
// For testing we disable https (should be true for production)
options.RequireHttpsMetadata = false;
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
// OpenID flow to use
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
});
}
index.cshtml:
#if(User.Identity.IsAuthenticated)
{
<form class="form-inline" asp-page="/Index" asp-page-handler="Logout">
<button type="submit" class="nav-link btn btn-link text-light">Sign out</button>
</form>
} else{
<a class="nav-link text-light" asp-page="/Index">Sign in</a>
}
Index.cshtml.cs:
public void OnGet(){...}
public async Task<IActionResult> OpPostLogout(){
Console.WriteLine("Logging out...");
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
Console.WriteLine("Signed out of Cookie Authentication!");
await HttpContext.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme);
Console.WriteLine("Signed out of OpenIDConnect!");
return RedirectToPage("./Logout");
}
I modified my Logout method to the following:
public IActionResult OnPostLogout()
{
return new SignOutResult(
new[] {
OpenIdConnectDefaults.AuthenticationScheme,
CookieAuthenticationDefaults.AuthenticationScheme
});
}
In my keycloak server, I modified the redirecturi and postlogout redirect uri to my application uri(for example: https://localhost:5001/*)
My StartUp.cs:
services.AddAuthentication(options =>
{
// Store the session to cookies
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
// OpenId authentication
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
options.Authority = Configuration.GetSection("Keycloak")["Authority"];
//Keycloak client ID
options.ClientId = Configuration.GetSection("Keycloak")["ClientId"];
//Keycloak client secret
//options.ClientSecret = Configuration.GetSection("Keycloak")["ClientSecret"];
// For testing we disable https (should be true for production)
options.RequireHttpsMetadata = false;
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
// OpenID flow to use
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
});

IdentityServer 4 WsFederation - How to get access token for calling API

I am using Identity Server 4 with the Ws-Federation plugin. Identity Server is configured to connect to Azure AD for authentication. Here is the relevant code from Identity Server project:
public void ConfigureServices(IServiceCollection services)
{
var rsaCertificate = new X509Certificate2("rsaCert.pfx", "1234");
services.AddRazorPages();
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<User, IdentityRole>(options =>
{
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(2);
options.Lockout.MaxFailedAccessAttempts = 3;
})
.AddDefaultUI()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddUserStore<CustomUserStore>()
.AddUserManager<CustomUserManager>()
.AddDefaultTokenProviders();
services.AddTransient<IUserStore<User>, CustomUserStore>();
services.AddTransient<IEmailSender, EmailSender>();
var builder = services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
// see https://identityserver4.readthedocs.io/en/latest/topics/resources.html
options.EmitStaticAudienceClaim = true;
})
.AddSigningCredential(rsaCertificate)
.AddInMemoryIdentityResources(IdentityConfig.IdentityResources)
.AddInMemoryApiScopes(IdentityConfig.ApiScopes)
.AddInMemoryClients(IdentityConfig.Clients)
.AddAspNetIdentity<User>()
.AddWsFederationPlugin(options =>
{
options.Licensee = "Licensee";
options.LicenseKey = "LicenseKey";
})
.AddInMemoryRelyingParties(new List<RelyingParty>());
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = WsFederationDefaults.AuthenticationScheme;
})
.AddWsFederation(options =>
{
options.Wtrealm = "Azure AD App Id";
options.MetadataAddress = "WSFed metadata URL from Azure AD App";
options.Events.OnSecurityTokenValidated = SecurityTokenValidated;
})
.AddCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromMinutes(IdentityConfig.SessionTimeoutInMinutes);
options.SlidingExpiration = true;
options.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.None;
});
}
I have an API protected with JWT bearer authentication which is connected to the same Identity Server. The relevant code from the API (Please note that https://localhost:5001 is the address which the Identity Server is running in):
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority = "https://localhost:5001";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false
};
});
}
I have an MVC client as well which is connected to the same Identity Server. I was able to successfully authenticate users from the MVC client. Now, what I would like to do is to call a protected API endpoint in the API project from within the MVC client. I haven't found any way to get the access token necessary for calling the protected API. Relevant code from the MVC client:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = WsFederationDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.Cookie.Name = "aspnetcorewsfed";
options.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.None;
options.SlidingExpiration = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(Configuration.GetValue<int?>("SessionTimeoutInMinutes") ?? 15);
})
.AddWsFederation(options =>
{
options.MetadataAddress = "https://localhost:5001/wsfed"; // Address of the Identity Server
options.RequireHttpsMetadata = false;
options.Wtrealm = "mvc"; // ClientId registered in Identity Server
options.CallbackPath = "/";
options.SkipUnrecognizedRequests = true;
});
}
There's documentation on the Identity Server website that describes how to access protected APIs as seen here. But this is using OpenIdConnect. Since I am using WsFederation, I have no clue on how to get the access token or refresh token. Is token refresh impossible with WsFed?
Can anyone point me in the right direction on how to go about this?
Use OpenIdConnect in the MVC client instead of WsFed. Change the code in the MVC client's Startup.cs to the following:
services.AddAuthentication(options =>{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
}).AddCookie("Cookies").AddOpenIdConnect("oidc", options =>{
options.Authority = "https://localhost:5001";
options.ClientId = "mvc-openid";
options.ClientSecret = "secret";
options.ResponseType = "code";
options.SaveTokens = true;
});
The corresponding client registration in Identity Server should be:
new Client {
ClientId = "mvc-openid",
ClientSecrets = {
new Secret("secret".Sha256())
},
AllowedGrantTypes = GrantTypes.Code,
RedirectUris = {
"https://localhost:6001/signin-oidc"
},
AllowedScopes = new List < string > {
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"api"
}
}
https://localhost:5001 is the Identity Server address and https://localhost:6001 is the MVC client address.
The access token for accessing the API can be obtained like so:
var accessToken = await HttpContext.GetTokenAsync("access_token");

How do I get more recent userinfo with AddJwtBearer

We use IdentityServer to handle SSO authentication across our apps.
My application is an Aspnet core 3.0 website that passes the users Token to javascript. The javascript then calls a separate aspnet 2.2 API.
Problem: Logging a user out and back in does not update the ClaimsPrincipal on the API with new claims.
I have confirmed that the Web application has the new claims.
If I login Incognito or clear my cookies the new claim shows up in the API.
I am not sure where the responsibility for getting the claims should be and how to fix it. I assume the claims are part of the encrypted access_token, therefore I assume the Web app is sending a stale access_token to the API. So is the Web App what I need to fix? And what would be the proper fix?
Api Startup Code
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = "Bearer";
options.DefaultChallengeScheme = "Bearer";
})
.AddJwtBearer(options =>
{
options.Authority = oidcSettings.Authority;
options.Audience = oidcSettings.ApplicationName;
options.RequireHttpsMetadata = true;
});
Web App Startup Code
services.Configure<CookiePolicyOptions>(options =>
{
options.MinimumSameSitePolicy = SameSiteMode.Unspecified;
options.OnAppendCookie = cookieContext => { cookieContext.CookieOptions.SameSite = SameSiteMode.None; };
options.OnDeleteCookie = cookieContext =>
{
cookieContext.CookieOptions.SameSite = SameSiteMode.None; // this doesn't appear to get called.
};
});
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
}).AddCookie("Cookies", options =>
{
options.SlidingExpiration = false;
options.ExpireTimeSpan = TimeSpan.FromHours(8);
})
.AddOpenIdConnect("oidc", options =>
{
options.Authority = oidcSettings.Authority;
options.RequireHttpsMetadata = true;
options.ClientId = oidcSettings.ClientId;
options.ClientSecret = oidcSettings.ClientKey;
options.ResponseType = OpenIdConnectResponseType.Code;
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("offline_access");
options.Scope.Add(oidcSettings.ApplicationName);
options.ClaimActions.MapJsonKey("role", "role"); // claims I am looking for are mapped here
options.Events.OnUserInformationReceived = async (context) =>
{
await Task.CompletedTask; // confirmed that after new sign in I can see updated info here.
};
});
TLDR: Javascript from Web app calls Api using access_token. When user logs out and logs back in, the API does not receive updated claims. I am not sure if the issue is the API needs to call out to identity server for user info or the Web App is not signing out properly and needs to send a fresh access_token?

Multiple Azure AD Authentication Identity Server 4

I would like to know if it's possible to setup a .NET Core Application using IdentityServer 4 that can Authenticate to more than one AzureAd configuration.
Currently you can add 1 AzureAD configuration like this:
services.AddAuthentication()
.AddAzureAD(options => Configuration.Bind("AzureAd", options));
But I wanted to be able to Authenticate users from multiple Organisations using AzureAd. So different TenantId...etc
This will have to be done on the fly depending on the organisation chosen in the UI.
How can I accomplish that ?
You can use AddOpenIdConnect middleware :
services.AddAuthentication()
.AddOpenIdConnect("AADTenant1", "AADTenant1", options =>
{
options.ClientId = "<app1>";
options.Authority = "https://login.microsoftonline.com/<tenant1>/";
options.CallbackPath = "/signin-oidc-aadtenant1";
options.SaveTokens = true;
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
})
.AddOpenIdConnect("AADTenant2", "AADTenant2", options =>
{
options.ClientId = "<app2>";
options.Authority = "https://login.microsoftonline.com/<tenant2>/";
options.CallbackPath = "/signin-oidc-aadtenant2";
options.SaveTokens = true;
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
})
And trigger the scheme you want to challenge :
var callbackUrl = Url.Action("ExternalLoginCallback");
var props = new AuthenticationProperties
{
RedirectUri = callbackUrl,
Items =
{
{ "scheme", provider },
{ "returnUrl", returnUrl }
}
};
return Challenge(provider, props);

Swagger UI using swasbuckle is getting me CORS Origin issue on login redirect from Azure

I'm facing a new issue with swashbuckle and login redirection after make a request and my token has been expired.
I really don't know if is a common issue or if this thins can't be done, that's why I make the question.
My request to my api enponit redirects fine but, when I click the Execute button on swagger UI, the request to https://localhost:port/api/v1/Contacts, the response is 302 and then try to redirect me to my login provider with this issue and I cannot redirect to login again.
What I'm missing here????
this is my authentication config:
//Add Auth options
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddOpenIdConnect(options =>
{
options.ClientId = ClientId;
options.Authority = $"{Instance}/{TenantId}";
options.SignedOutRedirectUri = SignedOutRedirectUri;
// Add needed scopes
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("roles");
options.AuthenticationMethod = OpenIdConnectRedirectBehavior.RedirectGet;
options.SaveTokens = true;
options.Events = new OpenIdConnectEvents
{
OnRemoteFailure = context =>
{
context.HandleResponse();
return Task.CompletedTask;
}
};
})
.AddCookie()
.AddAzureAdBearer(options => configuration.Bind("bearer", options));