[{"data":1,"prerenderedAt":121},["ShallowReactive",2],{"article-\u002Fwriting\u002Fdruxt-auth-050-two-ways-to-sign-in-20260926":3},{"id":4,"title":5,"articleType":6,"categories":7,"date":11,"description":12,"extension":13,"meta":14,"paragraphs":15,"path":116,"readingTime":117,"sitemap":118,"stem":119,"__hash__":120},"articleEntries\u002Farticles-data\u002Fdruxt-auth-050-two-ways-to-sign-in-20260926.json","Druxt Auth 0.5.0; two ways to sign in without leaving your site","Blog post",[8,9,10],"Drupal","Druxt","Planet Drupal","2026-09-26T21:30:00+10:00","Simple OAuth 6 removed the password grant. Druxt Auth 0.5.0 gets it working again through a contrib module, and the authorization code grant now takes credentials too, so a Nuxt frontend signs people in without sending them to Drupal.","json",{},[16,33,57,73,89,108],{"type":17,"layout":18,"regions":19},"section","layout_onecol",{"content":20},[21,24,26],{"type":22,"html":23},"text_formatted","\u003Cp>Every Druxt site I have built signs people in by sending them somewhere else to do it: out to Drupal's login page, on to a consent screen, and eventually back. With Druxt Auth \u003Ccode>0.5.0\u003C\u002Fcode> the username and password can now be handled entirely in the frontend instead, either through the authorization code grant or through the password grant.\u003C\u002Fp>",{"type":22,"html":25},"\u003Cp>If you have not used it, Druxt Auth wires Nuxt's auth module to \u003Ca href=\"https:\u002F\u002Fwww.drupal.org\u002Fproject\u002Fsimple_oauth\">Simple OAuth\u003C\u002Fa> on the Drupal side. It targets Nuxt 2 today, because \u003Ccode>@nuxtjs\u002Fauth-next\u003C\u002Fcode> does.\u003C\u002Fp>",{"type":27,"src":28,"width":29,"height":30,"alt":31,"caption":32},"media","\u002Fimages\u002Fwriting\u002Fdruxt-auth-sign-in-dialog.png",2560,1440,"The druxtjs.org playground rendering an Umami recipe block, with a Sign in to edit dialog open over it","\u003Cp>Signing in happens on the page you were already on.\u003C\u002Fp>",{"type":17,"layout":18,"regions":34,"title":56},{"content":35},[36,38,44,46,48,52,54],{"type":22,"html":37},"\u003Cp>The first is the \u003Cstrong>authorization code grant\u003C\u002Fstrong>, which now takes credentials directly. Druxt Auth signs the visitor in through Drupal's JSON login route first, so the authorize step finds a session waiting and returns a code without rendering anything:\u003C\u002Fp>",{"type":39,"title":40,"language":41,"code":42,"highlighted":43},"code","Authorization code, handed credentials","js","\u002F\u002F Nuxt 2\nawait $auth.loginWith('drupal-authorization_code', {\n  credentials: { name, pass },\n})","\u003Cspan class=\"token comment\">\u002F\u002F Nuxt 2\u003C\u002Fspan>\n\u003Cspan class=\"token keyword\">await\u003C\u002Fspan> $auth\u003Cspan class=\"token punctuation\">.\u003C\u002Fspan>\u003Cspan class=\"token function\">loginWith\u003C\u002Fspan>\u003Cspan class=\"token punctuation\">(\u003C\u002Fspan>\u003Cspan class=\"token string\">'drupal-authorization_code'\u003C\u002Fspan>\u003Cspan class=\"token punctuation\">,\u003C\u002Fspan> \u003Cspan class=\"token punctuation\">{\u003C\u002Fspan>\n  \u003Cspan class=\"token literal-property property\">credentials\u003C\u002Fspan>\u003Cspan class=\"token operator\">:\u003C\u002Fspan> \u003Cspan class=\"token punctuation\">{\u003C\u002Fspan> name\u003Cspan class=\"token punctuation\">,\u003C\u002Fspan> pass \u003Cspan class=\"token punctuation\">}\u003C\u002Fspan>\u003Cspan class=\"token punctuation\">,\u003C\u002Fspan>\n\u003Cspan class=\"token punctuation\">}\u003C\u002Fspan>\u003Cspan class=\"token punctuation\">)\u003C\u002Fspan>",{"type":22,"html":45},"\u003Cul>\u003Cli>No client secret in the app, because underneath it is still authorization code with PKCE.\u003C\u002Fli>\u003Cli>A real Drupal session, which your editors need if they work in Drupal's admin pages through the frontend. \u003Ccode>logout()\u003C\u002Fcode> ends it.\u003C\u002Fli>\u003Cli>Drupal and the frontend have to look like one site to the browser, so it needs the proxy.\u003C\u002Fli>\u003C\u002Ful>",{"type":22,"html":47},"\u003Cp>The second is the \u003Cstrong>password grant\u003C\u002Fstrong>. I shipped it in \u003Ccode>0.4.0\u003C\u002Fcode>, when Simple OAuth 5 still had the grant. The 6.x branch never did, so upgrading took it away. Thankfully \u003Ccode>0.5.0\u003C\u002Fcode> and \u003Ca href=\"https:\u002F\u002Fwww.drupal.org\u002Fproject\u002Fsimple_oauth_password_grant\">Simple OAuth Password Grant\u003C\u002Fa> get it running again:\u003C\u002Fp>",{"type":39,"title":49,"language":41,"code":50,"highlighted":51},"Password grant, through the server route","\u002F\u002F Nuxt 2\nawait $auth.loginWith('drupal-password', {\n  data: { username, password },\n})","\u003Cspan class=\"token comment\">\u002F\u002F Nuxt 2\u003C\u002Fspan>\n\u003Cspan class=\"token keyword\">await\u003C\u002Fspan> $auth\u003Cspan class=\"token punctuation\">.\u003C\u002Fspan>\u003Cspan class=\"token function\">loginWith\u003C\u002Fspan>\u003Cspan class=\"token punctuation\">(\u003C\u002Fspan>\u003Cspan class=\"token string\">'drupal-password'\u003C\u002Fspan>\u003Cspan class=\"token punctuation\">,\u003C\u002Fspan> \u003Cspan class=\"token punctuation\">{\u003C\u002Fspan>\n  \u003Cspan class=\"token literal-property property\">data\u003C\u002Fspan>\u003Cspan class=\"token operator\">:\u003C\u002Fspan> \u003Cspan class=\"token punctuation\">{\u003C\u002Fspan> username\u003Cspan class=\"token punctuation\">,\u003C\u002Fspan> password \u003Cspan class=\"token punctuation\">}\u003C\u002Fspan>\u003Cspan class=\"token punctuation\">,\u003C\u002Fspan>\n\u003Cspan class=\"token punctuation\">}\u003C\u002Fspan>\u003Cspan class=\"token punctuation\">)\u003C\u002Fspan>",{"type":22,"html":53},"\u003Cul>\u003Cli>No redirect, and no proxy needed: the credentials go to Drupal's token endpoint through Druxt Auth's own server route.\u003C\u002Fli>\u003Cli>That route needs a server, so a statically generated site is out.\u003C\u002Fli>\u003Cli>Tokens and nothing else, so Drupal's own pages stay anonymous until you set \u003Ccode>passwordSession\u003C\u002Fcode>, which does need the proxy.\u003C\u002Fli>\u003C\u002Ful>",{"type":22,"html":55},"\u003Cp>Neither needs a form from you. \u003Ccode>0.5.0\u003C\u002Fcode> ships the sign-in too, a \u003Ccode>DruxtAuthLogin\u003C\u002Fcode> component and a Nuxt page at \u003Ccode>\u002Fuser\u002Flogin\u003C\u002Fcode>. Add \u003Ccode>pages\u002Fuser\u002Flogin.vue\u003C\u002Fcode> to replace the page, or a \u003Ccode>DruxtAuthLoginDefault\u003C\u002Fcode> component to theme the form.\u003C\u002Fp>","The two ways in",{"type":17,"layout":18,"regions":58,"title":72},{"content":59},[60,62,68,70],{"type":22,"html":61},"\u003Cp>Both post credentials to an endpoint rather than to Drupal's login form, and that form is where Drupal hangs the rest of logging in. \u003Ca href=\"https:\u002F\u002Fwww.drupal.org\u002Fproject\u002Ftfa\">TFA\u003C\u002Fa> interposes there. So does a forced password change, a terms-of-service gate, and anything else that alters it. Skip the form and you skip all of that, and so does anyone else who has the password.\u003C\u002Fp>",{"type":27,"src":63,"width":64,"height":65,"alt":66,"caption":67},"\u002Fimages\u002Fwriting\u002Fdruxt-auth-consent-screen.png",880,265,"Drupal's authorization dialog: You are allowing druxtjs.org to, three role scopes listed, with Allow and Deny buttons","\u003Cp>Drupal asking the visitor to allow the frontend. The redirect shows this, and the login form before it. Neither grant shows either screen.\u003C\u002Fp>",{"type":22,"html":69},"\u003Cp>Rate limiting still runs, in separate counters. Both read their limits from \u003Ccode>user.flood\u003C\u002Fcode>, so the numbers look the same, while the credential sign-in registers under \u003Ccode>user.http_login\u003C\u002Fcode> and the password grant under \u003Ccode>oauth2_password_grant.failed_login_user\u003C\u002Fcode>. Separate counters mean separate budgets, so five attempts per account becomes ten. Clearing the core flood entries will not release someone locked out by the other.\u003C\u002Fp>",{"type":22,"html":71},"\u003Cp>None of that makes either grant wrong. It makes them a trade, because the trip to Drupal is what ran those checks. If you need them, call \u003Ccode>loginWith\u003C\u002Fcode> without credentials and the old flow comes back, Drupal's login page and all. That is how I run two factor on another site. It works, but it means the full round trip every time, and making that less ugly is the next thing I want to fix.\u003C\u002Fp>","What the login form was doing for you",{"type":17,"layout":18,"regions":74,"title":88},{"content":75},[76,78,82,84,86],{"type":22,"html":77},"\u003Cp>You need to enable \u003Ca href=\"https:\u002F\u002Fdruxtjs.org\u002Fhow-to\u002Fproxy\">the Druxt proxy\u003C\u002Fa> for the \u003Cstrong>authorization code grant\u003C\u002Fstrong>:\u003C\u002Fp>",{"type":39,"title":79,"language":41,"code":80,"highlighted":81},"nuxt.config.js","druxt: {\n  proxy: { api: true },\n}","\u003Cspan class=\"token literal-property property\">druxt\u003C\u002Fspan>\u003Cspan class=\"token operator\">:\u003C\u002Fspan> \u003Cspan class=\"token punctuation\">{\u003C\u002Fspan>\n  \u003Cspan class=\"token literal-property property\">proxy\u003C\u002Fspan>\u003Cspan class=\"token operator\">:\u003C\u002Fspan> \u003Cspan class=\"token punctuation\">{\u003C\u002Fspan> \u003Cspan class=\"token literal-property property\">api\u003C\u002Fspan>\u003Cspan class=\"token operator\">:\u003C\u002Fspan> \u003Cspan class=\"token boolean\">true\u003C\u002Fspan> \u003Cspan class=\"token punctuation\">}\u003C\u002Fspan>\u003Cspan class=\"token punctuation\">,\u003C\u002Fspan>\n\u003Cspan class=\"token punctuation\">}\u003C\u002Fspan>",{"type":22,"html":83},"\u003Cp>It puts Drupal and the frontend on one origin, which is what lets the session cookie from the JSON login reach the authorize step. Without it the sign-in falls back to the redirect. On separate domains the cookie is third party, which leaves the redirect as the only option there, and none of this applies to the password grant.\u003C\u002Fp>",{"type":22,"html":85},"\u003Cp>Consumers are next. A Consumer cannot be public and confidential at once, so a site running both flows needs two, with \u003Ccode>passwordClientId\u003C\u002Fcode> for the second. It also has to authorize automatically, or the visitor sees Drupal's consent page anyway. On Simple OAuth 6 you need an OAuth2 scope too, because it refuses every authorization request until one exists.\u003C\u002Fp>",{"type":22,"html":87},"\u003Cp>The last is behaviour, not configuration. A Drupal session already open in the browser is refused, rather than signing the visitor in as whoever left it there. The error sets \u003Ccode>sessionInUse\u003C\u002Fcode> so your form can say why. To end that session instead, point \u003Ccode>sessionLogout\u003C\u002Fcode> at a route you write yourself in Drupal. Core will not do it.\u003C\u002Fp>","What you have to get right",{"type":17,"layout":18,"regions":90,"title":107},{"content":91},[92,94,96,98,100,102],{"type":22,"html":93},"\u003Cp>The password grant was never Druxt Auth's to give. Only Drupal's OAuth server can mint a token.\u003C\u002Fp>",{"type":22,"html":95},"\u003Cp>\u003Ca href=\"https:\u002F\u002Fwww.drupal.org\u002Fproject\u002Fsimple_oauth_password_grant\">Simple OAuth Password Grant\u003C\u002Fa> puts it back, maintained by \u003Ca href=\"https:\u002F\u002Fwww.drupal.org\u002Fu\u002Fnimoatwoodway\">Christoph Niedermoser\u003C\u002Fa> and \u003Ca href=\"https:\u002F\u002Fwww.drupal.org\u002Fu\u002Fchfoidl\">Christian Foidl\u003C\u002Fa>. They make the case for it themselves:\u003C\u002Fp>",{"type":22,"html":97},"\u003Cblockquote>\u003Cp>The PasswordGrant was part of the Simple OAuth module in Version 5 but got removed in Version 6 because the \u003Ca href=\"https:\u002F\u002Fdatatracker.ietf.org\u002Fdoc\u002Fhtml\u002Fdraft-ietf-oauth-security-topics\">OAuth2 best current practices\u003C\u002Fa> removed the PasswordGrant.\u003C\u002Fp>\u003Cp>However, when using Drupal in a decoupled scenario as a pure backend, you can trust your frontend application.\u003C\u002Fp>\u003Cp>For best user experience, the user must be able to input their login credentials on the Drupal frontend (which is decoupled), so the PasswordGrant makes sense here.\u003C\u002Fp>\u003C\u002Fblockquote>",{"type":22,"html":99},"\u003Cp>Thank you both for your contribution.\u003C\u002Fp>",{"type":22,"html":101},"\u003Cp>If either is useful to you, \u003Ccode>0.5.0\u003C\u002Fcode> is out now:\u003C\u002Fp>",{"type":39,"title":103,"language":104,"code":105,"highlighted":106},"Install","bash","composer require 'drupal\u002Fsimple_oauth_password_grant'\nnpm install druxt-auth@^0.5","\u003Cspan class=\"token function\">composer\u003C\u002Fspan> require \u003Cspan class=\"token string\">'drupal\u002Fsimple_oauth_password_grant'\u003C\u002Fspan>\n\u003Cspan class=\"token function\">npm\u003C\u002Fspan> \u003Cspan class=\"token function\">install\u003C\u002Fspan> druxt-auth@^0.5","Who put the password grant back",{"type":17,"layout":18,"regions":109},{"content":110},[111],{"type":112,"description":113,"url":114,"gitpod":115},"repository","\u003Cp>Druxt Auth, and the example app that runs both flows against a real Drupal. Sponsorship is what keeps Druxt maintained.\u003C\u002Fp>","https:\u002F\u002Fgithub.com\u002Fdruxt\u002Fdruxt-auth",false,"\u002Fwriting\u002Fdruxt-auth-050-two-ways-to-sign-in-20260926","4 min",{"loc":116},"articles-data\u002Fdruxt-auth-050-two-ways-to-sign-in-20260926","Ew_REYAoWv8kllE8xxvawaMS1u_CP2I9j_FHRtdLuyY",1790426002416]