Is accepting the current and the previous one-time password a bad practice?

If by previous you mean an OTP that predates one the user already authenticated with then yes, this is considered bad practice. The OTP is meant to be used once, hence the name. The whole point of 2FA is that it represents the "something you have" in the security trinity. Allowing a token to be used twice defeats the purpose of what OTPs are meant to prevent. Turning it into "something you know" which makes it just a second password.

This problem usually doesn't present itself as a brute force issue. For example in the case of TOTP where the time windows are relatively short, brute forcing isn't really used to circumvent it. The use case as an attacker for a site that allows OTPs to be reused, either current or past is interception.

If the client uses the OTP and it is somehow intercepted, the attacker now reduces the problem to username password. Which, if they're intercepting information is likely also listening for these.

In the case of HOTP the problem is even worse, because theoretically the counter or moving factor does not increment very often. Meaning the last or even previous OTP the user authenticated with will likely be valid for a long time. In some cases months depending on the implementation of the server.

If by previous you mean the OTP on or before the current one being generated but not yet used, then no, within limits. Standard implementations of OTPs apply a "window" to overcome sync issues usually. In the case of TOTP, a 5 second window is employed (both forwards and backward) but this, in any implementations that I have seen, always disallows any OTP that is on or before the last one the user authenticated with. If we take the case of a 15s moving step with a 15s window there would be three OTPs that can be used to authenticate over a 45s timeframe, one in the past, one now and one in the future (this assumes the interval is the same as the moving step). The reasoning behind this is elegantly explained in @cornelinux's answer.

For HOTP the window is a "look ahead" window. The last OTP the user authenticated with was allowed if it was within the next 10 OTPs of the previous one accepted, and then the implementation calculates the next 10 from that one for the check against the next OTP entered. It has the considerable good sense to disregard any of the OTPs not used in the window before the one used successfully.

Note:

In HOTP 10 is arbitrary. Usually the factor is about 2.5 times the number of OTPs required to authenticate successfully. So if the user needs 3 HOTPs to authenticate the window will be about 8 OTPs after the current one.

In TOTP 5 seconds is also variable. In some cases I've seen windows as large as 100% of the moving step. So with a 30s step and a window of 30s effectively makes a code valid for 1:30. Again, expanding both before and after the current iteration.


You may also want to take a look at RFC6238 (TOTP). https://tools.ietf.org/html/rfc6238#page-6

There is a timewindow for verifying the OTP value. (As @Nalaurien pointed out, only once). Allowing an older or even newer TOTP value has also practical reasons: clock skew of a hardware token and the time the user needs to read and enter the OTP value.

Clock Skew and Hardware

Note that TOTP was specified in 2011. HOTP, which is the basis for TOTP, was even specified in 2005. The iphone 1 was released in 2007. TOTP was never ment to run on a smartphone but on cheap hardware key fobs with a bad clock with a probably drifting time.

You may also notice that HOTP (RFC4226) and TOTP (RFC623) where written by people working at authentication hardware companies and not with Google or Apple. ;-)