● Framer Hacks

How to Password Protect Individual Pages on Framer

How to Password Protect Individual Pages on Framer

How to Password Protect Individual Pages on Framer

How to Password Protect Individual Pages on Framer

How to Password Protect Individual Pages on Framer

How to Password Protect Individual Pages on Framer

Framer doesn't have a built-in way to password protect individual pages, but this tutorial has you covered with a simple solution to lock things down.

Password protect specific pages on Framer, keeping others public

Customize text, password, and style using included design tokens

Handle and display error messages for incorrect passwords

Prevent content access through browser inspector mode

Published

Jun 21, 2024

Published

Jun 21, 2024

Published

Jun 21, 2024

Published

Jun 21, 2024

Published

Jun 21, 2024

Published

Jun 21, 2024

⚠️ Important

The password protection on this page uses client-side verification. While it provides a basic level of access control, please be aware that:

  1. This method is not foolproof and can potentially be bypassed by tech savvy visitors.

  2. The password may be visible in browser developer tools.

Recommendations:

  • Use a sensible password that you're comfortable being potentially exposed

  • This protection is best suited for casual access control, not for securing highly sensitive information.

  • For high-security needs, server-side authentication is strongly recommended.

Instructions

Instructions

Instructions

Instructions

Instructions

Create Code Override

Create Code Override

Create Code Override

Create Code Override

Create Code Override

1
1
1
1
1
1
Open your Framer project

Note: At this stage, you don't need to navigate to any specific page within the project yet. We'll be setting up the code override first, which can later be applied globally to individual pages.

2
2
2
2
2
2
Add a new code override and give it a name.
  1. On the left sidebar pane, Go to "Assets" tab

  2. Click (+) to add a new code file

  3. Name the code file Auth.tsx

  4. Select file type as "New Override"

  5. Click "Create"

  6. Delete the sample code in the new file

3
3
3
3
3
3
Copy and paste the provided code into the Auth.tsx file.
import React, { useState, useEffect, useRef } from "react"
import type { ComponentType } from "react"
import { addPropertyControls, ControlType } from "framer"
import {
    Eye,
    EyeSlash,
    LockSimple,
    LockSimpleOpen,
    ArrowUUpLeft,
} from "phosphor-react"

// ===== CUSTOMIZATION SECTION =====
// Edit this section to customize the password protection

// Add or remove passwords here (case-sensitive)
const ALLOWED_PASSWORDS = ["ABCD", "abcd", "Abcd"]

// Customize the text content here
const TEXT_CONTENT = {
    title: "PRIVATE CONTENT",
    subtitle: "Enter passcode to continue (passcode: abcd)",
    errorMessage: "Incorrect passcode. Please try again.",
    buttonText: "Unlock",
    returnButtonText: "Return to Projects",
}

// Change this URL to where you want users to return when clicking the link to return
const RETURN_URL = "https://www.google.com"

// Customize the design here
const STYLE_TOKENS = {
    colors: {
        background: "#FFFFFF", // Background Color
        text: "#000000", // Main text color
        primary: "#000000", // Color for buttons and important elements
        secondaryText: "#605D64", // Color for less important text
        buttonText: "#FFFFFF", // Color for the button text
        error: "#D8512A", // Color for error messages and error input border
        inputBorder: "#CCCCCC", // Default border color for input fields
        inputBorderFocus: "#000000", // Border color when input is focused
        inputBorderError: "#D8512A", // Border color when there's an error
        buttonHover: "#333333", // Button color on hover
        linkHover: "#333333", // Home link color on hover
    },
    fonts: {
        heading: "Syncopate, sans-serif", // Font for headings
        body: "General Sans, sans-serif", // Font for body text
        button: "General Sans, sans-serif", // Font for button text
    },
    fontSizes: {
        title: "clamp(1.25rem, 1.586vw + 1.151rem, 2rem)", // Responsive title size
        paragraph: "18px",
        input: "16px",
        button: "16px",
        small: "14px",
    },
    fontWeights: {
        normal: "500",
        medium: "600",
        bold: "800",
    },
    spacing: {
        xs: "4px",
        sm: "8px",
        md: "12px",
        lg: "24px",
        xl: "32px",
        xxl: "48px",
    },
    borderRadius: {
        small: "8px",
    },
    container: {
        maxWidth: "400px",
    },
}
// ===== END OF CUSTOMIZATION SECTION =====

