Roblox OAuth Provider For NextAuth

Due to the public beta of Roblox OAuth I have decided to open-source my Roblox provider for NextAuth.

NextAuth is an authentication library for the popular fullstack framework Next. And a provider is a service that can be used to authenticate users via NextAuth.


example usage

import { RobloxProvider, RobloxProviderCallbacks_Jwt } from "roblox-provider"

export const authOptions = {
  providers: [
    RobloxProvider({
      clientId: process.env.ROBLOX_ID,
      clientSecret: process.env.ROBLOX_SECRET,
      scopes: ["openid", "profile"],
      include: ["name", "displayName", "avatar"]
    })
  ],

  callbacks: RobloxProviderCallbacks_Jwt
}

further documentation can be found on the npm page linked below:

19 Likes

What a coincidence! I’m making a site using (and learning) next-auth that focuses around gaming and this will be a big help for auth options.

3 Likes

I’d love to try this out. Thank you for sharing

2 Likes

Hello, got an error when trying to get RobloxProvider:

 ⨯ E:\Developpement\ProjetDev\Website\node_modules\roblox-provider\dist\index.js:48
export var RobloxProvider = function (options) {
^^^^^^

SyntaxError: Unexpected token 'export'
    at Object.compileFunction (node:vm:360:18)
    at wrapSafe (node:internal/modules/cjs/loader:1055:15)
    at Module._compile (node:internal/modules/cjs/loader:1090:27)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1180:10)
    at Module.load (node:internal/modules/cjs/loader:1004:32)
    at Function.Module._load (node:internal/modules/cjs/loader:839:12)
    at Module.require (node:internal/modules/cjs/loader:1028:19)
    at Module.mod.require (E:\Developpement\ProjetDev\Website\node_modules\next\dist\server\require-hook.js:64:28)
    at require (node:internal/modules/cjs/helpers:102:18)
    at Object.roblox-provider (E:\Developpement\ProjetDev\Website\.next\server\pages\api\auth\[...nextauth].js:32:18)
    at __webpack_require__ (E:\Developpement\ProjetDev\Website\.next\server\webpack-api-runtime.js:33:42)
    at eval (webpack-internal:///(api)/./pages/api/auth/[...nextauth].js:5:73)
    at Object.(api)/./pages/api/auth/[...nextauth].js (E:\Developpement\ProjetDev\Website\.next\server\pages\api\auth\[...nextauth].js:52:1)
    at __webpack_require__ (E:\Developpement\ProjetDev\Website\.next\server\webpack-api-runtime.js:33:42)
    at eval (webpack-internal:///(api)/./node_modules/next/dist/build/webpack/loaders/next-route-loader/index.js?kind=PAGES_API&page=%2Fapi%2Fauth%2F%5B...nextauth%5D&preferredRegion=&absolutePagePath=.%2Fpages%5Capi%5Cauth%5C%5B...nextauth%5D.js&middlewareConfigBase64=e30%3D!:11:85)

Here my code (located in /pages/api/auth/[…nextauth].js

import { RobloxProvider, RobloxProviderCallbacks_Jwt } from "roblox-provider"

export const authOptions = {
  providers: [
    RobloxProvider({
      clientId: process.env.ROBLOX_ID,
      clientSecret: process.env.ROBLOX_SECRET,
      scopes: ["openid", "profile"],
      include: ["name", "displayName", "avatar"],
      redirectUri: process.env.REDIRECTURI,
    })
  ],

  callbacks: RobloxProviderCallbacks_Jwt
}
1 Like

I also have this issue. I’m going to do some digging, maybe put up some changes and build on the roblox provider.

If and when I do this, I’ll make an open-source repo for this so people can go through and make relevant changes.

That was quick lol, I didn’t need to dig deep.

There was an issue with the package json for the module, I assume @MightyPart has his workspace setup differently to the default.

Correct package.json:

{
  "name": "roblox-provider",
  "description": "A Roblox OAuth2 provider for NextAuth.",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "type":"module",
  "keywords": ["Roblox", "NextAuth", "NextAuth Provider", "Roblox OpenCloud", "OpenCloud", "Roblox OAuth2"],
  "version": "1.0.16",
  "author": "MightyPart",
  "license": "ISC",
  "scripts": {
    "build": "tsc"
  },
  "devDependencies": {
    "typescript": "^5.0.4"
  },
  "compilerOptions": {
    "module": "ES6",
    "target": "ESNext"
  },
  "files": ["dist/**/*", "src/**/*"]
}

So the error stems from the compiler options and the module type, as it would have likely been marked as CommonJS and lacked the compiler options for ES6 for nextjs.

 "type":"module",

was missing

as was the compiler options:

"compilerOptions": {
    "module": "ES6",
    "target": "ESNext"
  },

This may not be best practice, but it’s a quick fix that’s worked for me, and if anyone wants to weigh in about best practices for this kind of thing, feel free.

I’m getting this error, anyone know why?

Unknown argument `type`. Did you mean `name`? Available options are marked with ?. {
  message: '\n' +
    'Invalid `prisma.user.create()` invocation:\n' +
    '\n' +
    '{\n' +
    '  data: {\n' +
    '    type: "roblox",\n' +
    '    ~~~~\n' +
    '    name: "flvffywvffy",\n' +
    '    displayName: "flvffy",\n' +
    '    include: [\n' +
    '      "name",\n' +
    '      "displayName",\n' +
    '      "avatar"\n' +
    '    ],\n' +
    '    email: undefined,\n' +
    '    emailVerified: null,\n' +
    '?   id?: String,\n' +
    '?   bio?: String | Null,\n' +
    '?   age?: Int | Null,\n' +
    '?   image?: String | Null,\n' +
    '?   accounts?: AccountCreateNestedManyWithoutUserInput,\n' +
    '?   sessions?: SessionCreateNestedManyWithoutUserInput,\n' +
    '?   products?: ProductOwnerCreateNestedManyWithoutUserInput\n' +
    '  }\n' +
    '}\n' +
    '\n' +
    'Unknown argument `type`. Did you mean `name`? Available options are marked with ?.',
  stack: 'PrismaClientValidationError: \n' +
    'Invalid `prisma.user.create()` invocation:\n' +
    '\n' +
    '{\n' +
    '  data: {\n' +
    '    type: "roblox",\n' +
    '    ~~~~\n' +
    '    name: "flvffywvffy",\n' +
    '    displayName: "flvffy",\n' +
    '    include: [\n' +
    '      "name",\n' +
    '      "displayName",\n' +
    '      "avatar"\n' +
    '    ],\n' +
    '    email: undefined,\n' +
    '    emailVerified: null,\n' +
    '?   id?: String,\n' +
    '?   bio?: String | Null,\n' +
    '?   age?: Int | Null,\n' +
    '?   image?: String | Null,\n' +
    '?   accounts?: AccountCreateNestedManyWithoutUserInput,\n' +
    '?   sessions?: SessionCreateNestedManyWithoutUserInput,\n' +
    '?   products?: ProductOwnerCreateNestedManyWithoutUserInput\n' +
    '  }\n' +
    '}\n' +
    '\n' +
    'Unknown argument `type`. Did you mean `name`? Available options are marked with ?

Heres my code:

import NextAuth from "next-auth";
import type { NextAuthOptions } from "next-auth";
import type { OAuthConfig } from "next-auth/providers/oauth";
// import DiscordProvider from "next-auth/providers/discord";

// @ts-ignore
import { RobloxProvider, RobloxProviderCallbacks_Jwt } from "roblox-provider";

import { prisma } from "@/lib/prisma";
import { PrismaAdapter } from "@next-auth/prisma-adapter";

export const authOptions: NextAuthOptions = {
  adapter: PrismaAdapter(prisma),
  providers: [
    // DiscordProvider({
    //   clientId: process.env.DISCORD_ID!,
    //   clientSecret: process.env.DISCORD_SECRET!,
    // }),
    RobloxProvider({
      clientId: process.env.ROBLOX_ID,
      clientSecret: process.env.ROBLOX_SECRET,
      redirectUri: process.env.ROBLOX_REDIRECT,
      scopes: ["openid", "profile"],
      include: ["name", "displayName", "avatar"],
    }),
  ],
  callbacks: RobloxProviderCallbacks_Jwt,
};

const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };

Have you managed to fix this? I have really similar code with the exact same error, here it is:

import NextAuth, { NextAuthOptions } from "next-auth";
import { PrismaAdapter } from "@next-auth/prisma-adapter"
import Providers from "next-auth/providers";
import prisma from "@/lib/prisma";
import GoogleProvider from "next-auth/providers/google";
import DiscordProvider from "next-auth/providers/discord";
import { RobloxProvider, RobloxProviderCallbacks_Database} from "roblox-provider";

export const authOptions: NextAuthOptions = {
  adapter: PrismaAdapter(prisma),
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID as string,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
    }),
    DiscordProvider({
      clientId: process.env.DISCORD_CLIENT_ID as string,
      clientSecret: process.env.DISCORD_CLIENT_SECRET as string,
    }),
    RobloxProvider({
          clientId: process.env.ROBLOX_ID,
          clientSecret: process.env.ROBLOX_SECRET,
          redirectUri: process.env.ROBLOX_REDIRECTURI,
          scopes: ["openid", "profile"],
          include: ["name", "displayName", "avatar"],
        }),
  ],
  callbacks: RobloxProviderCallbacks_Database,
};

