-
Notifications
You must be signed in to change notification settings - Fork 4.4k
Skip session secret validation and add session expiry check in case of JWT authentication #8313
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Skip session secret validation and add session expiry check in case of JWT authentication #8313
Conversation
… JWT Signed-off-by: Souptik Datta <souptikdatta2001@gmail.com>
app/init.php
Outdated
App::setResource('clientSession', function (string $mode, Document $project, Document $cons 8000 ole, Appwrite\Utopia\Request $request) { | ||
Auth::setCookieName('a_session_' . $project->getId()); | ||
|
||
if (APP_MODE_ADMIN === $mode) { | ||
Auth::setCookieName('a_session_' . $console->getId()); | ||
} | ||
|
||
$session = Auth::decodeSession( | ||
$request->getCookie( | ||
Auth::$cookieName, // Get sessions | ||
$request->getCookie(Auth::$cookieName . '_legacy', '') | ||
) | ||
); | ||
|
||
// Get session from header for SSR clients | ||
if (empty($session['id']) && empty($session['secret'])) { | ||
$sessionHeader = $request->getHeader('x-appwrite-session', ''); | ||
|
||
if (!empty($sessionHeader)) { | ||
$session = Auth::decodeSession($sessionHeader); | ||
} | ||
} | ||
|
||
if (empty($session['id']) && empty($session['secret'])) { | ||
$fallback = $request->getHeader('x-fallback-cookies', ''); | ||
$fallback = \json_decode($fallback, true); | ||
$session = Auth::decodeSession(((isset($fallback[Auth::$cookieName])) ? $fallback[Auth::$cookieName] : '')); | ||
} | ||
|
||
return $session; | ||
}, ['mode', 'project', 'console', 'request']); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we avoid setting an additional resource considering we already have the user
resource?
Also, we have a session
resource below this.
app/controllers/api/account.php
Outdated
@@ -2386,6 +2387,7 @@ | |||
// 'iss' => 'http://api.mysite.com', | |||
'userId' => $user->getId(), | |||
'sessionId' => $current->getId(), | |||
'session' => json_encode($clientSession), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer to avoid adding this session
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we need to rely a little less on the session secret. We should validate once and then rely on the Auth::$unique
, instead. Relying on Auth::$secret
requires us to iterate over session and hash, which can be expensive.
Please also add a test case to verify calling get current session works with a JWT.
Signed-off-by: Souptik Datta <souptikdatta2001@gmail.com>
Signed-off-by: Souptik Datta <souptikdatta2001@gmail.com>
Signed-off-by: Souptik Datta <souptikdatta2001@gmail.com>
Thanks @stnguyen90! Got your point. 👍 It makes sense to use Therefore I have removed But the And now this How does this look now? I have also added the test case. 👍 |
Signed-off-by: Souptik Datta <souptikdatta2001@gmail.com>
Signed-off-by: Souptik Datta <souptikdatta2001@gmail.com>
if (empty($user->find('$id', $jwtSessionId, 'sessions'))) { // Match JWT to active token | ||
$session = $user->find('$id', $jwtSessionId, 'sessions'); | ||
|
||
if (empty($session) || Auth::isSessionExpired($session)) { // Match JWT to active token |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@stnguyen90 This missing time check was the cause for this #8000 issue. Therefore added this session expired check over here. That should be also fixed now, as per my tests. 🤞
Just FYI - For JWT we still don't have a secret check like we have in sessions, that's because we don't have that data in JWT. But I think that's the point of directly storing the userID, so that we don't have to depend on the session secret, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just FYI - For JWT we still don't have a secret check like we have in sessions, that's because we don't have that data in JWT. But I think that's the point of directly storing the userID, so that we don't have to depend on the session secret, right?
Yes. We just need to check the JWT is valid ($jwt->decode()
) and if it is, trust the value is valid.
])); | ||
|
||
$this->assertEquals(200, $response['headers']['status-code']); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@stnguyen90 I was thinking to add a test for checking the expiry time condition for JWT to validate the changes of #8000 I did above.
Should we add that? If yes, is there any recommended way here to change the default expiry time, or use some other session creation endpoint which allows setting the expiry time?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you make an API call to update the project auth settings and change the session expiry before creating the session?
Otherwise, you can try doing a manual test and add screenshot(s) as evidence.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@stnguyen90 Thanks for the idea of calling project auth API to change the expiry time!
Did that, and added the test to test if JWT properly respects session expiry time!
if (empty($user->find('$id', $jwtSessionId, 'sessions'))) { // Match JWT to active token | ||
$session = $user->find('$id', $jwtSessionId, 'sessions'); | ||
|
||
if (empty($session) || Auth::isSessionExpired($session)) { // Match JWT to active token |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just FYI - For JWT we still don't have a secret check like we have in sessions, that's because we don't have that data in JWT. But I think that's the point of directly storing the userID, so that we don't have to depend on the session secret, right?
Yes. We just need to check the JWT is valid ($jwt->decode()
) and if it is, trust the value is valid.
])); | ||
|
||
$this->assertEquals(200, $response['headers']['status-code']); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you make an API call to update the project auth settings and change the session expiry before creating the session?
Otherwise, you can try doing a manual test and add screenshot(s) as evidence.
/** | ||
* User session ID. | ||
* | ||
* @var string | ||
*/ | ||
public static $sessionId = ''; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oye..I didn't realize we don't have this yet...
@TorstenDittmann, is this okay so that we:
- prevent finding the session using the secret multiple times as it can be expensive to iterate over every session and verify the secret hash
- not rely on a non-existent session secret when a JWT is provided.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sounds good to me :-)
Signed-off-by: Souptik Datta <souptikdatta2001@gmail.com>
What does this PR do?
As per mentioned in the attached issue, when using JWT as an authentication method, if we don't pass the session in the "get session" endpoint, then the authentication fails.
But that shouldn't be the case ideally, as JWT is meant for authentication and it should be able to authenticate even without the need of session. Or more specifically it should include the session data in itself, which should be used for authentication.
Auth::$secret
value which should be equals to the$session['secret']
, where$session
is the decoded value of the client session string.Auth::$secret
value is not populated, and that's why later where we check if the hash ofAuth::$secret
value is equal to thesecret
of the matched session object, it is failing.Auth::$secret
value when using JWT. But that is only possible when we get the decoded value of the client session string as mentioned in the second point.accounts/jwt
handler function. But there is not resource currently present which provides that data.clientSession
inapp/init.php
and now consume that resource in the above mentioned handler function and pass the data to the JWT.Fixes #8174
Test Plan
We can test this in Postman -
http://localhost/v1/account/sessions/email
endpoint.x-appwrite-session
header.http://localhost/v1/account/jwt
.http://localhost/v1/account/sessions/current
, with the JWT set and also the session set.Related PRs and Issues
Checklist