const styles = {
    container: {
        display: "flex",
        flexDirection: "column",
        alignItems: "center",
        justifyContent: "center",
        minHeight: "100vh",
        backgroundColor: STYLE_TOKENS.colors.background,
        fontFamily: STYLE_TOKENS.fonts.body,
        color: STYLE_TOKENS.colors.text,
        padding: STYLE_TOKENS.spacing.md,
    },
    header: {
        marginBottom: STYLE_TOKENS.spacing.lg,
        textAlign: "center",
    },
    title: {
        fontSize: STYLE_TOKENS.fontSizes.title,
        fontWeight: STYLE_TOKENS.fontWeights.bold,
        fontFamily: STYLE_TOKENS.fonts.heading,
        lineHeight: "125%",
        marginBottom: STYLE_TOKENS.spacing.xs,
    },
    subtitle: {
        fontFamily: STYLE_TOKENS.fonts.body,
        fontWeight: STYLE_TOKENS.fontWeights.normal,
        fontSize: STYLE_TOKENS.fontSizes.paragraph,
        lineHeight: "150%",
        margin: "0px",
        color: STYLE_TOKENS.colors.secondaryText,
    },
    form: {
        width: "100%",
        maxWidth: STYLE_TOKENS.container.maxWidth,
    },
    inputContainer: {
        position: "relative",
    },
    input: {
        width: "100%",
        fontFamily: STYLE_TOKENS.fonts.body,
        fontSize: STYLE_TOKENS.fontSizes.paragraph,
        fontWeight: STYLE_TOKENS.fontWeights.normal,
        paddingTop: STYLE_TOKENS.spacing.md,
        paddingBottom: STYLE_TOKENS.spacing.md,
        paddingLeft: STYLE_TOKENS.spacing.md,
        paddingRight: STYLE_TOKENS.spacing.xxl,
        fontSize: STYLE_TOKENS.fontSizes.input,
        border: `1px solid ${STYLE_TOKENS.colors.inputBorder}`,
        borderRadius: STYLE_TOKENS.borderRadius.small,
        outline: "none",
        transition: "border-color 0.3s",
        WebkitAppearance: "none",
        MozAppearance: "none",
        appearance: "none",
    },
    inputError: {
        borderColor: STYLE_TOKENS.colors.inputBorderError,
    },
    showPasswordButton: {
        position: "absolute",
        right: STYLE_TOKENS.spacing.md,
        top: "50%",
        transform: "translateY(-50%)",
        background: "none",
        border: "none",
        cursor: "pointer",
        padding: "0",
    },
    button: {
        width: "100%",
        padding: STYLE_TOKENS.spacing.md,
        marginTop: STYLE_TOKENS.spacing.lg,
        fontFamily: STYLE_TOKENS.fonts.button,
        fontSize: STYLE_TOKENS.fontSizes.button,
        fontWeight: STYLE_TOKENS.fontWeights.medium,
        color: STYLE_TOKENS.colors.buttonText,
        backgroundColor: STYLE_TOKENS.colors.primary,
        border: "none",
        borderRadius: STYLE_TOKENS.borderRadius.small,
        cursor: "pointer",
        transition: "background-color 0.3s",
        display: "flex",
        alignItems: "center",
        justifyContent: "center",
    },
    buttonHovered: {
        backgroundColor: STYLE_TOKENS.colors.buttonHover,
    },
    error: {
        color: STYLE_TOKENS.colors.error,
        marginTop: STYLE_TOKENS.spacing.md,
        fontSize: STYLE_TOKENS.fontSizes.small,
    },
    returnLink: {
        marginTop: STYLE_TOKENS.spacing.xxl,
        color: STYLE_TOKENS.colors.text,
        textDecoration: "none",
        fontSize: STYLE_TOKENS.fontSizes.small,
        display: "flex",
        alignItems: "center",
    },
    returnLinkHovered: {
        color: STYLE_TOKENS.colors.linkHover,
    },
}

