Skip to content
This repository has been archived by the owner on Apr 10, 2023. It is now read-only.

feat: UI Auth/Login #41

Merged
merged 39 commits into from
Jan 20, 2023
Merged

feat: UI Auth/Login #41

merged 39 commits into from
Jan 20, 2023

Conversation

markphelps
Copy link
Contributor

@markphelps markphelps commented Jan 19, 2023

Adds OIDC login support to the UI 🎉

Fixes: FLI-70

Supported by: flipt-io/flipt#1279

DEMO: https://imgur.com/a/jUHFHJO

CleanShot 2023-01-19 at 16 12 11

Auth Checks

  • We query if we have a 'session' object stored in browser localstorage (flipt) via the new SessionProvider and useStorage / useSession hooks
  • If we do, then we are either logged in, or no login is required
  • If we don't, then we hit the /meta/info endpoint on the API. if the API returns an error (likely 401), then auth is required, so we'll redirect to the /login page
  • If no auth is required, then we'll get a response from /meta/info and store that in the session and continue on

Login

  • If auth is required (if we redirect the user to the login page), then we load the list of available OIDC providers (we only support OIDC for now, in the future this will need some refactoring to support other methods)
  • We render the buttons in the UI, checking for any known providers with prettier display names and icons
  • On click the buttons make the request to the authorizationURL configured in the Flipt Server
  • The user then performs the OIDC dance, on success it calls back to our API, passing along the token for us to store in our DB and then set the appropriate cookies in the browser

API Calls

  • All API calls now have the credentials: include option. This ❓ should we change to same-origin instead? it looks like this is actually the default
  • This will forward all cookies set on this domain to the backend API, which we then use to authenticate each request on the backend

User Profile

  • The Flipt backend API returns metadata provider by the Auth Provider, including name and optionally an image
  • We then render the profile image and name with a dropdown allowing the user to logout
  • TODO: test what happens if image is not available

Logout

  • feat: auth expire self / logout flipt#1279 added the ability to expire auth tokens and also clears the cookies mentioned above if the user requests /v1/auth/expire
  • When the user clicks logout, we issue the request to this API, resulting the auth token being expired in the DB as well as the browser cookies being cleared, then redirecting the user to /login again.

TODO

  • Test what happens if no name or image is available for the user after login
  • Determine if we should use same-origin instead of include for credential forwarding
  • Test with more providers

Future Improvements

  • I'd like to refactor the code that returns the login providers to be more flexible so that we can support other methods in the future (ie OAuth)

…into hooks-cleanup

* 'hooks-cleanup' of https://github.com/flipt-io/flipt-ui:
  chore(deps-dev): bump prettier-plugin-organize-imports (#28)
* main:
  fix(fetch): pass credentials: include (#6)
  chore: rename error hook; cleanup (#31)
  chore(deps-dev): bump prettier from 2.8.2 to 2.8.3 (#29)
  chore(deps-dev): bump @typescript-eslint/eslint-plugin (#34)
  chore(deps-dev): bump @typescript-eslint/parser from 5.48.1 to 5.48.2 (#35)
  chore(deps-dev): bump eslint from 8.31.0 to 8.32.0 (#30)
  chore(deps-dev): bump eslint-plugin-react from 7.32.0 to 7.32.1 (#33)
  chore(deps-dev): bump eslint-plugin-import from 2.27.4 to 2.27.5 (#32)
* main:
  feature: Validate distributions (#37)
* 'login' of https://github.com/flipt-io/flipt-ui:
  chore(deps): bump react-router-dom from 6.6.2 to 6.7.0 (#39)
  chore(deps-dev): bump @types/react from 18.0.26 to 18.0.27 (#40)
  chore(deps): bump swr from 2.0.0 to 2.0.1 (#38)
Copy link
Contributor

@darinmclain darinmclain left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes requested in the comments. Overall, looks good though!

src/components/SessionProvider.tsx Outdated Show resolved Hide resolved
src/components/SessionProvider.tsx Outdated Show resolved Hide resolved
}, [setError]);

useEffect(() => {
loadAvailableProviders();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like it would cause an endless loop of loading providers?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i checked with console.log it only calls once, I'm wondering if its because its wrapped in a useCallback?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be because you're passing the loadAvailableProviders function as an arg below?

useEffect docs suggest the second argument is compared between each render.
If it doesn't change then the first argument function is not called again.
Seems to me that function will never change. Or maybe there is some overloaded alternative behaviour when a function is passed as second arg.

Seems you could also pass [] for the same effect:
https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like more often than not you want state in your dependencies array passed to useEffect.
There are cases where you might pass a function. Though that seems to be when they're defined out of scope:
https://reacttraining.com/blog/when-to-use-functions-in-hooks-dependency-array/

(I admit as I kept reading this I started to glaze over and maybe missed something key)

@GeorgeMac
Copy link
Contributor

@markphelps I didn't realize it was sameorigin by default. Yeah, I think we can get away with that.
I think we mandate that the UI and the API are under the same domain for now and not support other deployment strategies yet.
We can change that down the line.

This does mean continued used of things like vite for local dev.

Comment on lines 21 to 25
'io.flipt.auth.oidc.email': string;
'io.flipt.auth.oidc.email_verified': string;
'io.flipt.auth.oidc.name': string;
'io.flipt.auth.oidc.picture'?: string;
'io.flipt.auth.oidc.provider': string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be good to explore this with different requested scopes.
I see picture is required. However, I think technically they all could be required.
Since you can configure Flipt with no scopes (apart from the oidc scope which is required) and we can authenticate them but obtain next to no information.

Which, I think, is good. It is a feature the operator can decide on.

Copy link
Contributor

@GeorgeMac GeorgeMac left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my very limited knowledge, this looks great.
I put a couple rambling thoughts in here.

@markphelps markphelps merged commit 86acc1c into main Jan 20, 2023
@markphelps markphelps deleted the login branch January 20, 2023 17:31
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants