W
Wasp2w ago
mb23

Missing routes for Slack oAuth

I want to allow my users to authenticate using their Slack account (not as a main auth method though), rather as kind of integration. I have set up these routes in main.wasp: // 2a) Slack OAuth: Auth-Redirect route SlackAuthRoute { path: "/slack/auth", to: SlackAuthPage } page SlackAuthPage { component: import SlackAuthPage from "@src/client/app/SlackAuthPage.jsx", authRequired: false } // 2b) Slack OAuth: Callback route SlackCallbackRoute { path: "/slack/callback", to: SlackCallbackPage } page SlackCallbackPage { component: import SlackCallbackPage from "@src/client/app/SlackCallbackPage.jsx", authRequired: false } But somehow, when trying to reach these pages (as part of the authentication process) I get this error on the pages: $d data : "Error: No route matches URL "/slack/callback"" error : Error: No route matches URL "/slack/callback" at vn (https://linkforce.io/assets/index-LRfPohIU.js:58:10991) at VN (https://linkforce.io/assets/index-LRfPohIU.js:57:11080) at L4 (https://linkforce.io/assets/index-LRfPohIU.js:76:792) at https://linkforce.io/assets/index-LRfPohIU.js:211:28465 internal : true status : 404 statusText : "Not Found" [[Prototype]] : Object constructor : class $d [[Prototype]] : Object
7 Replies
kapa.ai
kapa.ai2w ago
Hi @mb23. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- It looks like you're experiencing an issue with your Slack OAuth routes in your Wasp application. Based on the error message, your application can't find the routes you've defined. Possible Causes and Solutions 1. Route Order Matters When using wildcard or catch-all routes, the order of route declarations in your main.wasp file matters. Make sure your Slack routes are defined before any wildcard routes.
// Define specific routes first
route SlackAuthRoute { path: "/slack/auth", to: SlackAuthPage }
route SlackCallbackRoute { path: "/slack/callback", to: SlackCallbackPage }

// Any wildcard routes should come last
route WildcardRoute { path: "*", to: WildcardPage }
// Define specific routes first
route SlackAuthRoute { path: "/slack/auth", to: SlackAuthPage }
route SlackCallbackRoute { path: "/slack/callback", to: SlackCallbackPage }

