ਟੋਕਨ ਟ੍ਰੈਪ (token trap) ਇੱਕ ਲਾਈਵ ਸਾਈਟ 'ਤੇ ਉਦੋਂ ਵਾਪਰਿਆ ਜਦੋਂ ਇੱਕੋ ਯੂਜ਼ਰ ਦੁਆਰਾ ਖੋਲ੍ਹੇ ਗਏ ਪੰਜ ਟੈਬਸ ਨੇ ਇੱਕੋ ਮਿਲੀਸਕਿੰਡ ਵਿੱਚ ਇੱਕ ਐਕਸਪਾਇਰ ਹੋਏ JWT ਨੂੰ ਰਿਫਰੈਸ਼ ਕਰਨ ਦੀ ਕੋਸ਼ਿਸ਼ ਕੀਤੀ, ਜਿਸ ਨਾਲ ਬੈਕਐਂਡ 'ਤੇ ਡੁਪਲੀਕੇਟ ਰਿਫਰੈਸ਼ ਰਿਕੁਐਸਟਾਂ ਦੀ ਭਰਮਾਰ ਹੋ ਗਈ ਅਤੇ ਸੈਸ਼ਨ ਤੁਰੰਤ ਅਵੈਲਿਡ (invalid) ਹੋ ਗਿਆ। ਹਰ ਟੈਬ ਨੇ ਯੂਜ਼ਰ ਨੂੰ ਲੌਗ ਆਊਟ ਕਰ ਦਿੱਤਾ, ਜੋ ਇਹ ਸਾਬਤ ਕਰਦਾ ਹੈ ਕਿ ਟੋਕਨ ਰੀਨਿਊਅਲ ਲਈ ਸਿਰਫ਼ ਇੱਕ ਟੈਬ ਵਾਲਾ ਹੱਲ ਹੁਣ ਕਾਫ਼ੀ ਨਹੀਂ ਹੈ।

ਟੋਕਨ ਟ੍ਰੈਪ ਕਿਉਂ ਮਹੱਤਵਪੂਰਨ ਹੈ

ਆਧੁਨਿਕ ਸਿੰਗਲ-ਪੇਜ ਐਪਸ (single-page apps) ਥੋੜ੍ਹੇ ਸਮੇਂ ਲਈ ਚੱਲਣ ਵਾਲੇ access tokens ਅਤੇ ਲੰਬੇ ਸਮੇਂ ਲਈ ਚੱਲਣ ਵਾਲੇ refresh token ਦੀ ਵਰਤੋਂ ਕਰਦੇ ਹਨ। ਜਦੋਂ access token ਐਕਸਪਾਇਰ ਹੋ ਜਾਂਦਾ ਹੈ, ਤਾਂ ਕਲਾਇੰਟ ਇੱਕ ਰਿਫਰੈਸ਼ ਰਿਕੁਐਸਟ ਭੇਜਦਾ ਹੈ, ਇੱਕ ਨਵਾਂ ਟੋਕਨ ਜੋੜ ਪ੍ਰਾਪਤ ਕਰਦਾ ਹੈ, ਅਤੇ ਅਸਲ ਕਾਲ ਨੂੰ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰਦਾ ਹੈ। ਜ਼ਿਆਦਾਤਰ ਡਿਵੈਲਪਰ ਇਸ ਫਲੋਅ ਨੂੰ ਇਨ-ਮੈਮਰੀ ਫਲੈਗ (ਜਿਵੇਂ ਕਿ isRefreshing = true) ਜਾਂ ਰਿਕੁਐਸਟ ਕਿਊ (request queue) ਨਾਲ ਸੁਰੱਖਿਅਤ ਕਰਦੇ ਹਨ, ਅਤੇ ਇਸਦਾ ਟੈਸਟ ਇੱਕ ਸਿੰਗਲ ਟੈਬ ਵਿੱਚ ਕਰਦੇ ਹਨ। ਅਸਲ ਦੁਨੀਆ ਵਿੱਚ ਯੂਜ਼ਰ ਕਈ ਟੈਬ ਖੁੱਲ੍ਹੇ ਰੱਖਦੇ ਹਨ: ਇੱਕ ਸੈਟਿੰਗ ਪੇਜ, ਇੱਕ ਐਨਾਲਿਟਿਕਸ ਡੈਸ਼ਬੋਰਡ, ਅਤੇ ਕੁਝ ਡੇਟਾ ਵਿਊਜ਼। ਜਦੋਂ access token ਐਕਸਪਾਇਰ ਹੁੰਦਾ ਹੈ, ਹਰ ਟੈਬ ਸੁਤੰਤਰ ਰੂਪ ਵਿੱਚ 401 ਐਰਰ ਦਾ ਪਤਾ ਲਗਾਉਂਦਾ ਹੈ, ਹਰ ਇੱਕ ਰਿਫਰੈਸ਼ ਰਿਕੁਐਸਟ ਭੇਜਦਾ ਹੈ, ਅਤੇ ਬੈਕਐਂਡ—ਖਾਸ ਕਰਕੇ ਜਦੋਂ ਇਹ refresh-token rotation ਨੂੰ ਲਾਗੂ ਕਰਦਾ ਹੈ—ਦੂਜੀ ਰਿਕੁਐਸਟ ਨੂੰ ਰੀਪਲੇਅ (replay) ਵਜੋਂ ਮੰਨਦਾ ਹੈ ਅਤੇ ਪੂਰੇ ਸੈਸ਼ਨ ਨੂੰ ਰੱਦ ਕਰ ਦਿੰਦਾ ਹੈ।

