So I am using a custom authentication but the problem is that lb4 doesn't add a securitySchemes in the openApi.json nor in the routes I've decorated with #authenticate.
Should I add it manually to the openapi.json, if yes, how?
LoopBack 4 provides the concept of general OAS Enhancers. These allow merging of new slices of the OAS 3 spec into the existing spec.
These enhancers are bound to the application with asSpecEnhancer metadata.
In OpenAPI 3.0 specs, either the entire API or selective endpoints can be secured by an authentication mechanism.
Entire API spec
This denotes the entire API as secured by a single authentication mechanism. This is probably the most common scenario.
This is achieved by adding a Security Requirement Object into the OpenAPI Object.
The authentication component can provide a new binding for the spec enhancer:
import {bind} from '#loopback/core';
import {
asSpecEnhancer,
mergeOpenAPISpec,
OASEnhancer,
OpenApiSpec,
ReferenceObject,
SecuritySchemeObject,
} from '#loopback/openapi-v3';
export type SecuritySchemeObjects = {
[securityScheme: string]: SecuritySchemeObject | ReferenceObject;
};
export const OPERATION_SECURITY_SPEC = [
{
// secure all endpoints with 'jwt'
jwt: [],
},
];
export const SECURITY_SCHEME_SPEC: SecuritySchemeObjects = {
jwt: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT',
},
};
/**
* A spec enhancer to add bearer token OpenAPI security entry to
* `spec.component.securitySchemes`
*/
#bind(asSpecEnhancer)
export class SecuritySpecEnhancer implements OASEnhancer {
name = 'bearerAuth';
modifySpec(spec: OpenApiSpec): OpenApiSpec {
const patchSpec = {
components: {
securitySchemes: SECURITY_SCHEME_SPEC,
},
security: OPERATION_SECURITY_SPEC,
};
const mergedSpec = mergeOpenAPISpec(spec, patchSpec);
return mergedSpec;
}
}
// A bunch of stuff was not included in this component (e.g. the constructor to register the authentication provider) for bevity.
export class JWTAuthenticationComponent implements Component {
bindings: Binding[] = [
createBindingFromClass(SecuritySpecEnhancer),
];
}
Real-world example
The JWT authentication extension does the same thing, and can be used as reference.
Per-operation spec
This would denote only select endpoint(s) as being secured by an authentication provider. This is useful if different parts of the API use different authentication mechanisms (or none at all).
Unfortunately, LoopBack 4 does not yet have first-class support for this yet.
A workaround is to selectively define the Security Requirement Object on each Operation Object.
However, this is error-prone as it may be difficult to keep it in sync with the actual endpoints. Feel free to join the conversation to show interest in the feature. This would allow the maintainers to better prioritize based on community demand.
Related
I am trying to using an openAM external identity provider to authenticate users of the WSO2 developer portal.
To do that I need to dynamically add the role Internal/subscriber to user authenticated via openAM because we don't have roles in openAM.
So I added the following code to the Script Based Adaptive Authentication:
var onLoginRequest = function(context) {
executeStep(1, {
onSuccess: function (context) {
// Extracting authenticated subject from the first step.
var user = context.currentKnownSubject;
assignUserRoles(user, ['Internal/subscriber']);
}
});
};
Trying the authentication I see in the wso2 logs the error "assignUserRoles" is not defined:
TID: [-1234] [] [2021-06-10 10:57:34,273] ERROR {org.wso2.carbon.identity.application.authentication.framework.config.model.graph.JsGraphBuilder} - Error in executing the javascript for service provider : apim_devportal, Javascript Fragment :
function (context) {
// Extracting authenticated subject from the first step.
var user = context.currentKnownSubject;
assignUserRoles(user, ['Internal/subscriber']);
} <eval>:4 ReferenceError: "assignUserRoles" is not defined
at jdk.nashorn.internal.runtime.ECMAErrors.error(ECMAErrors.java:57)
at jdk.nashorn.internal.runtime.ECMAErrors.referenceError(ECMAErrors.java:319)
at jdk.nashorn.internal.runtime.ECMAErrors.referenceError(ECMAErrors.java:291)
at jdk.nashorn.internal.objects.Global.__noSuchProperty__(Global.java:1442)
Any idea on how to solve this? Or any other alternative to give a default Internal/subscriber to any user authenticated via OpenAM?
The above-mentioned error is expected in the API Manager servers. This is because the API Manager servers are not built to support Adaptive Authentication as WSO2 IS servers.
So, if you are planning to perform the Adaptive Authentication, the best option would be to deploy a WSO2 IS server as a Key Manager with API Manager and perform the task. Further, as an alternative way, we can implement a custom Provisioning Handler to assign the `Internal/subscriber' role to the provisioning users.
You can refer to the SystemRolesRetainedProvisionHandler.java implementation for more clarity. We can make use of the retrieveRolesToBeDeleted() to append the Internal/subscriber role into the rolesToAdd variable and then configure the custom provisioning handler in the API Manager with the following TOML config
[authentication.framework.extensions]
provisioning_handler = "com.sample.custom.CustomRoleProvisioningHandler"
A sample implementation is given below
// CustomRoleProvisioningHandler.java
import java.util.List;
import java.util.Map;
import org.wso2.carbon.identity.application.authentication.framework.exception.FrameworkException;
import org.wso2.carbon.identity.application.authentication.framework.handler.provisioning.impl.DefaultProvisioningHandler;
import org.wso2.carbon.identity.application.authentication.framework.handler.provisioning.impl.SystemRolesRetainedProvisionHandler;
import org.wso2.carbon.user.core.UserRealm;
import org.wso2.carbon.user.core.UserStoreException;
public class CustomRoleProvisioningHandler extends SystemRolesRetainedProvisionHandler {
#Override
public void handle(List<String> roles, String subject, Map<String, String> attributes,
String provisioningUserStoreId, String tenantDomain) throws FrameworkException {
roles.add("Internal/subscriber");
super.handle(roles, subject, attributes, provisioningUserStoreId, tenantDomain);
}
}
Further here are few other implementations for custom provisioning handlers
tharakawijekoon/custom-provisioning-handler
nipunthathsara/wso2-custom-provisioning-handler
Hope this helps to achieve your requirement.
I am trying to setup authentication in flask-restplus application. I want to add authentication to all endpoints in the application but don't want to write decorator on each route.
I am looking for apikey based authentication. The problem is, I am unable to identify how to intercept all requests and check for authentication token in the header.
Current Code:
authorization = {
'apikey': {
'type': 'apiKey',
'in': 'header',
'name': 'x-auth'
}
}
api = Api(
title='title',
version='1.0',
description="List of API's ",
validate=True,
authorizations=authorization,
security='apikey'
)
After doing the above steps, when I open swagger I can add token using the authorize button. But once the token is passed I am unable to intercept request & verify if token is correct or not.
Currently all the examples I could find, added another decorator on each route which I don't want as it leads to poor design & duplicate code.
Currently the closest example I got is :https://www.youtube.com/watch?v=xF30i_A6cRw&list=LLpaDwEA6bAAPZU5lz0ZRsuw&index=1
but it also uses decorator on each route.
So the problem statement is:
How to intercept all requests & check for correct token in there header without adding decorator on all routes
Very recently, I ran into a similar problem. But luckily we do have the Namespace that accepts a list of decorators, where in you can pass the custom decorator at Resource level, and it will be implemented by default to each method of that resource.
api = Namespace(
'some Name here',
description='some description',
security='apiKey',
authorizations = authorizations,
decorators= [token_required]
)
One point to note however, I had to just specify the security with each doc in the method, as under:
#api.doc('some operation', security = 'apiKey')
The beauty with this is that one click authorization flows to each method in the resource.
The API Gateway endpoints we are using shall be restricted via permissions to a specific audience.
The idea is to use the lambda authorizer to fetch the permissions from an external service and then create the policy to allow or deny access to the endpoint.
For the matching of the permissions to the API endpoint the endpoint would need to provide the permissions it needs to the authorizer.
My question is now how can I enrich the endpoint data with its own required permissions and use them in the authorizer lambda(probably via the event) for further validation.
Example:
User1 is forwarded to the first endpoint GET/petstore/pets(this endpoint needs the permission -> View:Pets)
Lambda authorizer requests the user permissions from the external service
The service returns: [View:Pets , View:Somethingelse, etc.]
The lambda authorizer matches the user permissions against the required endpoint permission and creates the Allow policy on a match
User2 does the same but does not have the permission for viewing pets, no match -> Deny
Here is my code for the lambda:
import {Callback, Context} from 'aws-lambda';
import {Authorizer} from './authorizer';
export class App {
constructor(private authorizer: Authorizer = new Authorizer()) {
}
public handleEvent(event, callback: Callback): Promise<void> {
return this.authorizer.checkAuthorization(event, callback)
.then((policy) => callback(null, policy))
.catch((error) => callback(error, null));
}
}
const app: App = new App();
module.exports.lambda_handler = async (event) => {
return await app.handleEvent(event);
};
Code for the checkAuthorization method:
export class Authorizer {
public resourceAuthorizer: ResourceAuthorizer = new ResourceAuthorizer();
public authenticationChecker: AuthenticationChecker = new AuthenticationChecker();
public checkAuthorization(event, callback): Promise<object> {
const endpointPermissions = event.endpointPermissions; // <== this is what I need, a custom field in the event which
// is provided from the api endpoint in some way
// in my example this whould contain a string or json
// with 'View:Pets' and 'View:Somethingelse'
return this.authenticationChecker.check(event)
.then((decodedJwt) => {
const principalId: string = decodedJwt.payload.sub;
return Promise.resolve(decodedJwt)
.then((jwt) => this.resourceAuthorizer.check(jwt, event.endpointPermissions))
.then((payload) => callback(null,
getAuthorizationPolicy(principalId, 'Allow', event.endpointPermissions, payload)))
.catch((payload) => callback(null,
getAuthorizationPolicy(principalId, 'Deny', event.endpointPermissions, payload)));
}).catch((error) => {
console.log(error);
callback('Unauthorized');
});
}
}
The event.endpointPermissions is basically what I am looking for. Depending on the API endpoint this should be filled with the permissions neccessary for that endpoint. The resourceAuthorizer then fetches the users Permissions from the external service and compares them to the endpointPermissions and then creates the Allow or Deny policies.
So where can I enter the endpointPermissions in my API Endpoint to provide them to the Authorizer?
The event being passed to the Authorizer contains a methodArn, which is in the format:
arn:aws:execute-api:<Region id>:<Account id>:<API id>/<Stage>/<Method>/<Resource path>
This would give you the Method and Resource Path that you need. It also would give you an identifier of the API, but not the name of the API itself.
The API id, can be used to get the API name by using the AWS SDK. See here.
This should give you everything you need to construct your endpointPermissions value.
I got a solution to my problem without having to parse the ARN, but it's pretty unconventional:
In the method request of a resource create URL query string parameters with the permission names and set the checkbox for 'required'
When the request is called from the client(Postman) these mandatory parameters have to be provided as keys, they are endpoint-specific. The values do not matter because only the keys will be used at evaluation.
The event received by the authorizer now contains the queryStringParameters which can be evaluated for further use.
I recently resumed work on a project that had lain dormant for a year. It was using Angular on AspNet Core 1.1 and using an early version of OpenIddict 1.0. It was developed using VS2017.
I updated VS2017 to the latest release (15.7.5) but the project would not compile and when I fixed the compilation errors it wouldn't run. So eventually I bit the bullet and decided to update the project to Asp Net Core 2.1 and to use the latest version of OpenIddict. I have the project so it compiles but when it starts it gives the error in the title, namely "InvalidOperationException: Scheme already exists: Bearer"
I can't see what is wrong. I understand that somewhere a second scheme named 'Bearer' is being added, but I can't figure out where. I am enclosing below my Startup.cs in its entirety.
using AspNet.Security.OpenIdConnect.Primitives;
using Microsoft.AspNetCore.Builder;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using SIAngular.DBContexts;
using SIAngular.Models;
using SIAngular.Services;
using OpenIddict.Abstractions;
using System.IdentityModel.Tokens.Jwt;
using Microsoft.IdentityModel.Tokens;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Authentication.JwtBearer;
namespace SIAngular
{
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddCors();
services.AddMvc();
services.AddDbContext<ApplicationDbContext>(options =>
{
// Configure the context to use Microsoft SQL Server.
options.UseSqlServer(Configuration.GetConnectionString("SqlConnection"));
// Register the entity sets needed by OpenIddict.
// Note: use the generic overload if you need
// to replace the default OpenIddict entities.
options.UseOpenIddict();
});
// Register the Identity services.
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
//.AddDefaultTokenProviders();
// Configure Identity to use the same JWT claims as OpenIddict instead
// of the legacy WS-Federation claims it uses by default (ClaimTypes),
// which saves you from doing the mapping in your authorization controller.
services.Configure<IdentityOptions>(options =>
{
options.ClaimsIdentity.UserNameClaimType = OpenIdConnectConstants.Claims.Name;
options.ClaimsIdentity.UserIdClaimType = OpenIdConnectConstants.Claims.Subject;
options.ClaimsIdentity.RoleClaimType = OpenIdConnectConstants.Claims.Role;
});
services.AddOpenIddict()
// Register the OpenIddict core services.
.AddCore(options =>
{
// Configure OpenIddict to use the Entity Framework Core stores and models.
options.UseEntityFrameworkCore()
.UseDbContext<ApplicationDbContext>();
})
// Register the OpenIddict server services.
.AddServer(options =>
{
// Register the ASP.NET Core MVC services used by OpenIddict.
// Note: if you don't call this method, you won't be able to
// bind OpenIdConnectRequest or OpenIdConnectResponse parameters.
options.UseMvc();
// Enable the token endpoint.
options .EnableTokenEndpoint("/connect/token");
options.AcceptAnonymousClients();
options.DisableScopeValidation();
// Note: the Mvc.Client sample only uses the code flow and the password flow, but you
// can enable the other flows if you need to support implicit or client credentials.
options.AllowPasswordFlow();
// Mark the "email", "profile" and "roles" scopes as supported scopes.
options.RegisterScopes(OpenIdConnectConstants.Scopes.Email,
OpenIdConnectConstants.Scopes.Profile,
OpenIddictConstants.Scopes.Roles);
// During development, you can disable the HTTPS requirement.
options.DisableHttpsRequirement();
// Note: to use JWT access tokens instead of the default
// encrypted format, the following lines are required:
//
options.UseJsonWebTokens();
options.AddEphemeralSigningKey();
})
// Register the OpenIddict validation services.
.AddValidation();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
JwtSecurityTokenHandler.DefaultOutboundClaimTypeMap.Clear();
services.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.Authority = "http://localhost:53244/";
options.Audience = "resource_server";
options.RequireHttpsMetadata = false;
//options.IncludeErrorDetails = true;
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = OpenIdConnectConstants.Claims.Subject,
RoleClaimType = OpenIdConnectConstants.Claims.Role
};
});
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
}
public void Configure(IApplicationBuilder app)
{
app.UseDeveloperExceptionPage();
app.UseAuthentication();
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}
}
}
Can someone please exp-lain what I am doing wrong. My intent was to follow the OpenIddict examples but clearly I went wrong somewhere.
The full stacktrace follows:
System.InvalidOperationException: Scheme already exists: Bearer
at Microsoft.AspNetCore.Authentication.AuthenticationOptions.AddScheme(String name, Action`1 configureBuilder)
at Microsoft.AspNetCore.Authentication.AuthenticationBuilder.<>c__DisplayClass4_0`2.<AddSchemeHelper>b__0(AuthenticationOptions o)
at Microsoft.Extensions.Options.ConfigureNamedOptions`1.Configure(String name, TOptions options)
at Microsoft.Extensions.Options.OptionsFactory`1.Create(String name)
at Microsoft.Extensions.Options.OptionsManager`1.<>c__DisplayClass5_0.<Get>b__0()
at System.Lazy`1.ViaFactory(LazyThreadSafetyMode mode)
at System.Lazy`1.ExecutionAndPublication(LazyHelper executionAndPublication, Boolean useDefaultConstructor)
at System.Lazy`1.CreateValue()
at Microsoft.Extensions.Options.OptionsCache`1.GetOrAdd(String name, Func`1 createOptions)
at Microsoft.Extensions.Options.OptionsManager`1.Get(String name)
at Microsoft.Extensions.Options.OptionsManager`1.get_Value()
at Microsoft.AspNetCore.Authentication.AuthenticationSchemeProvider..ctor(IOptions`1 options, IDictionary`2 schemes)
at Microsoft.AspNetCore.Authentication.AuthenticationSchemeProvider..ctor(IOptions`1 options)
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, ServiceProviderEngineScope scope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScoped(ScopedCallSite scopedCallSite, ServiceProviderEngineScope scope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitSingleton(SingletonCallSite singletonCallSite, ServiceProviderEngineScope scope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass1_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType)
at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType)
at Microsoft.Extensions.Internal.ActivatorUtilities.ConstructorMatcher.CreateInstance(IServiceProvider provider)
at Microsoft.Extensions.Internal.ActivatorUtilities.CreateInstance(IServiceProvider provider, Type instanceType, Object[] parameters)
at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.<>c__DisplayClass4_0.<UseMiddleware>b__0(RequestDelegate next)
at Microsoft.AspNetCore.Builder.Internal.ApplicationBuilder.Build()
at Microsoft.AspNetCore.Hosting.Internal.WebHost.BuildApplication()
at Microsoft.AspNetCore.Hosting.Internal.WebHost.StartAsync(CancellationToken cancellationToken)
at Microsoft.AspNetCore.Hosting.WebHostExtensions.RunAsync(IWebHost host, CancellationToken token, String shutdownMessage)
at Microsoft.AspNetCore.Hosting.WebHostExtensions.RunAsync(IWebHost host, CancellationToken token)
at Microsoft.AspNetCore.Hosting.WebHostExtensions.Run(IWebHost host)
at SIAngular.Program.Main(String[] args) in C:\Users\username\Documents\Visual Studio 2017\Projects\SIAngular\Program.cs:line 20
I finally found the answer which is probably obvious to OpenIddict experts, but not to casual users.
Since I am using JWT the.AddValidation() after the registration of the OpenIddict server options is not needed. This is obvious in hindsight but I hope this helps someone else with this problem. I am sure I am not thbe only person dumb enough to have been caught by this and when I look at OpenIddict samples now I understand, but I think the comment "For JWT tokens, use the Microsoft JWT bearer handler." could be amended to "For JWT tokens, use the Microsoft JWT bearer handler and remove the call to AddValidation below.
I have tried the below code and worked for me.
public void ConfigureServices(IServiceCollection services)
{
// Code omitted for brevity
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Audience = "https://localhost:5000/";
options.Authority = "https://localhost:5000/identity/";
})
.AddJwtBearer("AzureAD", options =>
{
options.Audience = "https://localhost:5000/";
options.Authority = "https://login.microsoftonline.com/eb971100-6f99-4bdc-8611-
1bc8edd7f436/";
});
}
You can read this complete document on the below URL:
https://learn.microsoft.com/en-us/aspnet/core/security/authorization/limitingidentitybyscheme?view=aspnetcore-6.0
I have tried other solution as well.
Please check if you have multiple startup.cs files and you are using any authentication schemes in that files.
and also check to publish folder/deployment folder, need to delete App_Data Folder before deploying fresh/ latest changes.
My question is related to PassportJS - Using multiple passports within Express application topic.
So as long as I already have two separate passport instances, I realized that they both share the same cookie, created here:
application.use(session({ secret: 'my secret cookie', key: 'usid' }));
I can add one more cookie with different name using this:
app.use(connect.session({ secret: 'keyboard cat', key: 'rsid' }))
However, it is not clear to me how to instruct each passport to use its own cookie.
I figured out temporary solution for passport 0.4.0 via
passport._key = passport._sm._key = 'customPassportSessionKey'
You can solve it by making from the router and not from the app. I've solved the same issue by developing something like an AuthBuilder which receives certain parameters, each instance generates a new passport that way.
class AuthBuilderService {
constructor() {
this.passport = new Passport();
this.registerSerializers(); // Multiple serializers
this.registerStrategies(); // Multiple strategies
}
Then you can register multiple routers with the same passport and key (or just one) by calling the authbuilder.addRouter(...)
addRouter(router, loginFileName, failureUrl, successUrl) {
router.use('/login', express.static(`${__dirname}/../views/whatever`, {
index: loginFileName,
fallthrough: true,
}));
router.use(session({
secret: 'any secret',
key: (_isRouterA()) ? 'a' : 'b',
resave: true,
saveUninitialized: true
}));
router.use(this.passport.initialize());
router.use(this.passport.session());
this._configureRouter(router, failureUrl, successUrl); // There I'm handling login, logout and some middleware.
return this;
}
From the routers you want to protect:
routerUsers_1.get('something_1', method)
routerUsers_2.get('something_2', method2)
let authBuilder = new AuthBuilderService();
authBuilder.addRouter(routerUsers_1, 'login_users_vip.html', '/path/failure_vip/', '/path/success_vip');
authBuilder.addRouter(routerUsers_2, 'login_users.html', '/path/failure/', '/path/success');
routerAdmins.get('something', methodAdmin)
new AuthBuilderService().addRouter(routerAdmins, 'login_admins.html', '/path2/failure/', '/path2/success');
Then express app just use each router.
app.use('path-client-vip', routerUsers_1)
app.use('path-client', routerUsers_2)
app.use('path-admin', routerAdmin)
I'm working with 2 webapps (with different users, logins and content) in the same express app server, each webapp uses different instances of this AuthBuilderService for multiple routers using different passport, sessions, strategies and serializers between each AuthBuilderService instance.
Hope it will help somebody.