// Any wildcard routes should come last
route WildcardRoute { path: "*", to: WildcardPage }
2. OAuth Redirect URL Format In Wasp 0.13.0, the OAuth redirect URL format changed. The new format is:
{serverUrl}/auth/{provider}/callback
{serverUrl}/auth/{provider}/callback
Instead of:
{clientUrl}/auth/login/{provider}
{clientUrl}/auth/login/{provider}
Make sure you've updated your redirect URLs in your Slack OAuth application settings to match the new format Migration from 0.12.X to 0.13.X. 3. WASP_SERVER_URL Environment Variable If you're using Wasp 0.13.0 or later, make sure you've defined the WASP_SERVER_URL environment variable, which is required for OAuth providers. 4. Check for Typos Double-check that the path in your route declarations exactly matches the URL you're trying to access. Even a small typo can cause the route not to match. 5. Restart Your Development Server Sometimes, changes to routes require a full restart of your Wasp development server to take effect. If you're still experiencing issues after trying these solutions, you might need to look into modifying the templates Wasp uses to build your app, as mentioned in a Discord thread about wildcard routes Discord support thread: Wildcard routes for 404/not found redirects?. Let me know if any of these solutions help resolve your issue! ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @mb23, 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?
mb23
mb23OP2w ago
@kapa.ai I DON'T want to use the Slack auth as main auth method. It's not used as a signup method for users but rather as kind of an integration. None of your tips worked.
kapa.ai
kapa.ai2w ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- I understand you want to use Slack OAuth as an integration rather than a main authentication method. Looking at the knowledge sources, I see that Wasp's built-in OAuth support is primarily designed for main authentication methods. For your use case, you might need to implement a custom route using Wasp's server configuration capabilities. Here's how you could approach this: Custom Routes for Slack Integration Since the standard OAuth routes in Wasp are designed for main authentication methods, you can create custom routes for your Slack integration using the server setup function:
// in main.wasp
app MyApp {
title: "My app",
// ...
server: {
setupFn: import { mySetupFunction } from "@src/myServerSetupCode.js"
}
}
// in main.wasp
app MyApp {
title: "My app",
// ...
server: {
setupFn: import { mySetupFunction } from "@src/myServerSetupCode.js"
}
}
Then in your server setup code:
// src/myServerSetupCode.js
export const mySetupFunction = async ({ app }) => {
// Add custom Slack OAuth routes
app.get('/slack/auth', (req, res) => {
// Your Slack auth logic
})

app.get('/slack/callback', (req, res) => {
// Your Slack callback handling logic
})
}
// src/myServerSetupCode.js
export const mySetupFunction = async ({ app }) => {
// Add custom Slack OAuth routes
app.get('/slack/auth', (req, res) => {
// Your Slack auth logic
})

app.get('/slack/callback', (req, res) => {
// Your Slack callback handling logic
})
}
This approach allows you to create custom routes that aren't tied to Wasp's main authentication system, as shown in the Server Config documentation. If you're still experiencing issues, you might want to share a minimal reproducible example of your app with the Wasp community, as suggested in one of the Discord threads, to get more specific help with your implementation. Would you like more details on implementing these custom routes for your Slack integration? ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @mb23, 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?
mb23
mb23OP2w ago
@kapa.ai I've set it up but it didn't help: in main.wasp ... client: { rootComponent: import { App } from "@src/client/App.tsx", },
server: { setupFn: import { setupSpaFallback } from "@src/server/setupSpaFallback.js" }, ... in setupSpaFallback.js: // src/server/setupSpaFallback.js import express from 'express' // ES import for static middleware import { resolve } from 'path' import { fileURLToPath } from 'url' import { ServerSetupFn } from 'wasp/server' /** @type {ServerSetupFn} / export const setupSpaFallback = async ({ app }) => { // 1) Compute absolute paths
const __dirname = fileURLToPath(new URL('.', import.meta.url)) const webAppDir = resolve(process.cwd(), '.wasp/out/web-app') const indexHtml = resolve(webAppDir, 'index.html') // 2) Static‐files for assets app.use( '/assets', express.static(resolve(webAppDir, 'assets')) ) // 3) Specific OAuth fallback routes app.get('/slack/auth', (_req, res) => { res.sendFile(indexHtml) }) app.get('/slack/callback', (_req, res) => { res.sendFile(indexHtml) }) // 4) Catch‐all for any other client‐side route app.get('
', (_req, res) => { res.sendFile(indexHtml) }) }
kapa.ai
kapa.ai2w ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- I see you've implemented a custom server setup function to handle your Slack OAuth routes, but you're still experiencing issues. Let's analyze what might be happening. The approach you're taking is correct - using the setupFn to create custom routes. However, there's a potential issue with how the routes are being handled. The Issue Your implementation is sending the index.html file for these routes, which is a common SPA (Single Page Application) fallback strategy. However, this doesn't actually implement the OAuth logic - it just ensures the React app loads on those routes. For OAuth integration to work, you need to: 1. Handle the initial OAuth redirect to Slack 2. Process the callback from Slack with the authorization code Suggested Solution Instead of just serving the index.html file, you need to implement the actual OAuth flow:
// src/server/setupSpaFallback.js
import express from 'express'
import { resolve } from 'path'
import { fileURLToPath } from 'url'
import { ServerSetupFn } from 'wasp/server'
import axios from 'axios' // You'll need to install this

