DiscipleTools / disciple-tools-theme

Disciple.Tools is a coalition management system for disciple making movements.
https://disciple.tools
GNU General Public License v2.0
40 stars 62 forks source link

Sign in Workflow with auth token or cookie redirect/payload #2346

Open corsacca opened 8 months ago

zdmc23 commented 8 months ago

Here is an example of ChatGPT using Auth0 for a web browser based mobile login flow:

https://github.com/DiscipleTools/disciple-tools-theme/assets/191707/74a70def-5382-4f89-bd42-01c0720fdeb1

zdmc23 commented 8 months ago

Here is a ref to the Auth0 docs:

OAuth2 has a similar flow:

zdmc23 commented 8 months ago

For our purposes, since we also want to support a refresh_token, then we could probably just use the same endpoint (and grant_type=refresh_token):

https://developer.okta.com/docs/reference/api/oidc/#token

So, when that endpoint is accessed with that grant_type, then we check for the refresh token in the request. If it is present and valid, then issue new JWT (access and refresh tokens). If not (here is where the cookie exchange comes into play), we could validate the cookie, and assuming it is valid, then we also return a new JWT (access and refresh tokens), but this time via a more secure redirect that only the app will be handling. Then from that point forward, the mobile app will attempt to refresh the tokens according to the normal flow mentioned above (to keep the user logged in for as long as they actively use the app).

corsacca commented 8 months ago

THanks @zdmc23

would the refresh_token be able to happen in the background, so the user would not experience the redirects?

zdmc23 commented 8 months ago

THanks @zdmc23

would the refresh_token be able to happen in the background, so the user would not experience the redirects?

@corsacca Yes, with each API request, we could check the expiration date of the token, and just refresh it when it is getting close to expire. The user never notices anything different. Linking it to the usage is desirable, bc most often we do want the token to expire when they haven't been actively using the app/API (for security sake, so they haven't simply lost their phone or a hostile family member starts randomly checking apps, etc...)

zdmc23 commented 8 months ago

https://github.com/DiscipleTools/disciple-tools-theme/assets/191707/69c45247-6f23-4821-b08c-79d08b5fbfde

zdmc23 commented 8 months ago

@corsacca I think we can make this work without many changes. It relies on the redirect_to parameter at login:

https://example.com/wp-login.php?redirect_to=/wp-json/jwt-auth/v1/token

Upon successful login (whether standard username/password, MFA, SSO, whichever plugins are being used, so long as the standard WordPress redirect_to is being honored), it will redirect to the new endpoint which exchanges a valid session (ie, cookie) for a JWT access token.

Here is the code snippet to make it happen:

All edits would be in: https://github.com/DiscipleTools/disciple-tools-theme/blob/develop/dt-core/libraries/wp-api-jwt-auth/public/class-jwt-auth-public.php

        /**
         * Add the endpoints to the API
         */
        public function add_api_routes() {
                register_rest_route( $this->namespace, 'token', [
                        'methods'             => 'GET',
                        'callback'            => [ $this, 'exchange_cookie_for_jwt' ],
                        'permission_callback' => '__return_true',
                ] );

                register_rest_route( $this->namespace, 'token', [
                        'methods'             => 'POST',
                        'callback'            => [ $this, 'generate_token' ],
                        'permission_callback' => '__return_true',
                ] );

                register_rest_route( $this->namespace, 'token/refresh', [
                        'methods'             => 'POST',
                        'callback'            => [ $this, 'refresh_access_token' ],
                        'permission_callback' => '__return_true'
                    ]
                );

                register_rest_route( $this->namespace, 'token/validate', [
                        'methods'             => 'POST',
                        'callback'            => [ $this, 'validate_token' ],
                        'permission_callback' => '__return_true',
                ] );
        }

        public function refresh_access_token(WP_REST_Request $request) {
                if ( !$this->has_permission() ) {
                    return new WP_Error( __FUNCTION__, "You do not have permission for this", [ 'status' => 403 ] );
                }
                $this->validate_token( $request );
                $token = $this->generate_token_static( 'dummy-email', 'dummy-password' );

                //remove_filter( 'authenticate', [ $this, 'allow_programmatic_login' ], 10 );

                if ( $token ) {
                    return [
                        'login_method' => DT_Login_Methods::MOBILE,
                        'jwt' => $token,
                    ];
                }
        }

        public function exchange_cookie_for_jwt(WP_REST_Request $request) {
                //if ( !is_user_logged_in() ) {
                if ( !wp_validate_auth_cookie() ) {
                        wp_redirect( '/wp-login.php' );
                        exit();
                }
                $token = $this->generate_token_static( 'dummy-email', 'dummy-password' );
                wp_redirect( 'exp://127.0.0.1:8081/?token=' . $token );
                //wp_redirect( 'discipletools://example.com/?token=' . $token );
                //wp_redirect( 'dt://example.com/?token=' . $token );
                exit();
        }
