Add support for magic codes
Magic links are great, but sometimes magic codes are useful (e.g. on a computer that does not have access to the user's email). Any chance of this being included in Ash Auth?
yeah i've bumped into this. i build tools for students and they are not logged in to their email on the school computer, but are on the phone. code (on top of link) would be great.
So basically a device flow?
@jimsynz what do you mean by device flow?
Also, I piggybacked on magic link strategy code and got it working. I'm glad to open a PR and work to clean the code up, if the idea is the same as the magic link, but send a code instead of a link.
Magic codes would be a great addition as they are imho a better solution that links at a cost of a little bit more friction (arguably same now or even a bit lower as links if you require interaction) since the user has to copy paste the code.
As seen by this cve sometimes antivirus products and email scanners will prefetch the URL, which then effectively ‘clicks’ the link and can accidentally expire it.
Both email OTP and magic links are meant to expire once used, but only magic links can expire before they even get to the user. This would frustrate anyone.
Additionally links can be hijacked and swapped out in transit by phishing programs, meaning users might not be clicking an access link, but a URL designed to trick them.
Not to mention that auth flow with magic links always start in one tab and finish in another which I always find messy and not a good UX.
@tlvenn that particular vulnerability is now mitigated via requiring an interaction, but that doesn't mean we shouldn't support magic codes at some point 😄
yes sorry @zachdaniel, did not mean to imply it wasn't but one advantage of links over codes kinda disappear in that required user interaction, as the UX is not as seamless anymore. Arguably can even be a bit confusing from the user point of view as they simply don't understand why this step is necessary.
@ggarciajr would be great if you have time to open a PR.
@tlvenn just want to ask for direction before I open the PR. My current implementation stores codes in a new "magic_codes" table which holds a reference to the current tokens table used by the magic link. During the request phase it creates a jwt and stores it in the tokens table, and it stores the code in the new table. Once the user successfully validate the cod, then the old jwt token is revoked, and a new session jwt is created (just like the magic link strategy does). It also deletes the magic code from the codes table.
My question is: Is this the best way to go about it, or should, the magic code strategy, only work with magic code and only create a jwt token upon successfully sign in attempts? In this case, it would not create the first jwt during the request phase.
Probably not the best person to ask, @zachdaniel is the one you contributed most of the magic link logic if i am not mistaken. One thing to keep in mind is we probably need to rate limit the action that validate the code to prevent an attacker to try to brute force the 4 / 6 digits code.
Ideally leveraging Hammer to do this, would be kinda neat to have an ash hammer extension that let us declaratively rate limit any action.
The rate limit need / Hammer extension piggyback on the same need with totp / 2FA from this issue https://github.com/team-alembic/ash_authentication/issues/503
@ggarciajr it sounds right to me but there is one other thing that should happen which is that in the browser session it should have the jwt, and you'd have to submit both the jwt and the code, which would be done transparently. This locks the flow to that one browser session instead of any browser session.
Would also run this by many others so don't necessarily take my word as law on the right way to do this.
@zachdaniel thanks for the guidance. Do you have anyone in mind that we should run this idea by?
@jimsynz @maennchen would be the main two 😄
“Magic Code” is effectively a TOTP (time-based one-time password) system where the secret isn’t shared with the user — instead, a code is emailed and the validity window is extended. This makes it easier to use but significantly weaker in security terms: it’s essentially TOTP without a password and with relaxed timing. It can work for very low-sensitivity cases, but it’s not a strong login method.
For scenarios where a user can’t easily log in, for example, on a TV, game console, embedded display, or a shared school computer where they’re not logged into their private email, the correct standardized approach is OAuth Device Authorization Flow.
See: Auth0 – Device Authorization Flow
How it works and why it fits these use cases:
-
Device A (limited-input device) requests authorization and receives:
- a
device_code - a
user_code - a
verification_uri
- a
-
Device A shows the user code and verification URL (e.g., “Go to https://example.com/activate and enter 123-456”).
-
Device A starts polling the server to see if the user has approved access.
-
The user, on Device B (e.g., their phone or laptop), visits the URL, is either already logged in or logs in through the normal secure flow, and enters the device code.
-
Once the login is approved, Device A’s polling request succeeds and receives a valid access token.
Why this is better:
- The user logs in through your standard, fully secure authentication flow (password, MFA, etc.).
- The limited device never handles sensitive credentials.
- The process works even when the user can’t access email on that device.
- The authorization server strictly controls expiry, rate limits, and validity of codes.
In our specific case, the endpoint doing the login on Device A is the same as the Auth provider. So we should be able to implement something similar without the user code / polling and instead can just use a live view / pub-sub, which should make the implementation simpler. If we also want to support API use cases, a full implementation of the OAuth Device Flow would be beneficial.
I would argue the device auth flow is for a completely different use case as you presented @maennchen and while it shares some similarity with a magic code in the way it prompts the user for a code to log in, it solves a very different problem, it's an hybrid form of 2FA that you complete on another device.
More and more orgs are adopting password less login and there is a definitive shift toward codes rather than links for the reasons explain above.
Device / browser fingerprinting is important with password less flows and leveraging the jwt like @zachdaniel mentioned to lock the flow to that one browser session which started it is a very good point.
@maennchen @jimsynz I can start working to implement the magic code strategy, and here is the essential points for the strategy:
- Code length should be configurable via dsl
- Extend the Token resource by adding the magic_code attribute - I thought about defining a new resource by I'm starting to think it will be more complex than it is worth.
- Generate a jwt token, with the magic code, similar to what the magic link strategy does
- Store the jwt in the session as a mechanism to lock the auth flow to the device that requested the code.
Is this a good starting point? Is there anything I'm missing?