/** @type {ServerSetupFn} */
export const setupSpaFallback = async ({ app }) => {
// Static files setup as you have it...
const __dirname = fileURLToPath(new URL('.', import.meta.url))
const webAppDir = resolve(process.cwd(), '.wasp/out/web-app')
const indexHtml = resolve(webAppDir, 'index.html')

app.use('/assets', express.static(resolve(webAppDir, 'assets')))

// Slack OAuth initiation
app.get('/slack/auth', (req, res) => {
const slackAuthUrl = `<https://slack.com/oauth/v2/authorize?client_id=${process.env.SLACK_CLIENT_ID}&scope=chat:write&redirect_uri=${encodeURIComponent(process.env.WASP_SERVER_URL> + '/slack/callback')}`
res.redirect(slackAuthUrl)
})
// Slack OAuth callback
app.get('/slack/callback', async (req, res) => {
const { code } = req.query

if (!code) {
return res.status(400).send('Missing authorization code')
}

try {
// Exchange code for access token
const response = await axios.post('<https://slack.com/api/oauth.v2.access',> null, {
params: {
client_id: process.env.SLACK_CLIENT_ID,
client_secret: process.env.SLACK_CLIENT_SECRET,
code,
redirect_uri: process.env.WASP_SERVER_URL + '/slack/callback'
}
})

// Store the token in your database for the user
// You'll need to implement this part based on your app's needs

// Redirect to a success page
res.redirect('/slack-integration-success')
} catch (error) {
console.error('Slack OAuth error:', error)
res.redirect('/slack-integration-error')
}
})

// Catch-all for any other client-side route
app.get('*', (_req, res) => {
res.sendFile(indexHtml)
})
}
// src/server/setupSpaFallback.js
import express from 'express'
import { resolve } from 'path'
import { fileURLToPath } from 'url'
import { ServerSetupFn } from 'wasp/server'
import axios from 'axios' // You'll need to install this

/** @type {ServerSetupFn} */
export const setupSpaFallback = async ({ app }) => {
// Static files setup as you have it...
const __dirname = fileURLToPath(new URL('.', import.meta.url))
const webAppDir = resolve(process.cwd(), '.wasp/out/web-app')
const indexHtml = resolve(webAppDir, 'index.html')

app.use('/assets', express.static(resolve(webAppDir, 'assets')))

// Slack OAuth initiation
app.get('/slack/auth', (req, res) => {
const slackAuthUrl = `<https://slack.com/oauth/v2/authorize?client_id=${process.env.SLACK_CLIENT_ID}&scope=chat:write&redirect_uri=${encodeURIComponent(process.env.WASP_SERVER_URL> + '/slack/callback')}`
res.redirect(slackAuthUrl)
})
// Slack OAuth callback
app.get('/slack/callback', async (req, res) => {
const { code } = req.query

if (!code) {
return res.status(400).send('Missing authorization code')
}

try {
// Exchange code for access token
const response = await axios.post('<https://slack.com/api/oauth.v2.access',> null, {
params: {
client_id: process.env.SLACK_CLIENT_ID,
client_secret: process.env.SLACK_CLIENT_SECRET,
code,
redirect_uri: process.env.WASP_SERVER_URL + '/slack/callback'
}
})

// Store the token in your database for the user
// You'll need to implement this part based on your app's needs

// Redirect to a success page
res.redirect('/slack-integration-success')
} catch (error) {
console.error('Slack OAuth error:', error)
res.redirect('/slack-integration-error')
}
})

// Catch-all for any other client-side route
app.get('*', (_req, res) => {
res.sendFile(indexHtml)
})
}
Make sure to: 1. Set up environment variables for SLACK_CLIENT_ID and SLACK_CLIENT_SECRET 2. Create the necessary success/error pages in your React app 3. Implement the logic to store the Slack access token for the user This approach implements the actual OAuth flow rather than just serving the SPA, which should resolve your issue with the Slack integration. Let me know if you need more specific guidance on any part of this implementation! ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @mb23, 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?
Carlos
Carlos2w ago
Hi @mb23 did you make any progress in this? One main note that I think the bot didn't get quite right is that these /slack/auth and /slack/callback endpoints are usually an api endpoint and not a page. I'd probably implement it something like this (using Arctic as an OAuth library but feel free to use any other):
// main.wasp

// ...
api slackLogin {
fn: import { login } from "@src/api/slack",
httpRoute: (GET, "/slack/auth")
}

api slackCallback {
fn: import { callback } from "@src/api/slack",
httpRoute: (GET, "/slack/callback")
}
// ...
// main.wasp

