itpropro / nuxt-oidc-auth

OIDC (OpenID connect) focused auth module for Nuxt
https://nuxtoidc.cloud
MIT License
83 stars 22 forks source link

`const { user } = useOidcAuth();` should return `userInfo` #58

Open mrleblanc101 opened 1 month ago

mrleblanc101 commented 1 month ago

Hi, I believe it would be more logical to that the user object returned from useOidcAuth() to be the sub-key userInfo directly.

This:

  "userInfo": {
    "sub": "5a44613e-2a1a-4f25-8e26-72aba37fe0e6",
    "resource_access": {
      "campus-virtuel-laravel": {
        "roles": [
          "entrepreneur"
        ]
      },
      "account": {
        "roles": [
          "manage-account",
          "manage-account-links",
          "view-profile"
        ]
      }
    },
    "email_verified": true,
    "name": "Sébastien LeBlanc",
    "preferred_username": "eeq_dev_user1@qa.libeo.com",
    "given_name": "Sébastien",
    "locale": "fr",
    "family_name": "LeBlanc",
    "email": "eeq_dev_user1@qa.libeo.com"
  },

Currently, it forces us to use unnecessary chaining all the time, ex:user.userInfo.given_name or user.userInfo.email. Most of what is currently set in the user object is not really user related, but authetification metadata. For exemple user.canRefresh doesn't make much sense, it's not the user that can refresh, but the scheme/provider/token that can be refreshed.