const handler = NextAuth(authOptions);

export { handler as GET, handler as POST };

Please post your Prisma Model schema, this isn’t originating from the provider.

Please see my above comment, this isn’t coming from the provider, it’s a Prisma error.

Sorry for the late reply.

// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

generator client {
  provider = "prisma-client-js"
  previewFeatures = ["jsonProtocol"]
}

datasource db {
  provider = "postgresql"
  url = env("POSTGRES_PRISMA_URL") // uses connection pooling
  directUrl = env("POSTGRES_URL_NON_POOLING") // uses a direct connection
}


model Account {
  id                 String  @id @default(cuid())
  userId             String
  type               String
  provider           String
  providerAccountId  String
  refresh_token      String?  @db.Text
  access_token       String?  @db.Text
  expires_at         Int?
  token_type         String?
  scope              String?
  id_token           String?  @db.Text
  session_state      String?

  user User @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@unique([provider, providerAccountId])
  @@index([userId])
}

model Session {
  id           String   @id @default(cuid())
  sessionToken String   @unique
  userId       String
  expires      DateTime
  user         User     @relation(fields: [userId], references: [id], onDelete: Cascade)
  @@index([userId])
}

model User {
  id            String    @id @default(cuid())
  name          String?
  email         String?   @unique
  emailVerified DateTime?
  linkedRblxAcc String?
  image         String?
  accounts      Account[]
  sessions      Session[]
}

