Question: Authentication flow
See original GitHub issueI’m trying to implement user authentication with following requirements:
- Sign-in can be triggered by user action
- Auth token must be refreshed after some delay upon successful sign-in
- If there is token stored in localStorage on app start then we must refresh it immediately
Here’s my attempt:
function* authentication() {
let refreshDelay = null;
let token = JSON.parse(localStorage.getItem('authToken'));
if (token)
refreshDelay = call(delay, 0); // instant refresh
while (true) {
const {action} = yield race({
action: take([SIGN_IN, SIGN_OUT]),
delay: refreshDelay || call(waitForever)
});
refreshDelay = null;
if (action && action.type === SIGN_OUT) {
localStorage.removeItem('authToken');
continue;
}
try {
token = yield action == null ? auth.refreshToken(token) : auth.signIn();
localStorage.setItem('authToken', JSON.stringify(token));
yield put(authSuccess(token));
refreshDelay = call(delay, token.expires_in);
} catch (e) {
localStorage.removeItem('authToken');
yield put(authFailure(e));
}
}
}
This code works well, but I wonder if there is more elegant solution for handling refresh. Currently it’s too difficult to track refreshDelay
effect. I thought about extracting refresh to separate saga that waits for AUTH_SUCCESS
action and then sets up a delay, but in this case I won’t be able to cancel scheduled refresh if e.g. user signs out.
Issue Analytics
- State:
- Created 8 years ago
- Comments:46 (20 by maintainers)
Top Results From Across the Web
Security questions authentication method - Azure
Learn about using security questions in Azure Active Directory to help improve and secure sign-in events.
Read more >User pool authentication flow - Amazon Cognito
To verify the identity of users, modern authentication flows incorporate new challenge ... It can use custom challenges such as CAPTCHA or secret...
Read more >7 Must-Ask Questions When Selecting An Authentication ...
Are you in the process of reviewing your authentication procedure for your customers? Struggling to determine the most effective solution for your needs?...
Read more >What is challenge-response authentication?
Static challenges are requests that can be satisfied using the same answer or process every time. A static challenge includes the password recovery...
Read more >Best Practices for Choosing Good Security Questions
Security questions can add an extra layer of certainty to your authentication process. Security questions are an alternative way of identifying your consumers ......
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
First, I dont recommend calling service directly within sagas, it’d be better to use declarative calls. It makes possible testing all the operational logic inside the generator as explained in the declarative effects section
So I’ll first refactor localStorage calls into some isolated service
Another remark is regarding action watching flow. It’d be easier if you exploit the Structured programming benefits offered by generators. I’ll illustrate with a simple example, suppose our flow is just this simple sequence
Instead of doing something like
You can exploit the fact that SIGN_IN and SIGN_OUT fire always in sequence and never concurrently and offload the flow control burden (where are we in the program right now ?) to the underlying generator runtime (I simplify to illustrate the concept, I ll introduce concurrency next)
Before introducing concurrency, let’s first introduce the refresh cycles, when the token expires w’ll send again a request to the server to get a new token. so our sequence will become now
REFRESH* means many refreshes i.e. a loop in the generator
We’re still forgetting concurrency to keep things simple and progress step by step
But we’ve an issue there, the refresh loop executes forever, because there is no breaking condition. If the user signed out between 2 refreshes we’ve to break the loop. So the breaking condition is the SIGN_OUT action. Also the SIGN_OUT action is concurrent to the next expiration delay, So we have to introduce a
race
between the 2 eventsBut there are 2 othe issues, first the
authorize
saga may fail if the remote server responded with an error (e.g. invalid credentials, network error …). And second, there is another concurrency issue, what if the user signed out in the middle of a refresh request/response cycle ? We’d have to cancel the ongoing authorization operation.So first, we’ve to refactor our
authorize
sagaNow if a remote authorization fails, we signout the user and return null as token. So we’ve also to refactor our main Saga to take into account the failure (null return value)
Now, we can think of the left requirement. What if there is a token already in local storage ? We’ll simply skip the
take(SIGN_IN)
step and refresh immerdiatelySo IMO we should follow as much as we can the following
if
test makes only sens if we’re waiting for concurrent effects (usingrace
) or conditional results (success or error)SIGN_IN
thenSIGN_OUT
) then write 2 consecutive takes (take(SIGN_IN)
thentake(SIGN_OUT)
). Userace
only if there are concurrent events.As I said, the main benefit of using Generators is that it allows to leverage the power of Structured Programming and routine/subroutine approach. do you think that humans could write such complex programs using only goto jumps ?
@yelouafi what an answer 😄 this could make a good blog (medium) post I think to show the strength of Sagas