SciCatProject / scicat-backend-next

SciCat Data Catalogue Backend
https://scicatproject.github.io/documentation/
BSD 3-Clause "New" or "Revised" License
19 stars 21 forks source link

Support for multiple frontends with the same backend #1352

Open alahiff opened 1 month ago

alahiff commented 1 month ago

Summary

We would like to have multiple frontends sharing a single backend, using Keycloak for authentication. Each frontend would be for a different experiment and require different configuration (hence the reason for multiple frontends).

Current Behaviour

I tried removing OIDC_SUCCESS_URL from the backend config since there can be multiple possible URLs, depending on the frontend being used. I was expecting the code introduced by https://github.com/SciCatProject/scicat-backend-next/pull/603 to ensure this works correctly, but the backend gives:

[Nest] 1  - 08/01/2024, 12:40:05 PM   ERROR "Internal server error"
[Nest] 1  - 08/01/2024, 12:40:05 PM   ERROR TypeError [ERR_INVALID_URL]: Invalid URL

Adding some console.log statements into the backend it seems that request in getRequest in https://github.com/SciCatProject/scicat-backend-next/blob/master/src/auth/guards/oidc.guard.ts does contain the correct value of referer in the header. However, after authentication with Keycloak the backend sets referer to undefined (in oidc.guard.ts), so the information about referer obtained in the previous step was lost.

Expected Behaviour

When OIDC_SUCCESS_URL is not defined in the backend config, after succesful authentication users would get redirected back to the frontend they had originally come from (due to the referer header).

Details

I am using SciCatLive 2.8.0.

bpedersen2 commented 1 month ago

I think the better approach would be to split the auth in two parts instead (which is a very typical OIDC flow):

I did not try it yet, but my feeling that passing a valid token should already work, so the alternate clients just need to do the oidc-login themself ( there are suitable libs out there for most frameworks).

Otherwise we open up for referer-spoofing attacks.

alahiff commented 1 month ago

Thanks for the reply. That's pretty much what I was planning to do, with a completely custom frontend. I just need to work out how to actually create the users in the backend in this case (i.e. so there are appropriate documents in the UserIdentity collection).

I was also hoping it might be possible to run multiple instances of the official (unmodified) SciCat frontend instead as an option, but if not, that's not a problem.

nitrosx commented 1 month ago

You can configure multiple FE with a single BE. Each user working on the FE will get its own token.

Could you elaborate more on what you mean by "create users in the BE"?

I'm really curious to see a proof of concept of the solution that you two (@alahiff and @bpedersen2) mention in the post above

alahiff commented 1 month ago

You can multiple FE with a single BE. Each user working on the FE will get its own token.

How do you do this? The backend has the configuration OIDC_SUCCESS_URL, which means that after authentication via Keyclock users will always be redirected back to a single FE, rather than the FE they originally tried to login from. Maybe there's something simple I'm missing :-)

nitrosx commented 1 month ago

@alahiff my mistake. Apologies for the confusion. You are correct. I answered before I reviewed completely your and @bpedersen2 answers. We need some code refactoring before we can do that.

alahiff commented 1 month ago

Could you elaborate more on what you mean by "create users in the BE"?

If the FE is integrated with Keycloak (rather than the BE) then after successful authentication the frontend could create a token which could then be provided when querying the backend. I was thinking that the BE wouldn't even need to be integrated with OIDC, but of course would need to know the same secret as the FE in order to validate the tokens. The existing JWT auth in the backend could be used.

Looking at an existing token created by the BE, it contains _id and id (identical values) which refer to an existing user in MongoDB. So the problem with the above is that somehow the FE needs to get the id of the user identity from the BE, and to trigger creation of a new identity when a user logs in for the first time.

I'm not sure if this could be done using the existing user API in the BE, or maybe the existing OIDC integration in the BE could somehow be leveraged, as it already handles creating the user identity: https://github.com/SciCatProject/scicat-backend-next/blob/1cd3af406897eb8d386722a52081dc462fbd4298/src/auth/strategies/oidc.strategy.ts#L125

bpedersen2 commented 1 month ago

No, that is not how oidc auth should be used.

The typical flow is:

nitrosx commented 1 month ago

I trust @bpedersen2 to provide the correct information. I think I need to go back and read once more the OIDC documentation.

@alahiff: just to make sure that I understand correctly what you are trying to achieve, can you confirm that my following statement is true:

You would like to be able to deploy a single backend and multiple frontends with different URLs. You would like to be able to authenticate through OIDC from any of the frontend and be able to be redirected back to the correct one.

Thanks

alahiff commented 1 month ago

@nitrosx Yes, that statement is true.

nitrosx commented 1 month ago

@alahiff thanks for confirming. I will raise the question with the community at the next collaborators meeting, which is this afternoon.

alahiff commented 1 month ago

On each request to the BE this token is passsed to the BE

The current tokens accepted by the backend look like this:

{
  "_id": "66a248704e178fc76d15cd09",
  "username": "oidc-user",
  "email": "oidc-user@facility.com",
  "authStrategy": "oidc",
  "__v": 0,
  "id": "66a248704e178fc76d15cd09",
  "userId": "66a248704e178fc76d15cd09",
  "iat": 1722945688,
  "exp": 1722949288
}

How does the FE know the id in your step 2? (as it's something generated & stored in the backend).

I suppose one option is use in the tokens the ids from the OIDC provider instead, and modify the backend appropriately.

nitrosx commented 1 month ago

@alahiff I just chatted with the collaborators and @minottic pointed me to the following code: https://github.com/SciCatProject/scicat-backend-next/blob/master/src/auth/auth.controller.ts#L92-L95

According to this section of the code, the url that you are redirected to upon successful login is configured in the backend configuration under the oidc.successUrl. If you leave that entry empty, it will use the referrer field that is present in the request header.

Let me know if this help and your feedback on how to improve the documentation

alahiff commented 1 month ago

@nitrosx That was one of the first things I tried but it doesn't work unfortunately. After authentication with Keycloak referrer is undefined and the backend gives this error:

[Nest] 1  - 08/01/2024, 12:40:05 PM   ERROR "Internal server error"
[Nest] 1  - 08/01/2024, 12:40:05 PM   ERROR TypeError [ERR_INVALID_URL]: Invalid URL
nitrosx commented 1 month ago

Thanks for confirming. I will inquire more with the collaborators that have done it in the past and let you know.<

Now I'm wondering how we can test this use case in CI/CD

minottic commented 1 month ago

the snippet that Max shared tries to get the referer from the BE callback, and, given what you say, the referer is empty there because kc intercepts it and unset it. This is because of the login flow:

FE --> BE (init) --> KC --> BE (callback) --> FE

Given that the referer is inspected in the BE (callback), its value is lost because of the KC step. What one can do, I think, is to inspect the referer in the BE (init) instead and set it as the BE session FE successUrl. I think this can be done by modifying this part of the code, probably the OidcAuthGuard

minottic commented 1 month ago

I agree @bpedersen2's flow is a better implementation of OIDC, closer to PKCE