model VerificationToken {
  identifier String
  token      String   @unique
  expires    DateTime

  @@unique([identifier, token])
}

Add a type optional field onto your User model,
The above error is stating on prisma.user.create() you’re attempting to set a field called “type” when that doesn’t exist in the model, so it’s throwing an type error.

If you add this:
type String?
It should accommodate for the provider attempting to set it onto the document.

Let me know if this works.

1 Like

Thanks for open sourcing this! This is great. Sharing my experience on the more recent versions of Next and NextAuth.

I wasn’t able to make this work starting on Next v14 and the new next-auth v5 beta due to some missing configurations in the package:

The next-auth team made some code breaking changes after they revamped next-auth to be more agnostic and renamed it as Auth.js. I think one of them is related to the error above (correct me if I’m wrong). Migration guide can be found here.

As of writing, a pull request was already made to include Roblox in the list of built-in providers. However, if needed, one can create a custom implementation of the ROBLOX provider following this guide. Make sure to also add the authorization, token, and client params. Auth.js doesn’t seem to automatically detect the token route, and we need the authorization parameters to define our custom scopes. See example below:

const RobloxProvider = (
    options: RobloxProviderOptions // your custom config interface
): OIDCConfig<RobloxProfile> => {
    return {
        id: "roblox",
        name: "Roblox",
        type: "oidc",
        ...
        client: {
            authorization_signed_response_alg: "ES256",
            id_token_signed_response_alg: "ES256",
        },
        token: "https://apis.roblox.com/oauth/v1/token", // manually add token endpoint
        authorization: {
            params: {
                url: "https://apis.roblox.com/oauth/v1/authorize",
                scope: options.scopes.join(" "), // define your scopes here
                redirect_uri: options.redirectUri,
                state: options.state,
                response_type: "code"
            },
        },
    }
}
2 Likes