Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Discussion options

When you're using integrations in the current version of Trigger.dev, you need to be the owner of the associated accounts. For example, you can use our GitHub integration using your GitHub personal access token or login to your account with OAuth in our dashboard.

This is great for a lot of use cases, but sometimes you want to be authenticated with services as your users. Some examples:

  • Send Slack notifications to your users when events happen in your app
  • Subscribe to changes in one of your users Airtable bases that syncs items to your platform

Basically you want to add integrations to your product.

What would the developer experience be?

Authenticating your users with APIs

  1. You would install one of our UI packages in your app (e.g. @trigger.dev/react)
  2. In your app you'd add a screen where you want your users to authenticate with an API
  3. When your user clicks "Airtable" you would invoke our UI, this would collect the required info and complete the authentication flow.

The exact structure of the React components is TBD.

import {
  AuthenticationFlow,
  AuthenticationTrigger,
  AuthenticationPanel,
  AuthenticationForm,
} from "@trigger.dev/react";

function YourPage() {
  //this would come from your backend in real life
  const userId = "123";
  return (
    <div>
      <AirtableLogin userId={userId} />
    </div>
  );
}

//you would create this component in your app
//this is a basic example, better to have a component that supports all integrations
function AirtableLogin({ userId }: { userId: string }) {
  return (
    <AuthenticationFlow integration="airtable" id="airtable" accountId={userId}>
      <AuthenticationTrigger className="rounded-sm bg-slate-200 p-2">
        Sign in with Airtable
      </AuthenticationTrigger>
      <AuthenticationPanel className="border-slate-200 bg-white p-4">
        <h1 className="text-2xl font-bold">Sign in with Airtable</h1>
        <AuthenticationForm
          className="flex flex-col gap-4"
          inputClassName="bg-white border border-slate-200 text-sm"
          labelClassName="text-slate-500 text-sm"
        />
      </AuthenticationPanel>
    </AuthenticationFlow>
  );
}

For API keys this would show a form. OAuth would take them through the OAuth flow. The UI would be "headless", meaning it would have no styles so you can style it how you wish. We'll provide some examples to make it easier to get started. Some APIs require extra information, like Shopify which needs a Shop URL. We'll automatically render these in the form and collect the required data.

Writing Jobs

They would be very similar to normal Jobs.

When you declare an integration, you'd add the connect option:

const slack = new Slack({
  //this can be anything you want, it's just a unique identifier
  id: "slack-customers",
  //this is new for Connect
  connect: true,
});

When a Job is run the authentication details get injected immediately before run() is called. This is how it currently works for OAuth (as the underlying access tokens get refreshed so change over time). Currently for API keys, we never get sent them. But for integrations with connect: true we would store your user's API keys, like we do OAuth tokens. It's worth noting that all credentials are encrypted at rest, and all endpoints use HTTPS.

You write Jobs exactly like before. Integrations with connect enabled would be associated with the user's account. Also you'll be able to get the accountId inside the Job.

client.defineJob({
  id: "slack-customer-notification",
  name: "Slack Customer Notification",
  version: "1.0.0",
  trigger: eventTrigger({
    name: "slack.notification",
    schema: z.object({
      channel: z.string(),
      message: z.string(),
    }),
  }),
  integrations: {
    slack,
  },
  run: async (payload, io, ctx) => {
    //this will post a message to your user's Slack channel
    const message = await io.slack.postMessage("Slack message", {
      channel: payload.channel,
      text: payload.message,
    });

    //details about the account are available in the context
    io.logger.log(`User id: ${ctx.account?.id}`);
  },
});

Triggering Jobs

This would work slightly differently, as runs need to be associated with a user.

eventTrigger (sendEvent)

This sendEvent would trigger the Job above, and cause a run as your user (with userId "123").

client.sendEvent(
  {
    name: "slack.notification",
    payload: {
      channel: "C01J9JZQZ9N",
      message: "Hello World",
    }
  },
  {
    accountId: "123",
  }
);

Webhooks

You'll need to register your user somewhere for the webhook. Then we'll subscribe to them for you.

//the job
const github = new Github({
  id: "github-customers",
  connect: true,
});

const dynamicOnIssueOpenedTrigger = new DynamicTrigger(client, {
  id: "github-issue-opened",
  event: events.onIssueOpened,
  source: github.sources.repo,
});

client.defineJob({
  id: "listen-for-dynamic-trigger",
  name: "Listen for dynamic trigger",
  version: "0.1.1",
  trigger: dynamicOnIssueOpenedTrigger,
  integrations: { github },
  run: async (payload, io, ctx) => {
    //do stuff
  },
});

Somewhere else in your code:

//the first param should be a unique ID for this particular registration of the Job
await dynamicOnIssueOpenedTrigger.register(`${userId}-${repo}`, { owner, repo }, {
    accountId: "123",
});

You could also register for this dynamic trigger in another job.

Using authenticated clients outside of Jobs

You'll be able to use the authenticated clients outside of Jobs. This is useful when you want to show UI in your app. In our example above, we need to allow the user to select their preferred Slack channel to receive notifications.

//some file in your backend, outside of a Job
import { client } from "@/trigger";
import { WebClient } from "@slack/web-api";

//in real life the accountId would come from your app
const auth = await client.getAuth({ id: "slack-customers", accountId: "123" });

//The Slack integration only supports OAuth
if (auth.type !== "oauth2") throw new Error("Expected OAuth2 auth");

//create the normal Slack SDK using the access token
const slackClient = new WebClient(auth.accessToken);
const result = await client.conversations.list();

What changes to Trigger.dev are required to support this?

  • The UI components need to be added to each of UI packages. We'd start with React.
  • API endpoints need to be created to support the authentication flow and accept the data..
  • Storing API Keys for Connect.
  • Create a public getAuth method on the client.
  • Integration's cloneForRun method needs support for API keys from the backend.
  • Expanding on our dynamicTriggers to support subscribing to webhooks as your users.
You must be logged in to vote

Replies: 3 comments

Comment options

A related piece of work is a change to how OAuth integrations are added to your Jobs. Currently you can either add the code first then connect, or connect and then write the code.

I suggest we make it so the only way to connect using OAuth is to add the code first, then follow the steps in the dashboard to do the auth flow. This will simplify things but critically it will unify things for the Connect flows above.

You must be logged in to vote
0 replies
Comment options

"...Work will start on it in a month or two..."
"The UI components need to be added to each of UI packages. We'd start with React"
Do you have any plans for work on other frameworks (I'm interested in Vue)?

You must be logged in to vote
0 replies
Comment options

Initial support for being able to make requests using your users auth has been shipped as "Bring your own Auth":

https://trigger.dev/docs/documentation/guides/using-integrations-byo-auth

You must be logged in to vote
0 replies
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Category
💡
Ideas
Labels
None yet
3 participants
Morty Proxy This is a proxified and sanitized view of the page, visit original site.