I have a CakePHP application, which accesses multiple APIs. For these APIs, I store keys and password/ secret in a central configuration file.
Small example..
/**
* A set of Keys set as environment variables for Security Reasons.
*/
'openweathmapappID' => 'xxxxxxxxxxxxxxxxxxxxxx',
'googleplaceapikey' => 'xxxxxxxxxxxxxxxxxxxxxx',
I load these credentials centrally in app.php file.
Configure::load('app', 'default', false);
I then use them in my Controllers.
$key = Configure::read('openweathmapappID');
$response = $http->get('http://api.openweathermap.org/data/2.5/weather?q=London&APPID=' . $key);
Now how can I best secure my configuration file? Or is it already sufficiently secure?
Related
Context
I have a Service Provider (SP) based on IdentityServer 4 and Sustainsys.Saml2.AspNetCore2 that is configured to use Azure as an IdP (SAML2).
I also have a SPA with an api that connects to my SP (with oidp) to identify my user. The api then creates a JWT for my user to use.
I can login my user correctly.
Question
My issue comes with the logout. I want to use the logout url parameter of Azure to notify my SP about the logout. I manage to see the SAML Logout Request as a string when I configure an endpoint of mine but I can't exploit it and parsing it manually does't seem right.
Is there an existing endpoint that would come with my dependencies that I missed?
The goal here is to revoke all my user's application sessions (the apps to which my user is connected throug my SP).
Configuration
Idp configuration in the SP (called in Startup.cs).
The Saml2AuthModel comes from a config file.
public static AuthenticationBuilder AddSaml2Auth(this AuthenticationBuilder builder, Saml2AuthModel saml2AuthModel)
{
builder.AddSaml2(saml2AuthModel.Scheme, saml2AuthModel.DisplayName ?? saml2AuthModel.Scheme, options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.SignOutScheme = IdentityServerConstants.SignoutScheme;
options.SPOptions.EntityId = new EntityId(saml2AuthModel.ServiceProviderEntityId);
options.SPOptions.ModulePath = "/" + saml2AuthModel.ModulePath ?? saml2AuthModel.Scheme ?? options.SPOptions.ModulePath;
var idp = new IdentityProvider(
new EntityId(saml2AuthModel.IdentityProviderEntityId),
options.SPOptions
);
idp.MetadataLocation = saml2AuthModel.IdentityProviderMetadataLocation;
options.IdentityProviders.Add(idp);
});
return builder;
}
The Sustainsys.Saml2 library has support for single logout. To enable it, you need to set up a service signing key. The reason is that logout requests and responses should be signed. So the library doesn't expose the logout endpoints if it has no signing keys available.
We have a service architecture that currently only supports client authentication. A Java service based on spring boot and spring security issues long lived JWT based on tenants for other services to authenticate against each other. For example a render service needs to get templates from the template service.
We now want to build a user service with node.js that issues short lived tokens for users to also access some of those services and only access the resource visible to the user. For example the user wants to see only their templates in a list.
My question is: what do I need to watch out for when implementing the /auth resource on the user service? I have managed to issue a JWT with the required information and obviously the same secret in the user service to access the template service. But I'm not sure if it is secure enough. I had to add a random JID to the user JWT to get it accepted by the template service (which is also implemented with spring boot).
Is there a security issue I need to watch out for? Is this approach naiive?
This is my javascript code that issues the JWT:
const jwt = require('jwt-simple');
const secret = require('../config').jwtSecret;
const jti = require('../config').jti;
// payload contains userId and roles the user has
const encode = ({ payload, expiresInMinutes, tenantId}) => {
const now = new Date();
payload.jti = jti; // this is a UUID - spring security will otherwise not accept the JWT
payload.client_id = tenantId; // this is required by the template service which supports tenants identified through their clientId
const expiresAt = new Date(now.getTime() + expiresInMinutes * 60000);
payload.expiresAt = expiresAt;
return jwt.encode(payload, secret);
};
I think of adding some type information to the user JWT so that those java services that do not allow any User access can directly deny access for all user JWTs. Or maybe I can use the JTI here? Will research how spring boot handles that. I'll probably also have to add #Secured with a role distinction to all the services that allow user access to only some resources.
But those are technical details. My concern really is that I am unsure about wether the entire concept of using JWTs issued from different sources is secure enough or what I have to do in the user service to make it so.
Yeah your concept is right since you are the owner of jwt that means only you can write the jwt, others can read it but can not modify it.
So your userservice will create the token with certain information like userid and another service will decode that jwt fetch userid and validate that userid
Is it possible for a ServiceStack api to accept jwt tokens from multiple identity providers?
I have one admin application that will be calling all our apis across environments. I need to allow my api's to accept jwt tokens from two different identity providers. This can be accomplished in web api, by calling the .AddJwtBearer help method twice and not providing a default schema in the AddAuthentication() helper. And the providing both in the AddAuthorization helper method. I tested this out in ServiceStack and it is not working for me.
This is in the .net core startup, configure services.
services.AddAuthentication()
.AddJwtBearer("Bearer", options => {
options.Authority = Configuration["IDENTITYSRV_WEB_BASEURL"];
options.RequireHttpsMetadata = Boolean.Parse(Configuration["IDENTITY_HTTPSMETADATA"]);
options.Audience = Configuration["IDENTITY_VALIDAUDIENCE"];
})
.AddJwtBearer("Admin", options =>
{
options.Authority = "Configuration["IDENTITYSRV_WEB2_BASEURL"]";
options.RequireHttpsMetadata = Boolean.Parse(Configuration["IDENTITY_HTTPSMETADATA"]);
options.Audience = Configuration["IDENTITY_VALIDAUDIENCE"];
});
AppHost
AuthFeature auth = new AuthFeature(() => new AuthUserSession(), new IAuthProvider[] { new NetCoreIdentityAuthProvider(AppSettings), })
{
IncludeAssignRoleServices = false,
IncludeRegistrationService = false,
IncludeAuthMetadataProvider = false
};
Plugins.Add(auth);
Any suggestions or work around?
ServiceStack's JWT Auth Provider supports all HMAC and RSA crypto algorithms for its JWT or JWE tokens, but it can only be configured with the 1 algorithm you want it to use. So it's technically not possible for it to verify JWT Tokens from different authorities which would likely be configured with different keys and algorithms.
The next obstacle is that if all Identity providers were configured to use the same Key and Algorithm, they would need to encode the same contents ServiceStack uses in order for the JWT to be correctly deserialized into an Authenticated ServiceStack Session. Most of the names used in ServiceStack's JWT's have well-known names however there are others that don't like roles and perms, in which case ServiceStack JWT's have adopted the Azure Active Directory Conventions. Although there is an opportunity to apply custom logic when inspecting the verified JWT Body and populating the Session using the PopulateSessionFilter.
Ultimately I don't think trying to funnel multiple JWT's into the same implementation is a viable strategy, instead I would be looking at creating Custom JWT Auth Providers like JwtAuthProviderReader.cs which just knows how to handle parsing JWT's from a single provider which would then know how to verify & extract the Authenticated Session Info from each JWT and use it to populate a ServiceStack Session. This could be done in a "single uber JWT Auth Provider" that has knowledge in being able to parse every JWT sent to it, but as JWT Tokens are sent with the same Authorization: Bearer {Token} HTTP Header there would need to be some information in the JWT Headers that determines which crypto algorithms it should use to validate each token. If all Identity Providers use different alg then that might be enough to be able to distinguish the identity provider.
Either way there's no pre-built solution in ServiceStack that you could configure to support multiple identity Auth Providers, the other approach you may want to look at is to maintain all Authentication in a central Identity Server Auth Provider so then your Websites only need to be configured to support Authentication from the central Identity Server.
tl;dnr
In essence, my app was working fine until implementing a shared cookie. Now I have an infinite login redirect loop. I've spent hours mucking about with cookie configurations, testing locally, deploying to Azure, and failing. How can I force HTTPS inside an Azure App Service?
Like many others, I've run into the infinite login loop issue. My application was working fine with ASP.net Core 2.2 (on Dot Net 4.7.1) and then I moved to a "shared Auth cookie". In development, everything works fine from localhost, but as soon as I publish to an Azure App service (.azurewebsites.net domain) , I get the infinite login redirect loop.
To start things off, using an Azure Key Vault, I set up the "Data Protection" services following these instructions link.
and set my shared cookie as such for all applications:
services.AddDataProtection()
.SetApplicationName("custom-app")
.PersistKeysToAzureBlobStorage(cloudStorageAccount, azureBlobKeyStoragePath)
.ProtectKeysWithAzureKeyVault(keyVault, clientId, clientSecret);
var authCookie = new CookieBuilder() {
Name = ".AspNetCore.Auth.SharedCookie",
Path = "/",
Domain = ".mycustomdomain.com",
SecurePolicy = CookieSecurePolicy.SameAsRequest, // tried None as well without luck
SameSite = SameSiteMode.None, // I've tried Lax without any changes
HttpOnly = false,
IsEssential = true //changing this makes no difference
};
services
.AddAuthentication(sharedOptions => {
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultAuthenticateScheme = OpenIdConnectDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddAzureAdB2C(options => configuration.Bind("AzureAdB2C", options))
.AddCookie(options => { options.Cookie = authCookie; });
...
var corsOrigins = new[] {
"http://localhost",
"https://localhost",
"http://*.mycustomdomain.com",
"https://*.mycustomdomain.com",
"http://*.azurewebsites.net",
"https://*.azurewebsites.net",
"https://*.onmicrosoft.com",
"https://login.microsoftonline.com"
};
app.UseCors(cors => {
cors.WithOrigins(corsOrigins)
.SetIsOriginAllowedToAllowWildcardSubdomains()
.AllowCredentials();
cors.AllowAnyHeader();
cors.AllowAnyMethod();
});
Other than configuring the "Data Protection" services and additional CORS domains, no other changes were made to the application code.
My application is configured for HTTPS ... https://learn.microsoft.com/en-us/aspnet/core/security/enforcing-ssl?view=aspnetcore-2.2
...
app.UseHsts();
...
app.UseHttpsRedirection();
...
And I also verified that my Azure App Service is set for HTTPS access on both the "Custom domains" and "SSL settings" options.
Based on all the posts I have come across, I understand that the main issue may have to do with HTTPS redirecting from the Azure ADB2C endpoints and the shared cookie not being stored/read properly. THe reverse proxy drops the HTTPS and only calls HTTP. I just can't seem to figure out which combination should work. I am using the OutofProcess host, and I can see that the internal calls are HTTP. How can I get these to be HTTPS?
Side Note: I tried changing the Correlation or Nonce cookie domains as well, but this results in Correlation Errors.
TIA, any direction pointing would be appreciated.
Short list of posts I've referenced:
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/azure-ad-b2c?view=aspnetcore-2.2
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-2.2
How can I share Cookie Authentication across apps in Azure with .Net Core?
Share Cookie authentication between ASP.NET Core 2.2 and ASP. NET MVC 5 (.NET Framework 4.6.1) without Microsoft.Identity
Azure AD login inifinite loop
AspNet Core Identity - cookie not getting set in production
.net core 2.0 cookie authentication getting stuck in infinite redirect loop when trying to access over https
ASP.NET Core 2.1 cookie authentication appears to have server affinity
Configure cors to allow all subdomains using ASP.NET Core (Asp.net 5, MVC6, VNext)
https://github.com/aspnet/Security/issues/219
https://github.com/aspnet/Security/issues/1231
https://github.com/aspnet/Security/issues/1583
https://blogs.msdn.microsoft.com/benjaminperkins/2017/11/30/how-to-make-an-azure-app-service-https-only/
After some time off studying my configuration and creating a test app from scratch ... I came across the following post:
ASP.NET Core Sharing Identity Cookie across azure web apps on default domain (*.azurewebsites.net)
My current assumption is that this is indeed the culprit, I will try a new test tonight with one of my custom domains and see what happens.
The application uses OAuth2 flow to login on the users' O365 accounts and to store the returned access tokens in the session variable. The following code is used to store the tokens:
var authResult = await authContext.AcquireTokenByAuthorizationCodeAsync(
Request.Query["code"],
loginRedirectUri,
new ClientCredential(ConfigSettings.ClientId, ConfigSettings.ClientSecret),
ConfigSettings.O365UnifiedAPIResource);
var authResultEWS = await authContext.AcquireTokenByAuthorizationCodeAsync(
Request.Query["code"],
loginRedirectUri,
new ClientCredential(ConfigSettings.ClientId, ConfigSettings.ClientSecret),
ConfigSettings.EWSAPIResource);
HttpContext.Session.SetString(SessionKeys.Login.AccessToken, authResult.AccessToken);
HttpContext.Session.SetString(SessionKeys.Login.EWSAccessToken, authResultEWS.AccessToken);
And here is how we get the tokens back in our controllers:
private string GetSessionValue(string key)
{
byte[] buffer = new byte[2048];
HttpContext.Session.TryGetValue(key, out buffer);
return System.Text.Encoding.UTF8.GetString(buffer);
}
This soluton works on a local 5 nodes cluster but once published on an Azure 3 node cluster, the Session does not seem to work.
I used remote debugging and the access tokens are correctly added but once I call GetSessionValue, HttpContext.Session contains 0 key.
If using HttpContext.Session is a bad idea for distributed architectures like SF, what would be a good replacement solution ?
By default Session data is scoped to the node it runs on. In order to have a highly available (distributed) solution, you'd need to take the data and replicate it to other nodes.
Service Fabric Reliable Stateful Services and Actors have such mechanisms built in. You could use one of those to cache your (protected) access tokens. (and optionally serve as a gateway to O365)