DEV Community

Victoria for PropelAuth

Posted on • Originally published at propelauth.com

Building Custom UIs with Shadcn and PropelAuth's Integration MCP Server

With our recent release of the PropelAuth Integration MCP server, we wanted to put it plus our shadcn component registry to the test by building some of the most unique and …interesting components to replace PropelAuth’s hosted pages.

The Integration MCP server not only helps you get up and running with PropelAuth as quickly as possible, it can also help you build custom versions of your login and signup pages, such as this one:

Login page

Or maybe you’re building an app aimed at GenZ and, like me, you’re an out of touch Millennial. Our MCP server plus your favorite AI agent can assist you in building out whatever this is?

Login page

I know what you’re thinking - you would never trust a website that asks for your “Vibe Identifier” and “Secret Sauce” to authenticate. But since we know PropelAuth is doing all the work behind the scenes, we can trust that it’s safe and secure. Let’s get started!

Installing the PropelAuth Integration MCP Server

Let’s start by connecting the PropelAuth Integration MCP Server to your favorite AI agent. In this example we’ll be using Claude Desktop. If you’re using a different tool check out our installation docs here.

In the Claude Desktop app, click on the + icon → Connectors → Manage connectors:

Manage connectors menu in Claude

In the next menu, click on the + icon again followed by Add custom connector. When prompted, name the connector “PropelAuth” and enter “https://mcp.propelauth.com/mcp” as the URL.

Add custom connector menu

And you’re set! You can now start building your custom UI components.

Building Login Pages

This guide assumes that you have already integrated PropelAuth into your project. If you haven’t, check out our getting started guide here.

With the PropelAuth Integration server, creating your own custom login and signup pages could not be easier. First, let’s disable the hosted login and signup pages by navigating to the PropelAuth Dashboard, clicking on Look & Feel, followed by Build your own UI.

Let’s disable the Sign Up and Log In pages and set the signup redirect to {YOUR_APP_URL}[?signup=true](<http://localhost:5173/?signup=true>). This will make it so PropelAuth functions such as redirectToSignupPage still redirect to our custom signup page.

Build your own UI page

Now we can get to building! Using the PropelAuth Integration MCP server with your AI Agent, simply make a prompt such as this one to have it start building your login and signup pages:

PropelAuth, build a signup and login page that uses email and password login and signup, magic links, and Enterprise SSO. Use styling to match the rest of my project.

If the MCP server is setup correctly you should see a prompt like this one asking for permission to query the Integration server. Depending on your prompt you’ll also see a few more for each login method you specified.

Allow Claude to PropelAuth Custom UI

Each response from the Integration server will return documentation and instructions to your agent on how to properly setup PropelAuth’s frontend APIs in your project, such as installing the @propelauth/frontend-apis-react library and creating a login context provider.

When Claude is done you should have a working login and signup page!

Login page

This is a great start, but we’re not done just yet. While this will handle the first part of the login and signup process for your users, there are some other pages that we need to build out to handle email confirmations, MFA, and more.

Drop In Components with Shadcn

In the previous step, Claude was instructed to build a LoginStateManager component. Results may vary, but yours should look something like this:

import { LoginState } from '@propelauth/frontend-apis'
import { useLoginContext } from '../hooks/useLoginContext'
import { LoginContextProvider } from '../contexts/LoginContext'
import LoginAndSignup from './LoginAndSignup'

const LoginElementByState = () => {
    const { loginState, isLoading, error } = useLoginContext()

    if (isLoading) {
        return <div>Loading...</div>
    } else if (error || !loginState) {
        return <div>{error ?? "An unexpected error has occurred"}</div>
    }

    switch (loginState) {
        case LoginState.LOGIN_REQUIRED:
            return <LoginAndSignup />
        case LoginState.USER_MISSING_REQUIRED_PROPERTIES:
            return <div>Update User Properties (Not Implemented)</div>
        case LoginState.EMAIL_NOT_CONFIRMED_YET:
            return <div>Please confirm your email.</div>
        case LoginState.UPDATE_PASSWORD_REQUIRED:
            return <div>Update Password (Not Implemented)</div>
        case LoginState.USER_MUST_BE_IN_AT_LEAST_ONE_ORG:
            return <div>Join or Create Organization (Not Implemented)</div>
        case LoginState.TWO_FACTOR_ENROLLMENT_REQUIRED:
            return <div>Enroll in 2FA (Not Implemented)</div>
        case LoginState.TWO_FACTOR_REQUIRED:
            return <div>Verify 2FA (Not Implemented)</div>
        case LoginState.LOGGED_IN:
            window.location.reload()
            return null
    }
}

const LoginStateManager = () => {
    return (
        <LoginContextProvider>
            <LoginElementByState />
        </LoginContextProvider>
    )
}

export default LoginStateManager

Enter fullscreen mode Exit fullscreen mode

The LoginStateManager handles (you guessed it) the user’s login state. Does the user need to login? Redirect them to the login and signup page. Is the user’s email not confirmed? Show them the email not confirmed page. Has the user finished the login process? Redirect them to your app.

So far we have only addressed the LOGIN_REQUIRED and LOGGED_IN states. We could continue prompting Claude to generate these components for us, but let’s go a slightly different route and use PropelAuth’s shadcn component registry instead. This registry allows us to drop pre-made components directly into your app, meaning AI doesn’t have to get involved (until we ask it to update the styling for us).

Installing shadcn

Start by following the shadcn installation instructions to set up shadcn in your app. When you’re done, run this in your terminal to initialize shadcn:

npx shadcn@latest init
Enter fullscreen mode Exit fullscreen mode

Importing Components

With shadcn, all we have to do to install the necessary components to handle the remaining login states is run the following command in the terminal:

npx shadcn@latest add \\
  <https://components.propelauth.com/r/request-password-reset.json> \\
  <https://components.propelauth.com/r/confirm-your-email.json> \\
  <https://components.propelauth.com/r/join-an-organization.json> \\
  <https://components.propelauth.com/r/enroll-in-mfa.json> \\
  <https://components.propelauth.com/r/verify-mfa-for-login.json> \\
  <https://components.propelauth.com/r/update-user-property.json>
Enter fullscreen mode Exit fullscreen mode

With these new components installed, simply add them to each login state case, like so:

import { LoginState } from '@propelauth/frontend-apis'
import { useLoginContext } from '../hooks/useLoginContext'
import { LoginContextProvider } from '../contexts/LoginContext'
import LoginAndSignup from './LoginAndSignup'
import UpdateUserProperty from './update-user-property/update-user-property'
import ConfirmYourEmail from './confirm-your-email/confirm-your-email'
import UpdateUserPassword from './update-user-password/update-user-password'
import JoinAnOrganization from './join-an-organization/join-an-organization'
import EnrollInMfa from './enroll-in-mfa/enroll-in-mfa'
import VerifyMfaForLogin from './verify-mfa-for-login/verify-mfa-for-login'

const LoginElementByState = () => {
    const { loginState, isLoading, error } = useLoginContext()

    if (isLoading) {
        return <div>Loading...</div>
    } else if (error || !loginState) {
        return <div>{error ?? "An unexpected error has occurred"}</div>
    }

    switch (loginState) {
        case LoginState.LOGIN_REQUIRED:
            return <LoginAndSignup />
        case LoginState.USER_MISSING_REQUIRED_PROPERTIES:
            return <UpdateUserProperty />
        case LoginState.EMAIL_NOT_CONFIRMED_YET:
            return <ConfirmYourEmail />
        case LoginState.UPDATE_PASSWORD_REQUIRED:
            return <UpdateUserPassword />
        case LoginState.USER_MUST_BE_IN_AT_LEAST_ONE_ORG:
            return <JoinAnOrganization />
        case LoginState.TWO_FACTOR_ENROLLMENT_REQUIRED:
            return <EnrollInMfa />
        case LoginState.TWO_FACTOR_REQUIRED:
            return <VerifyMfaForLogin />
        case LoginState.LOGGED_IN:
            window.location.reload()
            return null
    }
}

const LoginStateManager = () => {
    return (
        <LoginContextProvider>
            <LoginElementByState />
        </LoginContextProvider>
    )
}

export default LoginStateManager
Enter fullscreen mode Exit fullscreen mode

But hey, these components don’t match the styling of the rest of our project! Let’s move back to using Claude to help out, starting with the EnrollInMfa component. This component will render when a user is required to setup MFA before they can access your app.

I for one actually enjoyed the GenZ styling I had going earlier. Let’s prompt Claude to update the component with the same styling…but to add a few fun tricks to make it extra annoying.

Update the new EnrollInMfa component to have the same GenZ styling we created earlier. But add some creative ways to make the UX of the component as annoying and difficult as possible

After waiting a few moments, let’s see what we have…

Gif of a GenZ login page

Well, I guess I deserved that.

Wrapping Up

Building custom auth flows doesn't have to mean starting from scratch. With the PropelAuth Integration MCP server handling the heavy lifting on the backend, shadcn's component registry giving you pre-built building blocks, and AI helping you style everything to match your vision (GenZ aesthetic or otherwise), you can go from hosted pages to a fully custom login experience in a surprisingly short amount of time.

Whether you're building something polished and professional or something that asks users for their "Vibe Identifier," PropelAuth ensures the authentication itself remains secure and reliable no matter how chaotic the UI gets. Now go build something weird.

Top comments (0)