Gathering detailed insights and metrics for flowstate
Gathering detailed insights and metrics for flowstate
Gathering detailed insights and metrics for flowstate
Gathering detailed insights and metrics for flowstate
npm install flowstate
Typescript
Module System
Node Version
NPM Version
97.1
Supply Chain
99.1
Quality
75.7
Maintenance
100
Vulnerability
100
License
JavaScript (99.75%)
Makefile (0.25%)
Total Downloads
5,576,547
Last Day
7,078
Last Week
29,576
Last Month
120,616
Last Year
1,381,071
6 Stars
1,120 Commits
11 Forks
4 Watching
13 Branches
3 Contributors
Minified
Minified + Gzipped
Latest Version
0.6.0
Package Id
flowstate@0.6.0
Unpacked Size
45.05 kB
Size
12.88 kB
File Count
19
NPM Version
8.1.2
Node Version
16.13.2
Publised On
18 Oct 2023
Cumulative downloads
Total Downloads
Last day
15.9%
7,078
Compared to previous day
Last week
-4%
29,576
Compared to previous week
Last month
8.3%
120,616
Compared to previous month
Last year
-2.2%
1,381,071
Compared to previous year
5
6
This middleware manages and propagates per-request state across HTTP requests to a web application. This allows for implementing flows which are sequences of requests and responses that, taken together, culminate in a desired outcome.
By default, this state is kept in the session. The session itself stores state
by setting a cookie which applies to all requests to an application. This
middleware isolates that state so it can be applied to an individual sequence of
requests. To do this, state is propagated in return_to
and state
parameters
across requests. This middleware does this automatically whenever possible,
such as when redirecting. When not possible, such as when rendering a view,
locals and helpers are made available to the view so that return_to
and
state
parameters can be added to links and forms.
This middleware emerged from the state management functionality implemented by authentication-related packages, in particular passport-oauth2 and oauth2orize which implement OAuth 2.0. With this package, the functionality is made generic so that it can be applied to any HTTP endpoint.
1$ npm install flowstate
Add state middleware to your application or route:
1var flowstate = require('flowstate'); 2 3app.get('/login', flowstate(), function(req, res, next) { 4 // ... 5}); 6
The middleware will attempt to load any state intended for the endpoint, based
the state
parameter in the query or body of the request. If state is loaded,
it will be set at req.state
so that the handler can process it. The value set
at req.state
is referred to as the "current state".
If state is not loaded, an "uninitialized" state will be set at req.state
. A
state is uninitialized when it is new but not modified. If the request contains
a return_to
and optional state
parameter, those will be captured by the
uninitialized state as the location to return the user to when the current state
has been completely processed.
When a response is sent, any modifications to the current state will be saved
if the state is not complete. If the state is complete, any persisted state
will be removed. Note that an uninitialized state will never be saved since it
is not modified. However, the location to return the user to will be preserved
by propagating the return_to
and optional state
parameters on subsequent
requests.
1app.get('/login', flowstate(), function(req, res, next) { 2 var msgs = req.state.messages || []; 3 res.locals.messages = msgs; 4 res.locals.hasMessages = !! msgs.length; 5 res.render('login'); 6}); 7
When a response is sent by rendering a view, if there is state associated with
the request, res.locals.state
will be set to the current state's handle.
Otherwise the return_to
and state
parameters, if any, will be propagated by
setting res.locals.returnTo
and res.locals.state
. The view is expected to
decorate links with these properties and add them as hidden input to forms, in
order to propagate state to subsequent requests.
For example, if the above /login
endpoint is requested with a return_to
parameter:
1GET /login?return_to=%2Fdashboard HTTP/1.1
Then res.locals.returnTo
will be set to /dashboard
, making it available to
the view.
If the /login
endpoint is requested with both a return_to
and state
parameter:
1GET /login?return_to=%2Fauthorize%2Fcontinue&state=xyz HTTP/1.1
Then res.locals.returnTo
will be set to /authorize/continue
and res.locals.state
will be set to xyz
, making them available to the view.
If the /login
endpoint is requested with:
1GET /login?state=Zwu8y84x HTTP/1.1
Assuming the state was valid and intended for /login
, res.locals.state
will
be set to Zwu8y84x
and made available to the view. res.locals.returnTo
will
not be set.
1app.post('/login', flowstate(), authenticate(), function(req, res, next) { 2 if (mfaRequired(req.user)) { 3 return res.redirect('/stepup'); 4 } 5 // ... 6}, function(err, req, res, next) { 7 if (err.status !== 401) { return next(err); } 8 req.state.messages = req.state.messages || []; 9 req.state.messages.push('Invalid username or password.'); 10 req.state.failureCount = req.state.failureCount ? req.state.failureCount + 1 : 1; 11 req.state.complete(false); 12 res.redirect('/login'); 13}); 14
When a response redirects the browser, if the current state is complete, any
return_to
and state
parameters will be propagated by decorating the target
URL. If the current state is not complete, modifications will be saved and the
redirect will be decorated with the current state's handle.
For example, if the above /login
endpoint is requested with a return_to
and
state
parameter:
1POST /login HTTP/1.1
2Host: www.example.com
3Content-Type: application/x-www-form-urlencoded
4
5username=alice&password=letmein&return_to=%2Fauthorize%2Fcontinue&state=xyz
Then the user will be redirected to /stepup?return_to=%2Fauthorize%2Fcontinue&state=xyz
,
assuming the password is valid and MFA is required.
If the password is not valid, an uninitialized state is set at req.state
that
captures the return_to
and state
parameters. It is then saved and the user
is redirected to /login?state=Zwu8y84x
(where 'Zwu8y84x'
is the handle of
the newly saved state). The state data stored in the session is as follows:
1{ 2 "state": { 3 "Zwu8y84x": { 4 "location": "https://www.example.com/login", 5 "messages": [ "Invalid username or password." ], 6 "failureCount": 1, 7 "returnTo": "/authorize/continue", 8 "state": "xyz" 9 } 10 } 11}
This redirect will cause the browser to request the GET /login
route above.
Since the request is made with a state=Zwu8y84x
query parameter, the route will
load the state and make the handle (as well as messages) available to the view.
The view must add the handle to the login form as a hidden input field named
state
. When submitted, the browser will then make a request with that state
parameter:
1POST /login HTTP/1.1
2Host: www.example.com
3Content-Type: application/x-www-form-urlencoded
4
5username=alice&password=letmeinnow&state=Zwu8y84x
This time, the POST /login
route will load the state. If the password is
valid and MFA is required, the user will be will be redirected to
/stepup?return_to=%2Fauthorize%2Fcontinue&state=xyz
, as before. This is
because the original return_to
and state
parameters were captured by the
loaded state object, and are propagated by decorating the redirect location.
If another invalid password is submitted, the cycle of redirecting, rendering
the login view, and prompting the user for a password will repeat, with the
failureCount
incremented and saved each time.
1app.post('/login', flowstate(), authenticate(), function(req, res, next) { 2 if (mfaRequired(req.user)) { 3 return res.redirect('/stepup'); 4 } 5 res.resumeState(next); 6}, function(req, res, next) { 7 res.redirect('/'); 8}, function(err, req, res, next) { 9 // ... 10}); 11
When a user has completed a given flow, they should be returned to the location
they were navigating prior to entering the flow. This is accomplished by
calling resumeState()
, a function added to the response by this middleware.
If a current state was loaded, resumeState()
will return the user to the
captured return_to
and state
parameters, if any. Otherwise, it will return
the user to the return_to
and state
parameters carried by the request. If
neither of these exist, resumeState()
will call a callback, which will
typically be next
to invoke the next middleware. This middleware can then
redirect the user to a default location.
For example, when POST /login
is requested with a state
parameter:
1POST /login HTTP/1.1
2Host: www.example.com
3Content-Type: application/x-www-form-urlencoded
4
5username=alice&password=letmeinnow&state=Zwu8y84x
Then the user will be redirected to /authorize/continue&state=xyz
,
assuming the password is valid and MFA is not required.
If the /login
endpoint is requested with a return_to
parameter:
1POST /login HTTP/1.1
2Host: www.example.com
3Content-Type: application/x-www-form-urlencoded
4
5username=alice&password=letmein&return_to=%2Fdashboard
Then the user will be redirected to /dashboard
, after logging in.
If the /login
endpoint is requested without any state-related parameters:
1POST /login HTTP/1.1
2Host: www.example.com
3Content-Type: application/x-www-form-urlencoded
4
5username=alice&password=letmein
Then the user will be redirected to /
by the next middleware in the stack.
Copyright (c) 2016-2023 Jared Hanson
No vulnerabilities found.
No security vulnerabilities found.