zdmc23 commented 8 months ago

A couple of notes:

  1. the Redirect URI for the App probably needs to be configurable (not sure whether you have any preferences on where that belongs):

    • For Dev, it looks like: exp://127.0.0.1:8081.
    • For Prod, it will look like: dt://example.com/ (more obscure for security sake, but also greater chance of collision with other Apps since only 2 letter protocol), or discipletools://example.com (whichever you prefer that we use)
    • (or have the Prod protocol hardcoded and allow for a Dev override. I thought about having another URL query param for it, but I don't think we can be confident that 3rd party plugins forward on URL params the way that they should with redirect_to since it is a WP standard)
  2. currently generate_token requires email and password, but it probably shouldn't. That's not required for a JWT token. So we either need to change that, or update the code above to pull the User email and credentials and then pass them so the JWT token can be generated on "refresh"

  3. feel free to refactor the above however it should be to fit the D.T standard. In some sense it is pseudocode, bc I needed to comment out things like (bc it did not work to validate the cookie like I expected, etc...):

    if ( !wp_validate_auth_cookie() ) {
            wp_redirect( '/wp-login.php' );
            exit();
    }

Thx!

zdmc23 commented 8 months ago

Some other related things to eventually discuss, but not urgent:

{
    "access_token" : "ey...onNtiw",
    "token_type" : "Bearer",
    "expires_in" : 3600,
    "scope"      : "openid email",
    "refresh_token" : "ey...kk2VdY",
    "id_token" : "ey...kpOurg"
}
zdmc23 commented 8 months ago

https://github.com/DiscipleTools/disciple-tools-theme/assets/191707/1b79b700-d872-4f98-aa00-c65ffc4cdf51

^ sorry, I realized the prev video did not show the actual WP login form flow (bc of a prev login attempt caching the cookie into the ios jar), but here is an example that also includes MFA with TOTP. It works nicely

corsacca commented 2 weeks ago

Hey @zdmc23, I had a go at implementing the cookie exchange and refresh endpoints using your code above. See https://github.com/DiscipleTools/disciple-tools-theme/pull/2586 you can try the GET wp-json/jwt-auth/v1/token and POST wp-json/jwt-auth/v1/token/refresh

@kodinkat implemented some auto refresh strategy: https://github.com/DiscipleTools/disciple-tools-theme/pull/2548

I'm looking for what the mvp would be to get this working.

Signing in from the website will be helpful in any app or external server.

I need to double check how our SSO strategy could workflow works with this too.

zdmc23 commented 2 weeks ago

@corsacca Thx! What is the best way to download the theme for this particular commit hash? I can test it locally with my previous tester mobile app to try to confirm it

corsacca commented 2 weeks ago

Most efficient? Checkout the auth-redirect branch of the theme. otherwise direct download: https://github.com/DiscipleTools/disciple-tools-theme/archive/refs/heads/auth-redirect.zip