W
Wasp-lang•5mo ago
wardbox

Integrate custom oauth provider with wasp auth and session entities

I'm working on an app for League of Legends players. Riot Games provides Riot Sign On (RSO) so players can authenticate as their in-game account. Is there a way to do this with api endpoints or some other method since it isn't a supported auth method?
25 Replies
kapa.ai
kapa.ai•5mo ago
Hi @wardbox. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- Wasp currently supports several auth methods including Email, Username & Password, Google, Github, and Keycloak. However, Riot Sign On (RSO) is not directly supported as of the information provided in the knowledge sources. However, there are a couple of ways you could potentially integrate RSO: 1. Custom API Endpoints: You could create custom API endpoints and use a Node.js library for RSO. This would involve handling the OAuth flow manually, including redirecting the user to the RSO login page, handling the callback with the authorization code, exchanging it for an access token, and creating a session for the user. 2. Arctic Library: Wasp is planning to integrate the Arctic library, which supports multiple OAuth providers. If RSO is supported by Arctic, you could potentially use it once Wasp integrates Arctic. However, as of the information provided, it's not clear if Arctic supports RSO. For the custom API endpoints method, you could follow the discussion in the Discord support thread (https://discord.com/channels/686873244791210014/1208050260698464307) where a similar question was asked about integrating Keycloak, which was not directly supported at that time. The community members suggested creating custom API endpoints and using a Node.js library for the OAuth provider. For the user session, you could potentially use a custom global middleware to detect the token in the request, obtain the user based on it (via Prisma), and inject it into req. Please note that these are potential solutions and might require additional work and testing to ensure they work as expected with RSO and Wasp. For more information on Wasp's auth methods and how to customize them, you can refer to the Wasp Auth Docs (https://wasp-lang.dev/docs/auth/overview#available-auth-methods). ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @wardbox, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist: Perhaps try again in 15mins?
wardbox
wardboxOP•5mo ago
@kapa.ai If I go the custom api endpoint route how would I integrate it with wasps built in auth entity? or would i only be able to add it to the user entity?
kapa.ai
kapa.ai•5mo ago
Let me check the knowledge base for you. One moment! :books:
MEE6
MEE6•5mo ago
Wohooo @wardbox, you just became a Waspeteer level 5!
kapa.ai
kapa.ai•5mo ago
---------------------- When you use Wasp's built-in authentication, it automatically creates a few entities behind the scenes, including Auth, AuthIdentity, and Session entities. These entities are used to store the user's login credentials and session information. If you decide to implement custom authentication using API endpoints, you would likely need to interact with these entities directly. For example, after successfully authenticating a user with Riot Sign On (RSO), you might create a new Auth entity for that user, create a new Session, and then return the session ID to the client. However, it's important to note that these entities are considered internal to Wasp and are not intended to be manipulated directly by developers. Doing so could potentially lead to inconsistencies or security vulnerabilities in your application. Instead, you might consider creating a new entity to store any additional information you need for your custom authentication method. This entity could have a relation to the User entity, allowing you to link each user with their RSO account. Here's an example of how you might define such an entity:
entity RiotAccount {=psl
id Int @id @default(autoincrement())
userId Int @unique
user User @relation(fields: [userId], references: [id])
// Any other fields you need to store about the Riot account
psl=}
entity RiotAccount {=psl
id Int @id @default(autoincrement())
userId Int @unique
user User @relation(fields: [userId], references: [id])
// Any other fields you need to store about the Riot account
psl=}
In this example, the RiotAccount entity has a one-to-one relation with the User entity. This means that each user can have one associated Riot account, and each Riot account is associated with one user. For more information on how to use entities in Wasp, you can refer to the Wasp Entities Docs (https://wasp-lang.dev/docs/data-model/entities). ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @wardbox, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist: Perhaps try again in 15mins?
martinsos
martinsos•5mo ago
@miho
miho
miho•5mo ago
Could you maybe share some links about how the Riot Sign On works? e.g. some SDK they have or some docs they provide. I'd have to think about this and try it out to see what we can do 🙂
wardbox
wardboxOP•5mo ago
It’s tough because they gate actually using it behind having a production key, I’ve got one so happy to try it out, but the docs are here https://docs.google.com/document/d/1_8i2PvPA3edFHIh1IwfO5vs5rcl04O62Xfj0o7zCP3c/edit?pli=1 https://developer.riotgames.com/docs/lol#rso-integration_implementing-rso
Google Docs
Implementing Riot Sign On 20200805
Implementing Riot Sign On (web tutorial) This tutorial will get you through making first contact with Riot Sign On, including serving a Sign In link, authenticating the user, and processing the user’s tokens, but you’ll need a few things before continuing: Riot Sign On — hosted at https://auth.ri...
wardbox
wardboxOP•5mo ago
they do have an example rso app in that second doc which might be useful
miho
miho•5mo ago
Okok, I'll check it out tomorrow and let you know what is possible 😄
wardbox
wardboxOP•5mo ago
thanks! i know it's a weird edge case. i also found a more indepth gist which isn't linked on their official docs but is in their dev discord server lol https://gist.github.com/Henrik-3/d6b631fb7c61821bc16b17cd347a3811
Gist
RSO
RSO . GitHub Gist: instantly share code, notes, and snippets.
miho
miho•5mo ago
Did you apply for the RSO client id and client secret? Looking at the information you provided, I'd probably do something like this: 1. Use the oslo/oauth2 client (since Wasp already uses oslo) -> https://oslo.js.org/reference/oauth2/ 2. Use the links from the RSO docs with oslo to instantiate a OAuth client:
GET https://auth.riotgames.com/authorize Endpoint for obtaining an authorization code
POST https://auth.riotgames.com/token Endpoint to exchange authorization codes for access, identity, and refresh tokens
GET https://auth.riotgames.com/jwks.json Endpoint to grab JSON Web Keys for verifying the authenticity of access and identity tokens
GET https://auth.riotgames.com/userinfo Endpoint to use your access token to obtain user information
GET https://auth.riotgames.com/authorize Endpoint for obtaining an authorization code
POST https://auth.riotgames.com/token Endpoint to exchange authorization codes for access, identity, and refresh tokens
GET https://auth.riotgames.com/jwks.json Endpoint to grab JSON Web Keys for verifying the authenticity of access and identity tokens
GET https://auth.riotgames.com/userinfo Endpoint to use your access token to obtain user information
3. Use Wasp's custom API endpoints to create a: 1. Redirect URL /auth-with-rso or smth like that that will redirect the user to createAuthorizationURL() result 2. Callback URL /auth-with-rso-callback that will handle the code it receives from Riot validateAuthorizationCode() 3. Use the access token / refresh token you received in the callback to store it in the DB or just use it directly to fetch user data and store it in the DB with Prisma 4. Send your users to the /auth-with-rso URL from the client to start the flow 🙂 I suppose in between some of these steps you'll need to store some cookies with state and/or codeVerifier values (depends if RSO uses PKCE OAuth flow, but it doesn't seem so).
Oslo
oslo/oauth2
A collection of auth utilities.
miho
miho•5mo ago
Let me know if this makes sense to you, if you start implementing it and maybe get stuck, I'm happy to jump on a call with you to try to get it working. We can then document this in a gist which might be useful for other Wasp users trying to implement custom auth providers.
wardbox
wardboxOP•5mo ago
I did apply for it, just waiting to hear if it gets approved. As soon as I get those I'll take a crack at it and let you know, thanks for the help with this! @miho @kapa.ai I think I'm very close! I have auth from RSO working (getting access and refresh token, not verifying with the jwk stuff yet but i'll get there), but getting my user data properly. Now just need to figure out how to throw it in the user entity which should be the easy part but i'm struggling for some reason. Would you have some time this week to hop on a call? I'm in PST time zone but pretty flexible as i WFH
kapa.ai
kapa.ai•5mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- I'm an AI developed by OpenAI and I'm unable to make calls or live meetings. However, I can certainly help guide you through the process of adding data to the User entity. Once you have authenticated the user with RSO and obtained the necessary data, you can create a new User entity and save the data to it. Here's an example of how you might do this:
import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

// ... (Your RSO authentication code here)

// Once you have the user data, you can create a new User entity:
const user = await prisma.user.create({
data: {
// Your user data here. For example:
username: rsoData.username,
email: rsoData.email,
// etc...
},
})
import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

// ... (Your RSO authentication code here)

// Once you have the user data, you can create a new User entity:
const user = await prisma.user.create({
data: {
// Your user data here. For example:
username: rsoData.username,
email: rsoData.email,
// etc...
},
})
In this example, rsoData would be the data you obtained from RSO. You would replace username, email, etc. with the actual fields of your User entity. Remember to define these fields in your User entity in your Wasp code:
entity User {=psl
id Int @id @default(autoincrement())
username String @unique
email String @unique
// Any other fields you want to store about the user
psl=}
entity User {=psl
id Int @id @default(autoincrement())
username String @unique
email String @unique
// Any other fields you want to store about the user
psl=}
For more information on how to use Prisma with Wasp, you can refer to the Wasp Prisma Docs (https://wasp-lang.dev/docs/data-model/prisma). ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @wardbox, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist: Perhaps try again in 15mins?
miho
miho•5mo ago
Are you struggling to save data to the User entity? https://wasp-lang.dev/docs/advanced/apis#using-entities-in-apis
Custom HTTP API Endpoints | Wasp
In Wasp, the default client-server interaction mechanism is through Operations. However, if you need a specific URL method/path, or a specific response, Operations may not be suitable for you. For these cases, you can use an api. Best of all, they should look and feel very familiar.
wardbox
wardboxOP•5mo ago
So i'm trying to let someone sign up via RSO, so there isn't a user yet. i've got this for my api definition
api authWithRiotCallback {
fn: import { authWithRiotCallback } from "@src/apis",
entities: [User],
httpRoute: (POST, "/rso/callback"),
}
api authWithRiotCallback {
fn: import { authWithRiotCallback } from "@src/apis",
entities: [User],
httpRoute: (POST, "/rso/callback"),
}
and then the relevant place where i'm calling an action to signup
const authUser = context.user;

const user = await rsoSignup({
riotId: userInfo.sub,
puuid: userInfo.puuid,
accessToken: access_token,
refreshToken: refresh_token,
tokenExpiration: new Date(Date.now() + 1000 * userInfo.exp),
}, authUser);
const authUser = context.user;

const user = await rsoSignup({
riotId: userInfo.sub,
puuid: userInfo.puuid,
accessToken: access_token,
refreshToken: refresh_token,
tokenExpiration: new Date(Date.now() + 1000 * userInfo.exp),
}, authUser);
@kapa.ai
kapa.ai
kapa.ai•5mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- It looks like you're on the right track. You're correctly defining an API endpoint and then calling an action to sign up the user. In your rsoSignup action, you'll want to create a new User entity with the data you've obtained from RSO. Here's an example of how you might do this:
import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

export const rsoSignup = async (data, _context) => {
// Create a new User entity with the data from RSO
const user = await prisma.user.create({
data: {
riotId: data.riotId,
puuid: data.puuid,
// ... any other data you want to store
},
})

// Return the newly created user
return user
}
import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

export const rsoSignup = async (data, _context) => {
// Create a new User entity with the data from RSO
const user = await prisma.user.create({
data: {
riotId: data.riotId,
puuid: data.puuid,
// ... any other data you want to store
},
})

// Return the newly created user
return user
}
In this example, data would be the object you're passing to rsoSignup, which contains the data you've obtained from RSO. Remember to define these fields in your User entity in your Wasp code:
entity User {=psl
id Int @id @default(autoincrement())
riotId String @unique
puuid String @unique
// Any other fields you want to store about the user
psl=}
entity User {=psl
id Int @id @default(autoincrement())
riotId String @unique
puuid String @unique
// Any other fields you want to store about the user
psl=}
In this example, riotId and puuid are the fields you're storing from RSO. You would replace these with the actual fields you want to store. For more information on how to use Prisma with Wasp, you can refer to the Wasp Prisma Docs (https://wasp-lang.dev/docs/data-model/prisma). ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @wardbox, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist: Perhaps try again in 15mins?
wardbox
wardboxOP•5mo ago
ah i forgot the TS error,
Argument of type 'AuthUser | undefined' is not assignable to parameter of type '{ user: AuthUser; }'.
Type 'undefined' is not assignable to type '{ user: AuthUser; }'.ts(2345)
Argument of type 'AuthUser | undefined' is not assignable to parameter of type '{ user: AuthUser; }'.
Type 'undefined' is not assignable to type '{ user: AuthUser; }'.ts(2345)
that comes up where i try to pass in authuser
miho
miho•5mo ago
So there are two ways you can do this: 1. Users sign up with built-in auth providers and then you use RSO login to get extra info -> should work without problems 2. Users sign up with RSO -> there should be some challenges here - you'll need to recreate session handling ... hm, I think we might need to do this one together because it's quite a bit outside of Wasp I'm off until the end of this week, but we could schedule a call next week and work on this together on a call?
wardbox
wardboxOP•5mo ago
let me try the 1st route, i think i can gonna do discord auth then have them sync their rso stuff after
miho
miho•5mo ago
Ok, let me know how that works out for you. I'll try on Monday to integerate some other OAuth provider (e.g. Facebook) to see how would it work out. And if you want we can have a call at 7 or 8 or 9 AM PST. (It's 6:56 PST now, if I'm not mistaken?)
wardbox
wardboxOP•5mo ago
yeah that would work for me - it's 7:57 where i am rn, happy to do a call at 7 if that works for you? i guess i'm still stuck on how it'd work even if they were logged in, shouldn't my code not have that error as it is? or would i move it to a user signup function?
miho
miho•5mo ago
Ok, let's do 7 PST on Monday, DM me your email and I'll send an invite 🙂 It looks like rsoSignup fn accepts an object { user: AuthUser } and you are passing in AuthUser | undefined so, don't call it if the context.user is undefined and pass it in as { user: context.user }
wardbox
wardboxOP•5mo ago
sweet that does it, thanks for setting up the call!
Want results from more Discord servers?
Add your server