JavaScript isolation ਸਮੱਸਿਆ ਪੈਦਾ ਕਰਦਾ ਹੈ

ਹਰ ਬ੍ਰਾਊਜ਼ਰ ਟੈਬ ਆਪਣਾ ਵੱਖਰਾ JavaScript context ਚਲਾਉਂਦਾ ਹੈ। ਵੇਰੀਏਬਲਜ਼, ਟਾਈਮਰਜ਼, ਅਤੇ ਇਨ-ਮੈਮਰੀ ਫਲੈਗਸ ਦੂਜੇ ਟੈਬਸ ਨੂੰ ਦਿਖਾਈ ਨਹੀਂ ਦਿੰਦੇ, ਭਾਵੇਂ ਉਹ ਇੱਕੋ ਆਰਜਿਨ ਸਾਂਝਾ ਕਰਦੇ ਹੋਣ। ਇੱਕ ਫਲੈਗ ਜੋ ਕਹਿੰਦਾ ਹੈ “ਰਿਫਰੈਸ਼ ਪਹਿਲਾਂ ਹੀ ਚੱਲ ਰਿਹਾ ਹੈ” ਸਿਰਫ਼ ਉਸੇ ਟੈਬ ਦੇ ਅੰਦਰ ਰਹਿੰਦਾ ਹੈ ਜਿਸਨੇ ਇਸਨੂੰ ਸੈੱਟ ਕੀਤਾ ਸੀ। ਦੂਜੇ ਟੈਬਾਂ ਕੋਲ ਇਹ ਜਾਣਨ ਦਾ ਕੋਈ ਤਰੀਕਾ ਨਹੀਂ ਹੈ ਕਿ ਟੋਕਨ ਕਿਤੇ ਹੋਰ ਰਿਫਰੈਸ਼ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ, ਇਸ ਲਈ ਉਹ ਸਾਰੇ ਆਪਣੀ ਵੱਖਰੀ ਨੈੱਟਵਰਕ ਕਾਲ ਸ਼ੁਰੂ ਕਰ ਦਿੰਦੇ ਹਨ। ਇਹ ਸਮੱਸਿਆ ਇੰਟਰਸੈਪਟਰ (interceptor) ਵਿੱਚ ਕੋਈ ਬੱਗ ਨਹੀਂ ਹੈ; ਇਹ ਕਲਾਇੰਟ-ਸਾਈਡ ਸਟੇਟ ਆਇਸੋਲੇਸ਼ਨ (client-side state isolation) ਦੀ ਇੱਕ ਮੂਲ ਸੀਮਾ ਹੈ।

Web Locks API ਮਦਦ ਲਈ ਆਉਂਦੀ ਹੈ