{
  "canRefresh": true,
  "loggedInAt": 1727192810,
  "updatedAt": 1727192810,
  "expireAt": 1727193410,
  "provider": "keycloak",
  "userInfo": { ... },
  "userName": "Sébastien LeBlanc",
  "accessToken": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJnQkNVNXlCS1hwb3dyLTVSTmpMQk9wbkJsWFM3aWNaam0xcjRSckxSU0JzIn0.eyJleHAiOjE3MjcxOTM0MTAsImlhdCI6MTcyNzE5MjgxMCwiYXV0aF90aW1lIjoxNzI3MTkyODEwLCJqdGkiOiJkMzNiZmI3ZS02OGI1LTQ1NjYtYTFlOC1lNTRlYWJjNTFhYzkiLCJpc3MiOiJodHRwOi8vZWVxLXNzby5sb2NhbC52aWNpLmlvL3JlYWxtcy9lZXEiLCJhdWQiOlsiY2FtcHVzLXZpcnR1ZWwtbGFyYXZlbCIsImFjY291bnQiXSwic3ViIjoiNWE0NDYxM2UtMmExYS00ZjI1LThlMjYtNzJhYmEzN2ZlMGU2IiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiY2FtcHVzLXZpcnR1ZWwtdnVlanMiLCJzaWQiOiIyMGNiMzI1NC1iMzJiLTQyYWMtYjE1ZS03MjM2NWViNTZkYzEiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbImh0dHA6Ly9sb2NhbGhvc3Q6MzAwMCJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiIsInVzZXIiXX0sInJlc291cmNlX2FjY2VzcyI6eyJjYW1wdXMtdmlydHVlbC1sYXJhdmVsIjp7InJvbGVzIjpbImVudHJlcHJlbmV1ciJdfSwiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJvcGVuaWQgcHJvZmlsZSBlbWFpbCIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJuYW1lIjoiTWF0aGlldSBTYXZhZ2UiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJlZXFfZGV2X3VzZXIxQHFhLmxpYmVvLmNvbSIsImdpdmVuX25hbWUiOiJNYXRoaWV1IiwibG9jYWxlIjoiZnIiLCJmYW1pbHlfbmFtZSI6IlNhdmFnZSIsImVtYWlsIjoiZWVxX2Rldl91c2VyMUBxYS5saWJlby5jb20ifQ.Ze29XOJQf8oABDBLIpBxdtIGaYMs1I4FQGbUs9yjisqeLMpcG8bx5b9dTNNboRGVWEBoMh10g4-jBVqff6b3N8-ddG0FdCO1aUtRnEDj3k2-OHNh8F2DVexSyvI8nX1JebWsxThKHkeKaf--TDV1ZLB4irH_dNx6qaNJc3ZvW_-dcZhqSMztDkBKkOEUpsW2z6erg9nGq5FeVijeWVgFtkC2L3YMWJtPba0KF3dH5OwI4MZu8gedj1dt_XChDFGqLoA5sAJ2jOPphOkC_6pCLKXv92cvsSXWc_rzNgKMNj1RPFpOiEU-NKJMsmWq92DkZMwe9I7INEFoC73IWaD5SA",
  "idToken": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJnQkNVNXlCS1hwb3dyLTVSTmpMQk9wbkJsWFM3aWNaam0xcjRSckxSU0JzIn0.eyJleHAiOjE3MjcxOTM0MTAsImlhdCI6MTcyNzE5MjgxMCwiYXV0aF90aW1lIjoxNzI3MTkyODEwLCJqdGkiOiIzMjBiNGQ0Yi1kNmQ4LTQxZDgtYjQxMC1jNWRjN2QxYTVmNTUiLCJpc3MiOiJodHRwOi8vZWVxLXNzby5sb2NhbC52aWNpLmlvL3JlYWxtcy9lZXEiLCJhdWQiOiJjYW1wdXMtdmlydHVlbC12dWVqcyIsInN1YiI6IjVhNDQ2MTNlLTJhMWEtNGYyNS04ZTI2LTcyYWJhMzdmZTBlNiIsInR5cCI6IklEIiwiYXpwIjoiY2FtcHVzLXZpcnR1ZWwtdnVlanMiLCJub25jZSI6IndxTENqajgzdzc3RGhjT1ZVY0twdzdzanc0ckRqOEtDWGNPSHc2ckN0d2hxdzQwciIsInNpZCI6IjIwY2IzMjU0LWIzMmItNDJhYy1iMTVlLTcyMzY1ZWI1NmRjMSIsImF0X2hhc2giOiJnUEM2SmE3NWszWF9IUGp6XzQzdlJ3IiwiYWNyIjoiMSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJuYW1lIjoiTWF0aGlldSBTYXZhZ2UiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJlZXFfZGV2X3VzZXIxQHFhLmxpYmVvLmNvbSIsImdpdmVuX25hbWUiOiJNYXRoaWV1IiwibG9jYWxlIjoiZnIiLCJmYW1pbHlfbmFtZSI6IlNhdmFnZSIsImVtYWlsIjoiZWVxX2Rldl91c2VyMUBxYS5saWJlby5jb20ifQ.U3x9XiHjnc8CPoHiP0cO4-w-VtCwDZj6-2XesA-SreVPbuBofg9-HB1gqpfDyjJ0IUmlKuoDQ39b8b27r6HN-YcJtJdypHZSIa8XnPrC0DGEq6Q3kmVZmJsnR90mQQ5S1Bjb1ZE-YSSHULfJHiUXfkit1rpwEhSHVbAvZwBUGXubY61nt18p9-Li1tDwpM9sp-VdOkY1UKYdvI_NaBmcUlMeoOPGW_EA0vO9J2rZ00A3sDW2Ix1frjburng_IaiWm5N2H6S4sxaP8McedjylJyeX0XktLo309j6Djpg_hBDt1z2TF04n4c0Z_rUlSugT4Gpkgju5ql3kWEH12A5mZQ",
}

It is very rare that the front-end would need to access the keys that are currently in the user object. Otherwise, I believe there should be a way to extract userInfo directly from the composable.

mrleblanc101 commented 1 month ago

My proposal:

const { user } = useOidcAuth(); would return what is currently the userInfo object:

  "userInfo": {
    "sub": "5a44613e-2a1a-4f25-8e26-72aba37fe0e6",
    "resource_access": {
      "campus-virtuel-laravel": {
        "roles": [
          "entrepreneur"
        ]
      },
      "account": {
        "roles": [
          "manage-account",
          "manage-account-links",
          "view-profile"
        ]
      }
    },
    "email_verified": true,
    "name": "Sébastien LeBlanc",
    "preferred_username": "eeq_dev_user1@qa.libeo.com",
    "given_name": "Sébastien",
    "locale": "fr",
    "family_name": "LeBlanc",
    "email": "eeq_dev_user1@qa.libeo.com"
  },

const { session } = useOidcAuth(); would return what is currently the user object, without including the userInfo (and username ?) key:

{
  "canRefresh": true,
  "loggedInAt": 1727192810,
  "updatedAt": 1727192810,
  "expireAt": 1727193410,
  "provider": "keycloak",
  "accessToken": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJnQkNVNXlCS1hwb3dyLTVSTmpMQk9wbkJsWFM3aWNaam0xcjRSckxSU0JzIn0.eyJleHAiOjE3MjcxOTM0MTAsImlhdCI6MTcyNzE5MjgxMCwiYXV0aF90aW1lIjoxNzI3MTkyODEwLCJqdGkiOiJkMzNiZmI3ZS02OGI1LTQ1NjYtYTFlOC1lNTRlYWJjNTFhYzkiLCJpc3MiOiJodHRwOi8vZWVxLXNzby5sb2NhbC52aWNpLmlvL3JlYWxtcy9lZXEiLCJhdWQiOlsiY2FtcHVzLXZpcnR1ZWwtbGFyYXZlbCIsImFjY291bnQiXSwic3ViIjoiNWE0NDYxM2UtMmExYS00ZjI1LThlMjYtNzJhYmEzN2ZlMGU2IiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiY2FtcHVzLXZpcnR1ZWwtdnVlanMiLCJzaWQiOiIyMGNiMzI1NC1iMzJiLTQyYWMtYjE1ZS03MjM2NWViNTZkYzEiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbImh0dHA6Ly9sb2NhbGhvc3Q6MzAwMCJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiIsInVzZXIiXX0sInJlc291cmNlX2FjY2VzcyI6eyJjYW1wdXMtdmlydHVlbC1sYXJhdmVsIjp7InJvbGVzIjpbImVudHJlcHJlbmV1ciJdfSwiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJvcGVuaWQgcHJvZmlsZSBlbWFpbCIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJuYW1lIjoiTWF0aGlldSBTYXZhZ2UiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJlZXFfZGV2X3VzZXIxQHFhLmxpYmVvLmNvbSIsImdpdmVuX25hbWUiOiJNYXRoaWV1IiwibG9jYWxlIjoiZnIiLCJmYW1pbHlfbmFtZSI6IlNhdmFnZSIsImVtYWlsIjoiZWVxX2Rldl91c2VyMUBxYS5saWJlby5jb20ifQ.Ze29XOJQf8oABDBLIpBxdtIGaYMs1I4FQGbUs9yjisqeLMpcG8bx5b9dTNNboRGVWEBoMh10g4-jBVqff6b3N8-ddG0FdCO1aUtRnEDj3k2-OHNh8F2DVexSyvI8nX1JebWsxThKHkeKaf--TDV1ZLB4irH_dNx6qaNJc3ZvW_-dcZhqSMztDkBKkOEUpsW2z6erg9nGq5FeVijeWVgFtkC2L3YMWJtPba0KF3dH5OwI4MZu8gedj1dt_XChDFGqLoA5sAJ2jOPphOkC_6pCLKXv92cvsSXWc_rzNgKMNj1RPFpOiEU-NKJMsmWq92DkZMwe9I7INEFoC73IWaD5SA",
  "idToken": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJnQkNVNXlCS1hwb3dyLTVSTmpMQk9wbkJsWFM3aWNaam0xcjRSckxSU0JzIn0.eyJleHAiOjE3MjcxOTM0MTAsImlhdCI6MTcyNzE5MjgxMCwiYXV0aF90aW1lIjoxNzI3MTkyODEwLCJqdGkiOiIzMjBiNGQ0Yi1kNmQ4LTQxZDgtYjQxMC1jNWRjN2QxYTVmNTUiLCJpc3MiOiJodHRwOi8vZWVxLXNzby5sb2NhbC52aWNpLmlvL3JlYWxtcy9lZXEiLCJhdWQiOiJjYW1wdXMtdmlydHVlbC12dWVqcyIsInN1YiI6IjVhNDQ2MTNlLTJhMWEtNGYyNS04ZTI2LTcyYWJhMzdmZTBlNiIsInR5cCI6IklEIiwiYXpwIjoiY2FtcHVzLXZpcnR1ZWwtdnVlanMiLCJub25jZSI6IndxTENqajgzdzc3RGhjT1ZVY0twdzdzanc0ckRqOEtDWGNPSHc2ckN0d2hxdzQwciIsInNpZCI6IjIwY2IzMjU0LWIzMmItNDJhYy1iMTVlLTcyMzY1ZWI1NmRjMSIsImF0X2hhc2giOiJnUEM2SmE3NWszWF9IUGp6XzQzdlJ3IiwiYWNyIjoiMSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJuYW1lIjoiTWF0aGlldSBTYXZhZ2UiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJlZXFfZGV2X3VzZXIxQHFhLmxpYmVvLmNvbSIsImdpdmVuX25hbWUiOiJNYXRoaWV1IiwibG9jYWxlIjoiZnIiLCJmYW1pbHlfbmFtZSI6IlNhdmFnZSIsImVtYWlsIjoiZWVxX2Rldl91c2VyMUBxYS5saWJlby5jb20ifQ.U3x9XiHjnc8CPoHiP0cO4-w-VtCwDZj6-2XesA-SreVPbuBofg9-HB1gqpfDyjJ0IUmlKuoDQ39b8b27r6HN-YcJtJdypHZSIa8XnPrC0DGEq6Q3kmVZmJsnR90mQQ5S1Bjb1ZE-YSSHULfJHiUXfkit1rpwEhSHVbAvZwBUGXubY61nt18p9-Li1tDwpM9sp-VdOkY1UKYdvI_NaBmcUlMeoOPGW_EA0vO9J2rZ00A3sDW2Ix1frjburng_IaiWm5N2H6S4sxaP8McedjylJyeX0XktLo309j6Djpg_hBDt1z2TF04n4c0Z_rUlSugT4Gpkgju5ql3kWEH12A5mZQ",
}
itpropro commented 1 month ago

