Password Grant

Authenticate users using username and password.


The Password Grant allows users to authenticate using their credentials i.e. their username and password.

Demo

See https://example-auth.next-drupal.org/.

Flow

  1. User provides their username and password on your Next.js site.
  2. The username and password is sent via POST request to the API route `/api/auth/callback/credentials`.
  3. We then send the credentials to the authorization server (Drupal) for authentication (server-side).
  4. We get back an `access_token` for successful authentication.

Dependencies

Add `next-auth` and `jwt-decode` to your Next.js site.

yarn add next-auth@beta jwt-decode

We are using NextAuth.js 4 beta in the examples but the stable 3.x release works as well (with a few tweaks).

Installation

Drupal

Create a new OAuth consumer:

  1. Visit /admin/config/services/consumer/add
  2. Fill in Label and Secret.
  3. Click Save

Next.js

  1. Fill in the following in your `.env`:
OAUTH_CLIENT_ID= // The Consumer UUID created above.
OAUTH_CLIENT_SECRET= // The Client secret you added above.
NEXTAUTH_URL=http://localhost:3000 // The base url for your Next.js site
JWT_SIGNING_PRIVATE_KEY= // Generate one using `jose newkey -s 512 -t oct -a HS512`
  1. Update your `_app.tsx` and wrap `Component` in a `SessionProvider.

pages/_app.tsx

import { AppProps } from "next/app"
import { SessionProvider } from "next-auth/react"
export default function App({
Component,
pageProps: { session, ...pageProps },
}: AppProps) {
return (
<SessionProvider session={session}>
<Component {...pageProps} />
</SessionProvider>
)
}
  1. Create the following API route `/pages/api/auth/[...nextauth].js`.

pages/api/auth/[...nextauth].js

import NextAuth from "next-auth"
import CredentialsProvider from "next-auth/providers/credentials"
import jwt_decode from "jwt-decode"
export default NextAuth({
providers: [
CredentialsProvider({
name: "Credentials",
credentials: {
username: { label: "Username", type: "text", placeholder: "Username" },
password: { label: "Password", type: "password" },
},
async authorize(credentials) {
const formData = new URLSearchParams()
formData.append("grant_type", "password")
formData.append("client_id", process.env.OAUTH_CLIENT_ID)
formData.append("client_secret", process.env.OAUTH_CLIENT_SECRET)
formData.append("username", credentials.username)
formData.append("password", credentials.password)
// Get access token from Drupal.
const response = await fetch(
`${process.env.NEXT_PUBLIC_DRUPAL_BASE_URL}/oauth/token`,
{
method: "POST",
body: formData,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
}
)
const data = await response.json()
if (response.ok && data?.access_token) {
return data
}
return null
},
}),
],
jwt: {
signingKey: process.env.JWT_SIGNING_PRIVATE_KEY,
encryption: true,
},
callbacks: {
async jwt({ token, user }) {
// Forward the access token
if (user) {
token.accessToken = user.access_token
}
return token
},
async session({ session, token }) {
if (token?.accessToken) {
session.accessToken = token.accessToken
// Decode token and pass info to session.
// This data will be available client-side.
const decoded = jwt_decode(token.accessToken)
session.user.id = decoded.id
session.user.email = decoded.email
session.user.username = decoded.username
}
return session
},
},
})
  1. If you visit `/api/auth/signin` you should now see the credentials authentication form.
NextAuth.js Credentials Sign In
NextAuth.js Credentials Sign In

You can use this form to sign in.

The `/api/auth/signin` pages is a pre-built page that comes with NextAuth.js. If you would like to provide your own custom login page, see the documentation.

useSession

To handle authentication in your Next.js components, you can use the `useSession` hook.

import { useSession } from "next-auth/react"
export default function Page() {
const { data, status } = useSession()
if (status === "authenticated") {
return <p>Signed in as {data.user.email}</p>
}
return <a href="/api/auth/signin">Sign in</a>
}

Additional information

Drupal

When using the Password Grant, you can implement the `hook_simple_oauth_private_claims_alter` hook to pass additional information to the session.

<?php
use Drupal\simple_oauth\Entities\AccessTokenEntity;
use Drupal\user\Entity\User;
/**
* Implements hook_simple_oauth_private_claims_alter().
*/
function example_auth_simple_oauth_private_claims_alter(&$private_claims, AccessTokenEntity $access_token_entity) {
$user_id = $access_token_entity->getUserIdentifier();
/** @var \Drupal\user\UserInterface $user */
$user = User::load($user_id);
$private_claims = [
'id' => $user->id(),
'email' => $user->getEmail(),
'username' => $user->getAccountName(),
'field_name' => $user->get('field_name')->value,
];
}

Next.js

Then update your `session` callback as follows:

callbacks: {
async session({ session, token }) {
if (token?.accessToken) {
session.accessToken = token.accessToken
// Decode token and pass info to session.
// This data will be available client-side.
const decoded = jwt_decode(token.accessToken)
session.user.id = decoded.id
session.user.email = decoded.email
session.user.username = decoded.username
session.user.field_name = decoded.field_name
}
return session
},
},

TypeScript

If you're using TypeScript, you can augment the `User` type with the additional information.

types/next-auth.d.ts

import { DefaultSession, Session } from "next-auth"
declare module "next-auth" {
interface Session {
user?: DefaultSession["user"] & {
field_name?: string
}
}
}