Open sfmskywalker opened 2 years ago
Hi All
This is superb work. I've got a Elsa Server with Secured API against Azure AD. However I'm having troble setting the bearer token for the designer, that I'm using as a blazor component. JS Code:
function AuthorizationMiddlewarePlugin(elsaStudio) {
const eventBus = elsaStudio.eventBus;
eventBus.on('http-client-created', e => {
// Register Axios middleware.
e.service.register({
onRequest(request) {
request.headers = { 'Authorization': '${token}' }
return request;
}
});
});
}
Registration in my page:
const elsaStudioRoot = document.querySelector('elsa-studio-root');
elsaStudioRoot.addEventListener('initializing', e => {
const elsaStudio = e.detail;
elsaStudio.pluginManager.registerPlugin(AuthorizationMiddlewarePlugin);
});
It just doesn't seem to work. My C# is good, but my Axios and JS are pretty lightweight.
Any ideas, and I'll write the auth documentation for you :)
The errors I'm getting in the console are, unsurprisingly:
VM1166:1 GET https://localhost:5001/v1/features 401
(anonymous) @ VM1166:1
(anonymous) @ p-7f9fc0e9.js:2
However:
I have attempted to hard code the token, but even that doesn't work :(
@sfmskywalker any help will be gratefully receieved
Hi @nickbeau , can you tell me which Elsa package versions you are using? The fact that it fails on the /features
endpoint makes me believe that the issue may be solved when upgrading to the latest 2.6-preview release from MyGet. Perhaps you can give that a try and let me know? Alternatively, Elsa 2.6 is about to be released either today or tomorrow. But it would be good to know beforehand in case we can include a last-minute fix that makes it with 2.6 :)
Hi @sfmskywalker
We're using 2.5.0 across our solution, haven't tried 2.6 preview but can give it a crack tomorrow if that works :)
Yep, that works great :) Thanks!
Hi @sfmskywalker gave it a quick run, had a deadline but didn't take too much time, however superficially it seems to work! Thanks!
Great, thanks for letting me know! I’m releasing 2.6 coming Friday.
@sfmskywalker Quick question: how would you go about securing Elsa's API controllers with a different policy from the rest of the controllers?
Suppose I have an MVC app where I want Admin users to have access to the dashboard, but not regular users?
@sfmskywalker I managed to do it using this code:
public static IApplicationBuilder UseElsaApiAuthorization(this IApplicationBuilder app, string policyName)
{
return app.UseWhen(IsElsaApiRequest, x => x.Use(ApplyPolicy));
bool IsElsaApiRequest(HttpContext ctx)
{
var endpoint = ctx.Features.Get<IEndpointFeature>()?.Endpoint;
var descriptor = endpoint?.Metadata.GetMetadata<ControllerActionDescriptor>();
var controllerAssembly = descriptor?.ControllerTypeInfo.Assembly;
return controllerAssembly == typeof(Elsa.Server.Api.ElsaApiOptions).Assembly;
}
async Task ApplyPolicy(HttpContext ctx, Func<Task> next)
{
var authorizationService = ctx.RequestServices.GetRequiredService<IAuthorizationService>();
var authorizationResult = await authorizationService.AuthorizeAsync(ctx.User, policyName);
if (authorizationResult.Succeeded)
{
await next();
}
else
{
ctx.Response.StatusCode = 403;
}
}
}
I'm not sure if there's a better way, but this works for me right now.
@sfmskywalker I managed to do it using this code:
public static IApplicationBuilder UseElsaApiAuthorization(this IApplicationBuilder app, string policyName) { return app.UseWhen(IsElsaApiRequest, x => x.Use(ApplyPolicy)); bool IsElsaApiRequest(HttpContext ctx) { var endpoint = ctx.Features.Get<IEndpointFeature>()?.Endpoint; var descriptor = endpoint?.Metadata.GetMetadata<ControllerActionDescriptor>(); var controllerAssembly = descriptor?.ControllerTypeInfo.Assembly; return controllerAssembly == typeof(Elsa.Server.Api.ElsaApiOptions).Assembly; } async Task ApplyPolicy(HttpContext ctx, Func<Task> next) { var authorizationService = ctx.RequestServices.GetRequiredService<IAuthorizationService>(); var authorizationResult = await authorizationService.AuthorizeAsync(ctx.User, policyName); if (authorizationResult.Succeeded) { await next(); } else { ctx.Response.StatusCode = 403; } } }
I'm not sure if there's a better way, but this works for me right now.
Thanks @leddt for sharing, is this for api authentication only not dashboard?
@therese-william that's right. The dashboard can still be accessed, but it will not work as it depends on the API. I did not investigate for a way to secure the page itself. I wouldn't be surprised if it could be done in a similar way.
@sfmskywalker in this block:
const getAccessToken = async () => { const httpClient = axios.create({ baseURL: window.location.origin });
I'm getting ReferenceError: axios is not defined
. How can I import axios to use it in this plugin? Thanks
@leddt Hi, did you, by any chance, find how to apply authentication to the dashboard? I have an authentication service set up so that it should redirect to my IdP service if I access "http://localhost:5001/" or "http://localhost:5001/workflow-definitions", but it doesn't redirect from this URL. However, it redirects from any of elsa api endpoints, like "http://localhost:5001/v1/workflow-definitions". Do you know what might be the case?
@ArmyOfNinjas I did not, sorry. I think Elsa should expose some more official way of doing this, as my code for API authorization is already somewhat of a hack.
Does anyone have any ideas of how I can log who did what in Elsa? I was able to secure the system without finding this without issues but as I was trying to figure out a way to secure the controllers with policies I stumbled upon this solution. I'll give leddt's solution a try for securing my controllers with specific policies. Any ideas on logging the user that made the updates to the database would be appreciated.
One issue I'm noticing is that, using blazor, I can't find a way to quickly attach an event listener to elsa studio before the elsa js module does its thing and starts firing off HTTP calls before I've supplied my authentication middleware. Is there something obvious I'm missing?
I was thinking one thing I could add the JS in-line, but that doesn't seem to be supported with blazor (for good reason).
@sjd2021 I had a similar issue. Blazor doesn't follow the typical DOM event loading model. You have to tap into an event after render. This is the code I use on one of my blazor pages that feeds a token to the axios middleware. The RegesterElsa function would probably better named RegisterToken or something like that. That is nothing more than the javascript in this artilce listed above. Let us know how it works out for you.
[Parameter]
public string workflowDefinitionId { get; set; } = string.Empty;
public string AccessToken { get; set; }
[Inject]
IAccessTokenProvider TokenProvider { get; set; }
protected async override Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
var accessTokenResult = await TokenProvider.RequestAccessToken();
AccessToken = string.Empty;
if (accessTokenResult.TryGetToken(out var token))
{
AccessToken = token.Value;
}
await JS.InvokeVoidAsync("RegisterElsa",@AccessToken);
}
@manicfarmer1 I've actually been trying to use OnAfterRenderAsync (only I'm not calling base.OnAfterRenderAsync since it appears to do nothing), but unless I put some sort of delay in there, it never waits until the elsa-studio-root element is there. Even with the delay, it's sometimes not there. But then my concern is that the module that starts converting elsa-studio-root into its fully fleshed out object might end up firing off an unauthenticated call to /v1/features or something too soon if I set too long of a wait.
edit: It looks like there's also a case where it can find the element and attach the event listener, but it's too late because the element is already initialized. We're contemplating using a mutation observer, but it's really not ideal.
@sjd2021 I did wrap the javascript in a function for JS interop. I did have some issues like you described where it would call the service call before the token was obtained. I put an alert in there and it was getting called twice. The structure I have now has no issues though. I think I had to add that null reference check for the elsaStudioRoot but I can't remember for sure.
btw...access-control-headers is probably more specific to my implementation and probably not needed.
function RegisterElsa(token) {
const elsaStudioRoot = document.querySelector('elsa-studio-root');
if (elsaStudioRoot != null) {
elsaStudioRoot.addEventListener('initializing', e => {
const elsaStudio = e.detail;
elsaStudio.pluginManager.registerPlugin(AuthorizationMiddlewarePlugin);
});
}
function AuthorizationMiddlewarePlugin(elsaStudio) {
const eventBus = elsaStudio.eventBus;
eventBus.on('http-client-created', e => {
// Register Axios middleware.
e.service.register({
onRequest(request) {
var bearerToken = "Bearer " + token;
request.headers = { 'Authorization': bearerToken, 'Access-Control-Allow-Headers': 'access-control-allow-headers,access-control-allow-methods,access-control-allow-origin,authorization', 'Content-Type': 'application/json' }
return request;
}
});
});
}
}
Hi, How can we have sepearte authorization groups to control access to ELSA APIs and Dashboard? Like ReadAccess group to access APIs and AdminAccess group to access Dashboard...
I am writing my own web api for Elsa in my project and not include their web api in my project. It is a lot of work but I know no other way to achieve this and I want the Apis that interface the workflow engine to be modeled the same as my application. I suggest taking a look at Elsa 3 also as that is the newest version they are working on. There is a discord server as well that you can ask questions and probably get better answers from community members.
Elsa 2 doesn't support fine-grained control over what permissions a user should have, unfortunately.
However, Elsa 3 does have this. It also includes a default Identity module that you can use to create users and roles, which in turn have permissions. This module is optional, however, and you can completely control how to create a ClaimsPrincipal and its permissions claim. For example, if you use Auth0 as your identity provider, you can install it just like you would in any other ASP.NET API application.
The interesting part will be creating a custom designer plugin (ideally using StencilJS) that redirects to Auth0 to let the user sign in and then redirect back to the designer app, from where your plugin receives the acces tokens so that your plugin can attach them to outgoing HTTP requests sent to the workflow API endpoints.
A customer is working on exactly that, so it's possible that they will open source it for others to use as well. If not, I will eventually provide an implementation myself, as time permits.
@manicfarmer1 If you use Elsa 3, you might try using the endpoints provided from Elsa.Workflows.Api, which use FastEndpoints instead of API controllers. They are configured with fine-grained permissions.
Alternatively, I am considering moving the implementation of each endpoint to mediator request/response handlers, so that you don't have to repeat the implementation details. Instead, all you should have to do from your controllers is send the appropriate request model and then return the response model. Your controllers then only need to make sure they expose the expected routes and verbs, and you are in full control of API security.
i have aissue i used this lisk https://aspnetzero.com/blog/integrating-elsa-with-aspnet-zero-angular to integrating ELSA with ASP.NET Zero (Angular) every thing was working fine but when i use this way to send token all basic header removed befor: after use :
and i replaced
this: request.headers = { Authorization: Bearer ${token}
};
to
this request.headers = {
...request.headers,
Authorization: 'Bearer secret-token',
};
SSO is very important to connect the Elsa to existing system.
As soon as this feature is available, I'll start an ELSA project. until SSO is available, especially integration with MS Entra ID, I'll be on standby waiting for this feature. As an applications architect for my organization, I don't want account silos with different passwords for signing in regardless of how awesome and powerful ELSA is.
I assume the title is good to add 'SSO (Single Sign On)' words, etc. , then developer can understand the topic better.
We need to document how to configure the workflow server (ASP.NET Core) with authentication middleware and securing the Elsa API controllers and how to configure the dashboard with a plugin to send access tokens to the backend.
The documentation should be created as a guide and should describe the following:
Identity Provider
ASP.NET Core
Dashboard
Some sample snippets that can be used as input for the documentation:
In startup:
In the front-end, the following plugin can be created to read an access token from a locally stored cookie:
To register a plugin, see: https://elsa-workflows.github.io/elsa-core/docs/next/extensibility/extensibility-designer-plugins#custom-plugins.