Sign in Workflow with auth token or cookie redirect/payload
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
Here is a ref to the Auth0 docs:
- https://auth0.com/docs/get-started/authentication-and-authorization-flow/device-authorization-flow/mobile-device-login-flow-best-practices
OAuth2 has a similar flow:
- https://oauth.net/2/native-apps/
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).
THanks @zdmc23
would the refresh_token be able to happen in the background, so the user would not experience the redirects?
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...)
https://github.com/DiscipleTools/disciple-tools-theme/assets/191707/69c45247-6f23-4821-b08c-79d08b5fbfde
@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();
}
- @squigglybob , bc it looks like you have coded/refactored a decent amount of the login functionality
- @cairocoder01 , bc you were in the previous Slack thread
A couple of notes:
-
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), ordiscipletools://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_tosince it is a WP standard)
- For Dev, it looks like:
-
currently
generate_tokenrequires 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" -
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!
Some other related things to eventually discuss, but not urgent:
- the current access token is set to expire by default after 1 week. We could probably make that shorter now that we will have a refresh capability
- depending on default expiration, and anticipated user patterns, we could refresh the token every time the app loads (this would make sense if we keep the 1 wk exp), or it can be refreshed every so often in the background, or as much as every request (a bit excessive :-) )
- normally, there is a Bearer Token JWT which includes both Access Token and separate Refresh Token. It is fine how we have it, but if you ever wanted to match like OAuth2 standards, etc... then we could eventually do that.
{
"access_token" : "ey...onNtiw",
"token_type" : "Bearer",
"expires_in" : 3600,
"scope" : "openid email",
"refresh_token" : "ey...kk2VdY",
"id_token" : "ey...kpOurg"
}
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
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.
@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
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