Speckle Auth Izipizi

Hey guys,

I built an NPM package to make the process of authenticating apps with Speckle easier. It handles the OAuth process and I tried it hard to make it as simple as possible. I’d appreciate if you could give it a spin and write me any feedback you have.

Here’s a link:

import { SpeckleAuthClient, type ApplicationOptions } from 'speckle-auth';

const options: ApplicationOptions = {
  clientId: 'your-client-id',
  clientSecret: 'your-client-secret',
  serverUrl: 'https://app.speckle.systems',
};
const speckle = new SpeckleAuthClient(options);

async function authenticateUser() {
  const user = await speckle.user();
  if (!user) {
    await speckle.login();
  }
  return user;
}

async function logoutUser() {
  await speckle.logout();
}
7 Likes

Feb-06-2025 18-02-43

6 Likes

MVP!! :spockle_joy:

2 Likes

Vue has changed soooooo much since I last used it!

1 Like

wow that was quick!

1 Like

@vwb thank you for publishing this handy package :slight_smile: I’ve been having some trouble understanding to properly handle the login process.

When the redirect returns it seems to get stuck with trying to access the challenge code passed back in the url. I’ve been fiddling with the redirect endpoint and have gotten some mixed results, sometimes the auth process works as expected :tada: but then it will break without any change to the config or app. I have a haunch that this is more of a problem with how I should be handling the redirect, or maybe in how await client.login() should be handled within a react-app, but since my understanding of oauth is questionable at best, I figured I would seek help from the high council of specklers.

Here is an example setup using a test app. I also isolate the script and logs below since stackblitz seems to throw some errors with the post call.

example
import { useEffect } from 'react';
import { SpeckleAuthClient } from 'speckle-auth';

async function authenticate(
  client: SpeckleAuthClient,
  redirect: boolean,
) {
  console.log('authing...');
  const user = await client.user();

  if (!user && redirect) {
    console.log('logging in user');
    await client.login();
  }
  console.log('user value', user);
}

function App() {
  console.log('app start');
  const client = new SpeckleAuthClient({
    serverUrl: 'https://app.speckle.systems',
    clientId: '1e4e05c0ed',
    clientSecret: '👀',
  });

  const handleLogout = async () => {
    console.log('logging out...');
    await client.logout();
  };

  const handleLogin = async () => {
    console.log('logging in...');
    await authenticate(client, true);
  };

  useEffect(() => {
    console.log('first check for user');
    authenticate(client, false);
  }, []);

  return (
    <div>
      <button onClick={handleLogin}>login</button>
      <button onClick={handleLogout}>logout</button>
    </div>
  );
}

export default App;
logs after redirect
app start
first check for user
authing...
user value null 

Hey @haitheredavid, thanks for giving it a spin. My react knowledge is questionable, but could you try this sample?

import { useEffect, useState } from 'react';
import './App.css';

import { SpeckleAuthClient, type User } from '../../../';

const client = new SpeckleAuthClient({
  serverUrl: 'https://app.speckle.systems',
  clientId: import.meta.env.VITE_SPECKLE_CLIENT_ID,
  clientSecret: import.meta.env.VITE_SPECKLE_CLIENT_SECRET,
});

function App() {
  console.log('app start');

  const [userInfo, setUserInfo] = useState<User | null>(null);

  const handleLogout = () => {
    console.log('logging out...');
    client.logout(); // No need to await
    setUserInfo(null);
  };

  const handleLogin = async () => {
    console.log('manual login triggered');
    const user = await client.login();
    if (user) setUserInfo(user);
  };

  useEffect(() => {
    const authenticate = async () => {
      console.log('checking for existing session...');
      const user = await client.user();
      if (user) {
        setUserInfo(user);
      } else {
        // Only trigger login if no user is found
        const loginResult = await client.login();
        if (loginResult) setUserInfo(loginResult);
      }
    };

    authenticate();
    // Only run once on mount
  }, []);

  return (
    <div>
      <div>{userInfo ? `Hello, ${userInfo.name}` : 'Please log in'}</div>
      <button onClick={handleLogin}>login</button>
      <button onClick={handleLogout}>logout</button>
    </div>
  );
}

export default App;

I have added a full example here as well: speckle-auth/examples/react at master · vwnd/speckle-auth · GitHub

2 Likes

@vwb appreciate the quick reply + example! I see what I was doing wrong, the client object needs be moved out of the app block. :melting_face:

One thing that should be changed is wrapping the first auth check in useCallback since an async method in useEffect will cause some issues when mounted. Without it I was getting POST .... /auth/token/ 401 on the redirect.

I made that change and threw a pr your way.

thanks again :folded_hands:

2 Likes

Awesome @haitheredavid! Merged quickly last friday and forgot to comeback to thank you :smiley:

2 Likes