export function requireAuth(Component): ComponentType {
    return (props) => {
        const [authenticated, setAuthenticated] = useState(false)
        const [showPassword, setShowPassword] = useState(false)
        const [errorMessage, setErrorMessage] = useState("")
        const passwordRef = useRef(null)
        const [loading, setLoading] = useState(true)
        const [hovered, setHovered] = useState(false)

        const validateAuth = (e) => {
            e.preventDefault() // Prevent default form submission behavior
            const inputPassword = e.target.elements.password.value

            if (ALLOWED_PASSWORDS.includes(inputPassword)) {
                setAuthenticated(true) // Set authenticated state to true
                setErrorMessage("") // Clear any existing error message
            } else {
                setAuthenticated(false) // Ensure authenticated state is false
                setErrorMessage(TEXT_CONTENT.errorMessage) // Set error message for invalid password
            }

            // Optionally clear the password field after submission
            e.target.elements.password.value = ""
        }

        useEffect(() => {
            if (passwordRef.current) {
                passwordRef.current.focus()
            }
            setTimeout(() => setLoading(false), 1000)
        }, [])

        if (!authenticated) {
            return (
                <div style={styles.container}>
                    <div style={styles.header}>
                        <h1 style={styles.title}>{TEXT_CONTENT.title}</h1>
                        <p style={styles.subtitle}>{TEXT_CONTENT.subtitle}</p>
                    </div>
                    <form onSubmit={validateAuth} style={styles.form}>
                        <div style={styles.inputContainer}>
                            <input
                                type={showPassword ? "text" : "password"}
                                name="password"
                                ref={passwordRef}
                                style={{
                                    ...styles.input,
                                    ...(errorMessage ? styles.inputError : {}),
                                }}
                                onFocus={(e) =>
                                    (e.target.style.borderColor =
                                        STYLE_TOKENS.colors.inputBorderFocus)
                                }
                                onBlur={(e) =>
                                    (e.target.style.borderColor = errorMessage
                                        ? STYLE_TOKENS.colors.inputBorderError
                                        : STYLE_TOKENS.colors.inputBorder)
                                }
                                autoComplete="new-password"
                            />
                            <button
                                type="button"
                                onClick={() => setShowPassword(!showPassword)}
                                style={styles.showPasswordButton}
                            >
                                {showPassword ? (
                                    <EyeSlash
                                        size={20}
                                        color={
                                            STYLE_TOKENS.colors.secondaryText
                                        }
                                    />
                                ) : (
                                    <Eye
                                        size={20}
                                        color={
                                            STYLE_TOKENS.colors.secondaryText
                                        }
                                    />
                                )}
                            </button>
                        </div>
                        {errorMessage && (
                            <p style={styles.error}>{errorMessage}</p>
                        )}
                        <button
                            type="submit"
                            style={{
                                ...styles.button,
                                ...(hovered ? styles.buttonHovered : {}),
                            }}
                            onMouseEnter={() => setHovered(true)}
                            onMouseLeave={() => setHovered(false)}
                        >
                            {hovered ? (
                                <LockSimpleOpen size={20} />
                            ) : (
                                <LockSimple size={20} />
                            )}
                            <span
                                style={{
                                    marginLeft: STYLE_TOKENS.spacing.sm,
                                }}
                            >
                                {TEXT_CONTENT.buttonText}
                            </span>
                        </button>
                    </form>
                    <a
                        href={RETURN_URL}
                        style={styles.returnLink}
                        onMouseEnter={(e) =>
                            (e.currentTarget.style.color =
                                STYLE_TOKENS.colors.linkHover)
                        }
                        onMouseOut={(e) =>
                            (e.currentTarget.style.color =
                                STYLE_TOKENS.colors.text)
                        }
                    >
                        <ArrowUUpLeft
                            size={16}
                            style={{ marginRight: STYLE_TOKENS.spacing.sm }}
                        />
                        {TEXT_CONTENT.returnButtonText}
                    </a>
                </div>
            )
        }

        return <Component {...props} />
    }
}

// Add property controls (if needed)
addPropertyControls(requireAuth, {
    // Add your property controls here
})

4
4
4
4
4
4
Personalize the design and copy

Change the values of the design tokens: ALLOWED_PASSWORDS, TEXT_CONTENT, RETURN_URL, STYLE_TOKENS to fit your brand.

5
5
5
5
5
5
Save code override file

Press CMD + S on Mac or CTRL + S on Windows to save

Apply Password Protection

Apply Password Protection

Apply Password Protection

Apply Password Protection

Apply Password Protection

6
6
6
6
6
6
Apply password protection:
  1. Go to the specific page you want to password protect

  2. Select the Primary Frame on the page

  3. In the right sidebar, find the "Code Overrides" section

  4. From the "File" dropdown, choose "Auth.tsx"

  5. From the "Override" dropdown, select "requireAuth"

7
7
7
7
7
7
Test your password protection:
  1. Preview the page

  2. You should now see the password protection in action

Conclusion

Conclusion

Conclusion

Conclusion

Conclusion

And that's a wrap! You've successfully added password protection to your Framer page.

This powerful feature allows you to keep sensitive content secure while maintaining a seamless user experience. Remember, you can customize the look and feel of your password protection screen to match your brand perfectly. Now you can confidently share your work with clients or potential employers, knowing that your private content remains protected.

Happy designing!

Like or Share:

© 2024 Ying Chen. All rights reserved.

Made in San Francisco, CA

© 2024 Ying Chen. All rights reserved.

Made in San Francisco, CA

© 2024 Ying Chen. All rights reserved.

Made in San Francisco, CA

© 2024 Ying Chen. All rights reserved.

Made in San Francisco, CA

© 2024 Ying Chen. All rights reserved.

Made in San Francisco, CA

© 2024 Ying Chen. All rights reserved.

Made in San Francisco, CA