// ...
api slackLogin {
fn: import { login } from "@src/api/slack",
httpRoute: (GET, "/slack/auth")
}

api slackCallback {
fn: import { callback } from "@src/api/slack",
httpRoute: (GET, "/slack/callback")
}
// ...
// src/api/slack.ts

import * as arctic from "arctic"
import {type SlackLogin, type SlackCallback} from "wasp/server/api"
import assert from "node:assert/strict"
import { config } from 'wasp/server'

const slack = new arctic.Slack(process.env.SLACK_CLIENT_ID!, process.env.SLACK_CLIENT_SECRET!, process.env.SLACK_REDIRECT_URI)

export const login: SlackLogin = async (req, res) => {
const state = arctic.generateState()
const url = slack.createAuthorizationURL(state, ['openid', 'profile', /* add any more scopes you need */]);
res.cookie('oauth-state', state, {
secure: true, // set to false in localhost
path: "/",
httpOnly: true,
maxAge: 60 * 10 // 10 min
})
res.redirect(url.toString())
}

export const callback: SlackCallback = async (req, res) => {
const {code, state: urlState} = req.query
const {state: cookieState} = req.cookies
assert.equals(urlState, cookieState, "State does not match")
assert(code, "No code given")
const tokens = await slack.validateAuthorizationCode(code);
const accessToken = tokens.accessToken();
const idToken = tokens.idToken();

// now you can store the access and id tokens wherever you need them

// and redirect to your `page`
res.redirect(new URL('/slack-done', config.frontendUrl).toString())
}
// src/api/slack.ts

import * as arctic from "arctic"
import {type SlackLogin, type SlackCallback} from "wasp/server/api"
import assert from "node:assert/strict"
import { config } from 'wasp/server'

const slack = new arctic.Slack(process.env.SLACK_CLIENT_ID!, process.env.SLACK_CLIENT_SECRET!, process.env.SLACK_REDIRECT_URI)

export const login: SlackLogin = async (req, res) => {
const state = arctic.generateState()
const url = slack.createAuthorizationURL(state, ['openid', 'profile', /* add any more scopes you need */]);
res.cookie('oauth-state', state, {
secure: true, // set to false in localhost
path: "/",
httpOnly: true,
maxAge: 60 * 10 // 10 min
})
res.redirect(url.toString())
}

export const callback: SlackCallback = async (req, res) => {
const {code, state: urlState} = req.query
const {state: cookieState} = req.cookies
assert.equals(urlState, cookieState, "State does not match")
assert(code, "No code given")
const tokens = await slack.validateAuthorizationCode(code);
const accessToken = tokens.accessToken();
const idToken = tokens.idToken();

// now you can store the access and id tokens wherever you need them

// and redirect to your `page`
res.redirect(new URL('/slack-done', config.frontendUrl).toString())
}
And then in your frontend you'd have a link such as:
import { config } from 'wasp/client'

const installSlackHref = new URL("/slack/auth", config.apiUrl).toString()
export const InstallSlackLink = () => {
return <a href={installSlackHref}>Install Slack</a>
}
import { config } from 'wasp/client'

const installSlackHref = new URL("/slack/auth", config.apiUrl).toString()
export const InstallSlackLink = () => {
return <a href={installSlackHref}>Install Slack</a>
}
Is this useful, does it work? I typed it directly on Discord so it might have some minor typos but that's the general idea, let me know.
miho
miho2w ago
Here's an example for adding the Spotify OAuth method (just to give you some extra code examples to work with): https://gist.github.com/infomiho/3c63de7d53aba59d6293bcb59501a029#gistcomment-5372273 (not 100% your use case, but it might help to see how we did stuff in this case) Check the basics as well: - are you editing stuff locally and maybe you didn't deploy them? - are you creating client routes and expecting them as server routes? - make sure you are using the correct port when testing 3000 is the client, 3001 is the server
Gist
Implementing custom OAuth provider with Wasp 0.14.1+ (Spotify in th...
Implementing custom OAuth provider with Wasp 0.14.1+ (Spotify in this case) - .env.server

Did you find this page helpful?