I don't really like that idea, as the focus of this library is a oidc compliant implementation. The userInfo field can be undefined depending on the identity provider (it's just the result of the userInfo endpoint), so it's not really the user object itself. It would also leave out the claims property, which is often as important as the userInfo. It is also formally not the session, so the name session would also be confusing. For most use cases, either the idToken is parsed by the application or the accessToken is used (preferably in the backend of course) to access the user database with the actual user profile. If you just need the userInfo object, you can also reactively destructure that from the user object.

mrleblanc101 commented 1 month ago

I think we already discussed that in another issue, so my bad if it's a duplicate.

But double destructuring is so ugly 😭

I get that (front-end) session might not be the right name and could be confusing because it's not the same as the server session, I simply used that example because that what other modules like @sidebase/auth and Authjs-nuxt

itpropro commented 1 month ago

I know what you mean, if we find a better name than session, we could refactor that to have the user object (which could be potentially undefined then) and the oauth whatever name context object. It would be a breaking change and it has to be clear which data should actually be in the user object. What do you think about the claims?

mrleblanc101 commented 1 month ago

I think session is fine tbh. It's easy to understand that session on the client-side is related to the auth provider and not to cookies. But maybe metadata or tokens.

What do you think about the claims?

What about them ? I don't know much about claims as we don't use them. They should probably be able to be destructurer from useOidcAuth() composable too tho

itpropro commented 1 month ago

I think session is fine tbh. It's easy to understand that session on the client-side is related to the auth provider and not to cookies. But maybe metadata or tokens.

What do you think about the claims?

What about them ? I don't know much about claims as we don't use them. They should probably be able to be destructurer from useOidcAuth() composable too tho

Claims are currently at the root of the user object, as they are a part of the actual token, not the user information from the userInfo endpoint. With Entra ID for example, there is no userInfo endpoint, so the user object would be empty. Most user information would be either contained in the idToken, but more importantly, data about the user context would be handed over to the application to be able to user that for other services. We can also not forget one of the most common scenarios which would be to request a token for a specific service. If I have an API for example, the nuxt application as well as the API do have a client in the IdP. If I want to access the API from the Nuxt application, I would request a token with the resource hint containing the client id of the API, which results in the aud field on the token to be set to the API, otherwise the API should deny access. In this case, there is no user information at all, just the accessToken and some claims eventually.

I just want to avoid adjusting too much to the more common scenario in B2C cases, which is just simple login with an IdP and thereby making more complex but more common cases in B2B and enterprise scenarios more difficult.