ਜਦੋਂ ਕੋਈ ਟੈਬ ਲੌਕ (lock) ਫੜ ਕੇ ਰੱਖਦਾ ਹੈ, ਤਾਂ ਕੋਈ ਵੀ ਦੂਜਾ ਟੈਬ ਜੋ ਉਸੇ ਲੌਕ ਦੀ ਮੰਗ ਕਰਦਾ ਹੈ, ਉਸਦੇ ਰਿਲੀਜ਼ ਹੋਣ ਤੱਕ ਉਡੀਕ ਕਰਨਾ ਪੈਂਦਾ ਹੈ।

ਟੋਕਨ ਰਿਫਰੈਸ਼ ਲਈ ਇਹ ਕਿਵੇਂ ਕੰਮ ਕਰਦਾ ਹੈ

  1. 401 ਦਾ ਪਤਾ ਲਗਾਓ – ਕੋਈ ਵੀ ਟੈਬ ਜਿਸਨੂੰ ਅਨਅਥੋਰਾਈਜ਼ਡ ਰਿਸਪਾਂਸ ਮਿਲਦਾ ਹੈ, ਉਹ navigator.locks.request('auth_token_refresh_lock', async lock => { … }) ਨੂੰ ਕਾਲ ਕਰਦਾ ਹੈ।
  2. ਲੌਕ ਪ੍ਰਾਪਤ ਕਰੋ – ਜੇਕਰ ਕੋਈ ਹੋਰ ਟੈਬ ਲੌਕ ਨਹੀਂ ਫੜੀ ਬੈਠਾ, ਤਾਂ ਮੌਜੂਦਾ ਟੈਬ ਅੱਗੇ ਵਧਦਾ ਹੈ; ਨਹੀਂ ਤਾਂ ਇਹ ਉਦੋਂ ਤੱਕ ਰੁਕ ਜਾਂਦਾ ਹੈ ਜਦੋਂ ਤੱਕ ਲੌਕ ਖਾਲੀ ਨਹੀਂ ਹੋ ਜਾਂਦੀ।
  3. ਇੱਕ ਵਾਰ ਰਿਫਰੈਸ਼ ਕਰੋ – ਲੌਕ-ਧਾਰਕ ਰਿਫਰੈਸ਼ ਰਿਕੁਐਸਟ ਭੇਜਦਾ ਹੈ, ਨਵਾਂ access token ਅਤੇ ਇੱਕ ਟਾਈਮਸਟੈਂਪ localStorage ਵਿੱਚ ਸਟੋਰ ਕਰਦਾ ਹੈ, ਅਤੇ ਫਿਰ ਕਾਲਬੈਕ (callback) ਖਤਮ ਹੋਣ 'ਤੇ ਲੌਕ ਨੂੰ ਆਪਣੇ ਆਪ ਰਿਲੀਜ਼ ਕਰ ਦਿੰਦਾ ਹੈ।
  4. ਡੁਪਲੀਕੇਟ ਕੰਮ ਨੂੰ ਛੱਡੋ – ਜਦੋਂ ਉਡੀਕ ਕਰ ਰਿਹਾ ਟੈਬ ਅੰਤ ਵਿੱਚ ਲੌਕ ਪ੍ਰਾਪਤ ਕਰ ਲੈਂਦਾ ਹੈ, ਤਾਂ ਇਹ localStorage ਤੋਂ ਟਾਈਮਸਟੈਂਪ ਪੜ੍ਹਦਾ ਹੈ। ਜੇਕਰ ਟੋਕਨ ਇੱਕ ਨਿਰਧਾਰਤ ਸਮੇਂ (ਜਿਵੇਂ ਕਿ ਪਿਛਲੇ ਕੁਝ ਸਕਿੰਟਾਂ) ਦੇ ਅੰਦਰ ਰਿਫਰੈਸ਼ ਕੀਤਾ ਗਿਆ ਸੀ, ਤਾਂ ਟੈਬ ਨੈੱਟਵਰਕ ਕਾਲ ਨੂੰ ਛੱਡ ਦਿੰਦਾ ਹੈ ਅਤੇ localStorage ਤੋਂ ਆਪਣੇ ਇਨ-ਮੈਮਰੀ ਟੋਕਨ ਨੂੰ ਅੱਪਡੇਟ ਕਰਦਾ ਹੈ।
  5. ਕ੍ਰੈਸ਼ਾਂ ਨੂੰ ਸੰਭਾਲੋ – ਜੇਕਰ ਲੌਕ ਫੜੀ ਹੋਣ ਦੌਰਾਨ ਕੋਈ ਟੈਬ ਕ੍ਰੈਸ਼ ਹੋ ਜਾਂਦਾ ਹੈ ਜਾਂ ਬੰਦ ਹੋ ਜਾਂਦਾ ਹੈ, ਤਾਂ ਬ੍ਰਾਊਜ਼ਰ ਲੌਕ ਨੂੰ ਰਿਲੀਜ਼ ਕਰ ਦਿੰਦਾ ਹੈ, ਜਿਸ ਨਾਲ ਦੂਜਾ ਟੈਬ ਰਿਫਰੈਸ਼ ਨੂੰ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰ ਸਕਦਾ ਹੈ।

ਇੱਕ ਨਜ਼ਰ ਵਿੱਚ ਫਾਇਦੇ

  • ਜ਼ੀਰੋ ਵਾਧੂ ਨੈੱਟਵਰਕ ਕਾਲਾਂ – ਸਿਰਫ਼ ਪਹਿਲਾ ਟੈਬ ਹੀ ਬੈਕਐਂਡ ਨਾਲ ਗੱਲ ਕਰਦਾ ਹੈ।
  • ਸੈਸ਼ਨ ਦੀ ਤਬਾਹੀ ਨਹੀਂ – Refresh-token rotation ਇੱਕ ਵਾਰ ਵਰਤੋਂ ਦੇਖਦਾ ਹੈ, ਜਿਸ ਨਾਲ ਸੈਸ਼ਨ ਜਿਉਂਦਾ ਰਹਿੰਦਾ ਹੈ।
  • ਗ੍ਰੇਸਫੁੱਲ ਰਿਕਵਰੀ – ਬ੍ਰਾਊਜ਼ਰ-ਪ੍ਰਬੰਧਿਤ ਲੌਕ ਰਿਲੀਜ਼ ਟੈਬ ਦੇ ਗਾਇਬ ਹੋਣ 'ਤੇ ਡੈੱਡਲੌਕ (deadlocks) ਨੂੰ ਰੋਕਦਾ ਹੈ।

ਇੰਪਲੀਮੈਂਟੇਸ਼ਨ ਚੈੱਕਲਿਸਟ

  • ਆਪਣੀ Axios (ਜਾਂ fetch) ਇੰਟਰਸੈਪਟਰ ਵਿੱਚ ਲੌਕ ਰਿਕੁਐਸਟ ਦੇ ਨਾਲ ਰਿਫਰੈਸ਼ ਲੌਜਿਕ ਨੂੰ ਲਪੇਟੋ (wrap)।
  • ਰਿਫਰੈਸ਼ ਕੀਤੇ ਗਏ ਟੋਕਨ ਅਤੇ ਮਿਲੀਸਕਿੰਡ ਟਾਈਮਸਟੈਂਪ ਨੂੰ localStorage (ਜਾਂ sessionStorage ਜੇਕਰ ਤੁਸੀਂ ਪ੍ਰਤੀ-ਸੈਸ਼ਨ ਡੇਟਾ ਪਸੰਦ ਕਰਦੇ ਹੋ) ਵਿੱਚ ਸਟੋਰ ਕਰੋ।
  • ਜਦੋਂ ਲੌਕ ਦਿੱਤਾ ਜਾਂਦਾ ਹੈ, ਤਾਂ ਸਟੋਰ ਕੀਤੇ ਟਾਈਮਸਟੈਂਪ ਦੀ ਤੁਲਨਾ Date.now() ਨਾਲ ਕਰੋ। ਜੇਕਰ ਅੰਤਰ ਤੁਹਾਡੀ ਸੀਮਾ (threshold) ਤੋਂ ਘੱਟ ਹੈ, ਤਾਂ ਬੈਕਐਂਡ ਨੂੰ ਕਾਲ ਕਰਨ ਦੀ