> ## Documentation Index
> Fetch the complete documentation index at: https://translations.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

> Este tutorial valida el token de acceso con la gema jwt en una clase Auth0Client personalizada.

# Agrega autorización a tu API de Ruby on Rails

export const AuthCodeGroup = ({children, dropdown}) => {
  const [processedChildren, setProcessedChildren] = useState(children);
  useEffect(() => {
    let unsubscribe = null;
    function init() {
      unsubscribe = window.autorun(() => {
        const processChildren = node => {
          if (typeof node === "string") {
            let processedNode = node;
            for (const [key, value] of window.rootStore.variableStore.values.entries()) {
              const escapedKey = key.replaceAll(/[.*+?^${}()|[\]\\]/g, (String.raw)`\$&`);
              processedNode = processedNode.replaceAll(new RegExp(escapedKey, "g"), value);
            }
            return processedNode;
          } else if (Array.isArray(node)) {
            return node.map(processChildren);
          } else if (node && node.props && node.props.children) {
            return {
              ...node,
              props: {
                ...node.props,
                children: processChildren(node.props.children)
              }
            };
          }
          return node;
        };
        setProcessedChildren(processChildren(children));
      });
    }
    if (window.rootStore) {
      init();
    } else {
      window.addEventListener("adu:storeReady", init);
    }
    return () => {
      window.removeEventListener("adu:storeReady", init);
      unsubscribe?.();
    };
  }, [children]);
  return <CodeGroup dropdown={dropdown}>{processedChildren}</CodeGroup>;
};

export const QuickstartButtons = ({githubLink}) => {
  const text = {
    viewOnGithub: "Ver en GitHub",
    loginAndDownload: "Descargar ejemplo"
  };
  const parseGithubUrl = url => {
    try {
      const urlObj = new URL(url);
      const pathParts = urlObj.pathname.split("/").filter(Boolean);
      if (pathParts.length >= 4 && pathParts[2] === "tree") {
        const repoName = pathParts[1];
        const branch = pathParts[3];
        const path = pathParts.slice(4).join("/") || undefined;
        return {
          repo: repoName,
          branch,
          path
        };
      }
      console.warn("No se pudo analizar la URL de GitHub:", url);
      return null;
    } catch (error) {
      console.error("Error al analizar la URL de GitHub:", error);
      return null;
    }
  };
  const handleDownload = async () => {
    const params = parseGithubUrl(githubLink);
    if (!params) {
      console.error("Formato de URL de GitHub no válido");
      return;
    }
    try {
      await window.Auth0DocsUI?.getSample(params);
    } catch (error) {
      console.error("No se pudo descargar el ejemplo:", error);
    }
  };
  return <div className="quickstart_buttons flex flex-wrap gap-3 mb-4">
      <a href={githubLink} target="_blank" rel="noopener noreferrer" className="no_external_icon quickstart_button inline-flex items-center justify-center px-6 py-3 text-sm font-medium rounded-[18px] bg-black dark:bg-white !text-white dark:!text-black hover:bg-gray-800 dark:hover:bg-gray-100 transition-colors">
        {text.viewOnGithub}
      </a>
      <button onClick={handleDownload} type="button" className="no_external_icon quickstart_button inline-flex items-center justify-center px-6 py-3 text-sm font-medium rounded-[18px] border border-gray-300 dark:border-[#454545] bg-white dark:bg-[#272728] !text-black dark:!text-white hover:bg-gray-50 dark:hover:bg-neutral-800 transition-colors">
        {text.loginAndDownload}
      </button>
    </div>;
};

export const LoggedInForm = ({sampleApp}) => {
  const LS_APPS_KEY = "auth_demo_apps";
  const LS_APP_CFG_KEY = "auth_demo_app_cfg";
  const CHANNEL = "auth_flows_sync_v1";
  const mkChannel = () => new BroadcastChannel(CHANNEL);
  function uid() {
    return Math.random().toString(36).slice(2) + Date.now().toString(36);
  }
  function loadApps() {
    const raw = localStorage.getItem(LS_APPS_KEY);
    if (raw) return JSON.parse(raw);
    const seeded = [{
      id: "{yourClientId}",
      name: "Default App"
    }];
    localStorage.setItem(LS_APPS_KEY, JSON.stringify(seeded));
    return seeded;
  }
  function saveApps(apps) {
    localStorage.setItem(LS_APPS_KEY, JSON.stringify(apps));
  }
  function loadCfg() {
    const raw = localStorage.getItem(LS_APP_CFG_KEY);
    return raw ? JSON.parse(raw) : {};
  }
  function saveCfg(cfg) {
    localStorage.setItem(LS_APP_CFG_KEY, JSON.stringify(cfg));
  }
  const RightChevron = ({className = "w-5 h-5", ...props}) => <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke="currentColor" fill="none" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className} {...props}>
      <polyline points="9 18 15 12 9 6" />
    </svg>;
  const LightningIcon = () => <svg width="24" height="24" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
      <path fillRule="evenodd" clipRule="evenodd" className="fill-[#3F59E4] dark:fill-[#99A7F1]" d="M24.971 30.152H7.088c-1.786 0-2.745-2.103-1.574-3.453l19.07-21.988c1.33-1.532 3.835-.4 3.569 1.607L24.97 30.152z" />
      <path fillRule="evenodd" clipRule="evenodd" className="fill-[#3F59E4] dark:fill-[#99A7F1]" d="M23.201 17.885h17.885c1.787 0 2.746 2.102 1.575 3.453l-19.073 21.99c-1.33 1.532-3.835.4-3.568-1.607L23.2 17.885z" />
    </svg>;
  const LayersIcon = () => <svg width="24" height="24" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
      <path className="fill-[#3F59E4] dark:fill-[#99A7F1]" d="M34.54 29.135l6.373 3.183c1.566.782 1.566 3.017 0 3.8l-14.815 7.396a4.623 4.623 0 01-4.125 0L7.174 36.12c-1.565-.782-1.565-3.017 0-3.798l6.532-3.214" />
      <path className="fill-[#AAB6F3] dark:fill-[#3449BA]" d="M34.54 18.86l6.373 3.183c1.566.782 1.566 3.016 0 3.8L26.098 33.24a4.623 4.623 0 01-4.125 0L7.174 25.843c-1.565-.781-1.565-3.016 0-3.798l6.33-3.164" />
      <path className="fill-[#CFD6F8] dark:fill-[#22307C]" d="M21.94 23.058L7.306 15.745c-1.62-.81-1.62-3.123 0-3.932l14.631-7.319a4.693 4.693 0 014.194 0l14.648 7.319c1.622.81 1.62 3.124 0 3.932L26.13 23.058c-1.321.66-2.873.66-4.191 0z" />
    </svg>;
  const GithubIcon = () => <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="w-5 h-5">
      <path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path>
    </svg>;
  function IconTile({children}) {
    return <div className="
          shrink-0 grid place-items-center w-10 h-10 rounded-lg
          bg-indigo-50 ring-1 ring-indigo-200/60
          dark:bg-indigo-950/40 dark:ring-white/10
        ">
        {children}
      </div>;
  }
  function Card({className = "", children}) {
    return <div className={`rounded-2xl shadow-sm ring-1 ring-zinc-200 dark:ring-zinc-800 ${className}`}>{children}</div>;
  }
  function Button({variant = "primary", type = "button", onClick, children}) {
    const base = "inline-flex items-center justify-center gap-2 h-10 px-4 rounded-xl font-medium transition";
    let styles = "";
    if (variant === "primary") {
      styles = "mint-bg-indigo-600 text-white hover:mint-bg-indigo-700";
    } else if (variant === "outline") {
      styles = "border border-zinc-300 dark:border-zinc-700 mint-bg-transparent hover:mint-bg-zinc-50 dark:hover:mint-bg-zinc-800";
    } else if (variant === "ghost") {
      styles = "hover:mint-bg-zinc-100 dark:hover:mint-bg-zinc-800";
    }
    return <button type={type} onClick={onClick} className={`${base} ${styles}`}>
        {children}
      </button>;
  }
  function Input({id, label, value, onChange, placeholder, name}) {
    return <label className="block space-y-1">
        <span className="text-sm text-zinc-700 dark:text-zinc-300">{label}</span>
        <input id={id} name={name} className="w-full h-11 px-3 rounded-xl border border-zinc-300 dark:border-zinc-700 bg-white dark:bg-zinc-900 text-zinc-900 dark:text-zinc-100 focus:outline-none focus:ring-2 focus:ring-indigo-500" placeholder={placeholder} value={value} onChange={e => onChange(e.target.value)} />
      </label>;
  }
  function Select({label, value, onChange, options}) {
    return <label className="block space-y-1 max-w-[300px]">
        <span className="text-sm text-zinc-700 dark:text-zinc-300">{label}</span>
        <div className="relative">
          <select className="w-full h-11 appearance-none px-3 pr-9 rounded-xl border border-zinc-300 dark:border-zinc-700 bg-white dark:bg-zinc-900 text-zinc-900 dark:text-zinc-100 focus:outline-none focus:ring-2 focus:ring-indigo-500" value={value} onChange={e => onChange(e.target.value)}>
            <optgroup label="Generic Applications">
              {options.map(o => <option key={o.id} value={o.id}>
                  {o.name}
                </option>)}
            </optgroup>
          </select>
          <svg className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 w-5 h-5 text-zinc-500" viewBox="0 0 24 24">
            <path d="M7 10l5 5 5-5z" fill="currentColor" />
          </svg>
        </div>
      </label>;
  }
  function Toast({open, onClose, children}) {
    useEffect(() => {
      if (!open) return;
      const t = setTimeout(onClose, 2200);
      return () => clearTimeout(t);
    }, [open, onClose]);
    return <div className={`fixed right-4 top-4 z-50 transition ${open ? "opacity-100 translate-y-0" : "opacity-0 -translate-y-2 pointer-events-none"}`}>
        <div className="flex items-center gap-2 rounded-xl shadow ring-1 ring-emerald-200 bg-white dark:bg-zinc-900 px-4 py-2">
          <span className="w-1.5 h-8 rounded-l bg-emerald-500" />
          <svg className="w-5 h-5 text-emerald-600" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
            <path d="M20 6L9 17l-5-5" />
          </svg>
          <span className="text-sm text-zinc-900 dark:text-zinc-100">{children}</span>
        </div>
      </div>;
  }
  function Flows() {
    const [route, setRoute] = useState("menu");
    const [apps, setApps] = useState(loadApps());
    const [cfg, setCfg] = useState(loadCfg());
    const [selected, setSelected] = useState(apps[0]?.id || "");
    const [toast, setToast] = useState(false);
    const [bc] = useState(() => mkChannel());
    useEffect(() => {
      if (!apps.find(a => a.id === selected)) {
        setSelected(apps[0]?.id || "");
      }
    }, [apps, selected]);
    useEffect(() => {
      const onMsg = e => {
        const {type, payload} = e.data || ({});
        switch (type) {
          case "NAV":
            setRoute(payload.route);
            break;
          case "SELECT":
            setSelected(payload.appId);
            break;
          case "APPS_UPDATED":
            setApps(loadApps());
            break;
          case "CFG_UPDATED":
            setCfg(loadCfg());
            setToast(true);
            break;
          default:
            break;
        }
      };
      bc.addEventListener("message", onMsg);
      return () => bc.removeEventListener("message", onMsg);
    }, [bc]);
    const nav = nextRoute => {
      setRoute(nextRoute);
      bc.postMessage({
        type: "NAV",
        payload: {
          route: nextRoute
        }
      });
    };
    const selectApp = appId => {
      setSelected(appId);
      bc.postMessage({
        type: "SELECT",
        payload: {
          appId
        }
      });
    };
    const onCreate = name => {
      const id = uid();
      const next = [...apps, {
        id,
        name: name || "Untitled"
      }];
      setApps(next);
      saveApps(next);
      bc.postMessage({
        type: "APPS_UPDATED"
      });
      selectApp(id);
      nav("integrate");
    };
    const onSaveCfg = (appId, data) => {
      const next = {
        ...cfg,
        [appId]: data
      };
      setCfg(next);
      saveCfg(next);
      setToast(true);
      bc.postMessage({
        type: "CFG_UPDATED"
      });
    };
    return <div>
        {route === "menu" && <Menu onCreate={() => nav("create")} onIntegrate={() => nav("integrate")} />}

        {route === "create" && <CreateForm onCancel={() => nav("menu")} onSave={onCreate} />}

        {route === "integrate" && <IntegrateForm apps={apps} selected={selected} onSelect={selectApp} saved={cfg[selected]} onSave={data => onSaveCfg(selected, data)} onCancel={() => nav("menu")} />}

        <Toast open={toast} onClose={() => setToast(false)}>
          Successfully saved your changes.
        </Toast>
      </div>;
  }
  function Menu({onCreate, onIntegrate}) {
    return <ul className="space-y-4 list-none login_list">
        <li className="list-none !px-0">
          <button onClick={onCreate} className="w-full text-left">
            <Card className="p-5 hover:shadow-md transition">
              <div className="flex items-center justify-between">
                <div className="flex items-center gap-4">
                  <IconTile>
                    <LightningIcon />
                  </IconTile>
                  <h2 className="text-lg">Create a new application</h2>
                </div>
                <RightChevron className="w-4 h-4 text-zinc-500" />
              </div>
            </Card>
          </button>
        </li>
        <li className="list-none !px-0">
          <button onClick={onIntegrate} className="w-full text-left">
            <Card className="p-5 hover:shadow-md transition">
              <div className="flex items-center justify-between">
                <div className="flex items-center gap-4">
                  <IconTile>
                    <LayersIcon />
                  </IconTile>
                  <h2 className="text-lg">Integrate with an existing application</h2>
                </div>
                <RightChevron className="w-4 h-4 text-zinc-500" />
              </div>
            </Card>
          </button>
        </li>
        <li className="list-none !px-0">
          <a className="no_external_icon block" href={sampleApp ? sampleApp : "/"} target="_blank" rel="noreferrer">
            <Card className="p-5 hover:shadow-md transition">
              <div className="flex items-center justify-between">
                <div className="flex items-center gap-4">
                  <IconTile>
                    <GithubIcon />
                  </IconTile>
                  <h2 className="text-lg">View a sample application</h2>
                </div>
                <RightChevron className="w-4 h-4 text-zinc-500" />
              </div>
            </Card>
          </a>
        </li>
      </ul>;
  }
  function CreateForm({onSave, onCancel}) {
    const [name, setName] = useState("");
    return <div className="space-y-6">
        <Input id="app-name" label="Application Name" placeholder="My App" value={name} onChange={setName} />
        <p className="text-sm text-zinc-500">You can change this later in the application settings.</p>
        <div className="flex gap-3">
          <Button onClick={() => onSave(name)}>Guardar</Button>
          <Button variant="outline" onClick={onCancel}>
            Cancelar
          </Button>
        </div>
      </div>;
  }
  function IntegrateForm({apps, selected, onSelect, saved, onSave, onCancel}) {
    const [callbacks, setCallbacks] = useState(saved?.callbacks ?? "");
    const [logouts, setLogouts] = useState(saved?.logouts ?? "");
    const [origins, setOrigins] = useState(saved?.origins ?? "");
    useEffect(() => {
      setCallbacks(loadCfg()[selected]?.callbacks ?? "");
      setLogouts(loadCfg()[selected]?.logouts ?? "");
      setOrigins(loadCfg()[selected]?.origins ?? "");
    }, [selected]);
    return <div className="space-y-6">
        <div>
          <span className="block text-sm text-zinc-600 dark:text-zinc-300 mb-1">Selecciona tu aplicación</span>
          <Select label="" value={selected} onChange={onSelect} options={apps} />
        </div>

        <form className="space-y-4" onSubmit={e => {
      e.preventDefault();
      onSave({
        callbacks,
        logouts,
        origins
      });
    }}>
          <Input id="callbacks" name="callbacks" label="URLs de callback" placeholder="http://localhost:3000" value={callbacks} onChange={setCallbacks} />
          <Input id="logout" name="allowed_logout_urls" label="URLs de cierre de sesión" placeholder="http://localhost:3000" value={logouts} onChange={setLogouts} />
          <Input id="origins" name="web_origins" label="Orígenes web permitidos" placeholder="http://localhost:3000" value={origins} onChange={setOrigins} />

          <div className="flex gap-3 pt-2">
            <Button type="submit">Guardar</Button>
            <Button variant="outline" type="button" onClick={onCancel}>
              Cancelar
            </Button>
          </div>
        </form>
      </div>;
  }
  return <div className="w-full mx-auto py-8">
      <Flows />
    </div>;
};

export const SignUpForm = () => {
  const [isAuthenticated, setIsAuthenticated] = useState(false);
  const [storeReady, setStoreReady] = useState(false);
  useEffect(() => {
    let unsubscribe = null;
    function init() {
      setStoreReady(true);
      unsubscribe = window.autorun(() => {
        const authenticated = window.rootStore?.sessionStore?.isAuthenticated || false;
        setIsAuthenticated(authenticated);
      });
    }
    if (window.rootStore) {
      init();
    } else {
      window.addEventListener("adu:storeReady", init);
    }
    return () => {
      window.removeEventListener("adu:storeReady", init);
      unsubscribe?.();
    };
  }, []);
  function LoggedInForm({sampleApp}) {
    const LS_APPS_KEY = "auth_demo_apps";
    const LS_APP_CFG_KEY = "auth_demo_app_cfg";
    const CHANNEL = "auth_flows_sync_v1";
    const mkChannel = () => new BroadcastChannel(CHANNEL);
    function uid() {
      return Math.random().toString(36).slice(2) + Date.now().toString(36);
    }
    function loadApps() {
      const raw = localStorage.getItem(LS_APPS_KEY);
      if (raw) return JSON.parse(raw);
      const seeded = [{
        id: "{yourClientId}",
        name: "Aplicación predeterminada"
      }];
      localStorage.setItem(LS_APPS_KEY, JSON.stringify(seeded));
      return seeded;
    }
    function saveApps(apps) {
      localStorage.setItem(LS_APPS_KEY, JSON.stringify(apps));
    }
    function loadCfg() {
      const raw = localStorage.getItem(LS_APP_CFG_KEY);
      return raw ? JSON.parse(raw) : {};
    }
    function saveCfg(cfg) {
      localStorage.setItem(LS_APP_CFG_KEY, JSON.stringify(cfg));
    }
    const RightChevron = ({className = "w-5 h-5", ...props}) => <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke="currentColor" fill="none" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className} {...props}>
        <polyline points="9 18 15 12 9 6" />
      </svg>;
    const LightningIcon = () => <svg width="24" height="24" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
        <path fillRule="evenodd" clipRule="evenodd" className="fill-[#3F59E4] dark:fill-[#99A7F1]" d="M24.971 30.152H7.088c-1.786 0-2.745-2.103-1.574-3.453l19.07-21.988c1.33-1.532 3.835-.4 3.569 1.607L24.97 30.152z" />
        <path fillRule="evenodd" clipRule="evenodd" className="fill-[#3F59E4] dark:fill-[#99A7F1]" d="M23.201 17.885h17.885c1.787 0 2.746 2.102 1.575 3.453l-19.073 21.99c-1.33 1.532-3.835.4-3.568-1.607L23.2 17.885z" />
      </svg>;
    const LayersIcon = () => <svg width="24" height="24" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
        <path className="fill-[#3F59E4] dark:fill-[#99A7F1]" d="M34.54 29.135l6.373 3.183c1.566.782 1.566 3.017 0 3.8l-14.815 7.396a4.623 4.623 0 01-4.125 0L7.174 36.12c-1.565-.782-1.565-3.017 0-3.798l6.532-3.214" />
        <path className="fill-[#AAB6F3] dark:fill-[#3449BA]" d="M34.54 18.86l6.373 3.183c1.566.782 1.566 3.016 0 3.8L26.098 33.24a4.623 4.623 0 01-4.125 0L7.174 25.843c-1.565-.781-1.565-3.016 0-3.798l6.33-3.164" />
        <path className="fill-[#CFD6F8] dark:fill-[#22307C]" d="M21.94 23.058L7.306 15.745c-1.62-.81-1.62-3.123 0-3.932l14.631-7.319a4.693 4.693 0 014.194 0l14.648 7.319c1.622.81 1.62 3.124 0 3.932L26.13 23.058c-1.321.66-2.873.66-4.191 0z" />
      </svg>;
    const GithubIcon = () => <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="w-5 h-5">
        <path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path>
      </svg>;
    function IconTile({children}) {
      return <div className="
            shrink-0 grid place-items-center w-10 h-10 rounded-lg
            bg-indigo-50 ring-1 ring-indigo-200/60
            dark:bg-indigo-950/40 dark:ring-white/10
          ">
          {children}
        </div>;
    }
    function Card({className = "", children}) {
      return <div className={`rounded-2xl shadow-sm ring-1 ring-zinc-200 dark:ring-zinc-800 ${className}`}>{children}</div>;
    }
    function Button({variant = "primary", type = "button", onClick, children}) {
      const base = "inline-flex items-center justify-center gap-2 h-10 px-4 rounded-xl font-medium transition";
      let styles = "";
      if (variant === "primary") {
        styles = "mint-bg-indigo-600 text-white hover:mint-bg-indigo-700";
      } else if (variant === "outline") {
        styles = "border border-zinc-300 dark:border-zinc-700 mint-bg-transparent hover:mint-bg-zinc-50 dark:hover:mint-bg-zinc-800";
      } else if (variant === "ghost") {
        styles = "hover:mint-bg-zinc-100 dark:hover:mint-bg-zinc-800";
      }
      return <button type={type} onClick={onClick} className={`${base} ${styles}`}>
          {children}
        </button>;
    }
    function Input({id, label, value, onChange, placeholder, name}) {
      return <label className="block space-y-1">
          <span className="text-sm text-zinc-700 dark:text-zinc-300">{label}</span>
          <input id={id} name={name} className="w-full h-11 px-3 rounded-xl border border-zinc-300 dark:border-zinc-700 bg-white dark:bg-zinc-900 text-zinc-900 dark:text-zinc-100 focus:outline-none focus:ring-2 focus:ring-indigo-500" placeholder={placeholder} value={value} onChange={e => onChange(e.target.value)} />
        </label>;
    }
    function Select({label, value, onChange, options}) {
      return <label className="block space-y-1 max-w-[300px]">
          <span className="text-sm text-zinc-700 dark:text-zinc-300">{label}</span>
          <div className="relative">
            <select className="w-full h-11 appearance-none px-3 pr-9 rounded-xl border border-zinc-300 dark:border-zinc-700 bg-white dark:bg-zinc-900 text-zinc-900 dark:text-zinc-100 focus:outline-none focus:ring-2 focus:ring-indigo-500" value={value} onChange={e => onChange(e.target.value)}>
              <optgroup label="Generic Applications">
                {options.map(o => <option key={o.id} value={o.id}>
                    {o.name}
                  </option>)}
              </optgroup>
            </select>
            <svg className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 w-5 h-5 text-zinc-500" viewBox="0 0 24 24">
              <path d="M7 10l5 5 5-5z" fill="currentColor" />
            </svg>
          </div>
        </label>;
    }
    function Toast({open, onClose, children}) {
      useEffect(() => {
        if (!open) return;
        const t = setTimeout(onClose, 2200);
        return () => clearTimeout(t);
      }, [open, onClose]);
      return <div className={`fixed right-4 top-4 z-50 transition ${open ? "opacity-100 translate-y-0" : "opacity-0 -translate-y-2 pointer-events-none"}`}>
          <div className="flex items-center gap-2 rounded-xl shadow ring-1 ring-emerald-200 bg-white dark:bg-zinc-900 px-4 py-2">
            <span className="w-1.5 h-8 rounded-l bg-emerald-500" />
            <svg className="w-5 h-5 text-emerald-600" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
              <path d="M20 6L9 17l-5-5" />
            </svg>
            <span className="text-sm text-zinc-900 dark:text-zinc-100">{children}</span>
          </div>
        </div>;
    }
    function Flows() {
      const [route, setRoute] = useState("menu");
      const [apps, setApps] = useState(loadApps());
      const [cfg, setCfg] = useState(loadCfg());
      const [selected, setSelected] = useState(apps[0]?.id || "");
      const [toast, setToast] = useState(false);
      const [bc] = useState(() => mkChannel());
      useEffect(() => {
        if (!apps.find(a => a.id === selected)) {
          setSelected(apps[0]?.id || "");
        }
      }, [apps, selected]);
      useEffect(() => {
        const onMsg = e => {
          const {type, payload} = e.data || ({});
          switch (type) {
            case "NAV":
              setRoute(payload.route);
              break;
            case "SELECT":
              setSelected(payload.appId);
              break;
            case "APPS_UPDATED":
              setApps(loadApps());
              break;
            case "CFG_UPDATED":
              setCfg(loadCfg());
              setToast(true);
              break;
            default:
              break;
          }
        };
        bc.addEventListener("message", onMsg);
        return () => bc.removeEventListener("message", onMsg);
      }, [bc]);
      const nav = nextRoute => {
        setRoute(nextRoute);
        bc.postMessage({
          type: "NAV",
          payload: {
            route: nextRoute
          }
        });
      };
      const selectApp = appId => {
        setSelected(appId);
        bc.postMessage({
          type: "SELECT",
          payload: {
            appId
          }
        });
      };
      const onCreate = name => {
        const id = uid();
        const next = [...apps, {
          id,
          name: name || "Sin título"
        }];
        setApps(next);
        saveApps(next);
        bc.postMessage({
          type: "APPS_UPDATED"
        });
        selectApp(id);
        nav("integrate");
      };
      const onSaveCfg = (appId, data) => {
        const next = {
          ...cfg,
          [appId]: data
        };
        setCfg(next);
        saveCfg(next);
        setToast(true);
        bc.postMessage({
          type: "CFG_UPDATED"
        });
      };
      return <div>
          {route === "menu" && <Menu onCreate={() => nav("create")} onIntegrate={() => nav("integrate")} />}

          {route === "create" && <CreateForm onCancel={() => nav("menu")} onSave={onCreate} />}

          {route === "integrate" && <IntegrateForm apps={apps} selected={selected} onSelect={selectApp} saved={cfg[selected]} onSave={data => onSaveCfg(selected, data)} onCancel={() => nav("menu")} />}

          <Toast open={toast} onClose={() => setToast(false)}>
            Successfully saved your changes.
          </Toast>
        </div>;
    }
    function Menu({onCreate, onIntegrate}) {
      return <ul className="space-y-4 list-none login_list">
          <li className="list-none !px-0">
            <button onClick={onCreate} className="w-full text-left">
              <Card className="p-5 hover:shadow-md transition">
                <div className="flex items-center justify-between">
                  <div className="flex items-center gap-4">
                    <IconTile>
                      <LightningIcon />
                    </IconTile>
                    <h2 className="text-lg">Crear una nueva aplicación</h2>
                  </div>
                  <RightChevron className="w-4 h-4 text-zinc-500" />
                </div>
              </Card>
            </button>
          </li>
          <li className="list-none !px-0">
            <button onClick={onIntegrate} className="w-full text-left">
              <Card className="p-5 hover:shadow-md transition">
                <div className="flex items-center justify-between">
                  <div className="flex items-center gap-4">
                    <IconTile>
                      <LayersIcon />
                    </IconTile>
                    <h2 className="text-lg">Integrar con una aplicación existente</h2>
                  </div>
                  <RightChevron className="w-4 h-4 text-zinc-500" />
                </div>
              </Card>
            </button>
          </li>
          <li className="list-none !px-0">
            <a className="no_external_icon block" href={sampleApp ? sampleApp : "/"} target="_blank" rel="noreferrer">
              <Card className="p-5 hover:shadow-md transition">
                <div className="flex items-center justify-between">
                  <div className="flex items-center gap-4">
                    <IconTile>
                      <GithubIcon />
                    </IconTile>
                    <h2 className="text-lg">Ver una aplicación de ejemplo</h2>
                  </div>
                  <RightChevron className="w-4 h-4 text-zinc-500" />
                </div>
              </Card>
            </a>
          </li>
        </ul>;
    }
    function CreateForm({onSave, onCancel}) {
      const [name, setName] = useState("");
      return <div className="space-y-6">
          <Input id="app-name" label="Nombre de la aplicación" placeholder="Mi aplicación" value={name} onChange={setName} />
          <p className="text-sm text-zinc-500">Puedes cambiarlo más adelante en la configuración de la aplicación.</p>
          <div className="flex gap-3">
            <Button onClick={() => onSave(name)}>Guardar</Button>
            <Button variant="outline" onClick={onCancel}>
              Cancelar
            </Button>
          </div>
        </div>;
    }
    function IntegrateForm({apps, selected, onSelect, saved, onSave, onCancel}) {
      const [callbacks, setCallbacks] = useState(saved?.callbacks ?? "");
      const [logouts, setLogouts] = useState(saved?.logouts ?? "");
      const [origins, setOrigins] = useState(saved?.origins ?? "");
      useEffect(() => {
        setCallbacks(loadCfg()[selected]?.callbacks ?? "");
        setLogouts(loadCfg()[selected]?.logouts ?? "");
        setOrigins(loadCfg()[selected]?.origins ?? "");
      }, [selected]);
      return <div className="space-y-6">
          <div>
            <span className="block text-sm text-zinc-600 dark:text-zinc-300 mb-1">Selecciona tu aplicación</span>
            <Select label="" value={selected} onChange={onSelect} options={apps} />
          </div>

          <form className="space-y-4" onSubmit={e => {
        e.preventDefault();
        onSave({
          callbacks,
          logouts,
          origins
        });
      }}>
            <Input id="callbacks" name="callbacks" label="URLs de callback" placeholder="http://localhost:3000" value={callbacks} onChange={setCallbacks} />
            <Input id="logout" name="allowed_logout_urls" label="URLs de cierre de sesión" placeholder="http://localhost:3000" value={logouts} onChange={setLogouts} />
            <Input id="origins" name="web_origins" label="Orígenes web permitidos" placeholder="http://localhost:3000" value={origins} onChange={setOrigins} />

            <div className="flex gap-3 pt-2">
              <Button type="submit">Guardar</Button>
              <Button variant="outline" type="button" onClick={onCancel}>
                Cancelar
              </Button>
            </div>
          </form>
        </div>;
    }
    return <div className="w-full mx-auto py-8">
        <Flows />
      </div>;
  }
  function SignUpFormInternal() {
    return <div className="flex flex-col gap-2 items-center h-full">
        <img noZoom src="/docs/img/quickstarts/action_hero_dashboard.svg" alt="Regístrate para obtener una cuenta de Auth0" style={{
      width: "250px",
      height: "250px"
    }} />
        <span className="text-center" style={{
      width: "400px"
    }}>
          Regístrate para obtener una{" "}
          <a href="https://auth0.com/signup" target="_blank" rel="noopener noreferrer">
            cuenta de Auth0
          </a>{" "}
          o{" "}
          <span className="font-semibold text-primary dark:text-white cursor-pointer" onClick={() => console.log("log in")}>
            inicia sesión
          </span>{" "}
          en tu cuenta existente para integrarte directamente con tu propio tenant.
        </span>
        <button onClick={() => console.log("sign up")} className="bg-primary dark:bg-primary-light text-white dark:text-black px-4 py-2 rounded-md mt-4 font-medium" style={{
      width: "140px"
    }}>
          Regístrate
        </button>
      </div>;
  }
  return <></>;
};

export const SideMenuSectionItem = ({id, children}) => {
  return <div id={`side-menu-item-${id}`} className="recipe-side-menu-item flex flex-col w-full h-full">
      {children}
    </div>;
};

export const SideMenu = ({sections, children}) => {
  const [visibleSection, setVisibleSection] = useState(sections[0]?.id ?? null);
  const checkVisibility = () => {
    let currentVisible = null;
    const viewportHeight = window.innerHeight;
    const scrollY = window.scrollY;
    sections.forEach(({id}) => {
      const section = document.getElementById(id);
      if (section) {
        const rect = section.getBoundingClientRect();
        const sectionTop = rect.top + scrollY;
        const sectionBottom = sectionTop + rect.height;
        const multiplier = viewportHeight > 1600 ? 0.34 : 0.22;
        if (scrollY + viewportHeight * multiplier >= sectionTop && scrollY <= sectionBottom) {
          currentVisible = id;
        }
      }
    });
    if (currentVisible && currentVisible !== visibleSection) {
      setVisibleSection(currentVisible);
    }
  };
  useEffect(() => {
    const throttledCheck = () => {
      setTimeout(checkVisibility, 100);
    };
    checkVisibility();
    window.addEventListener("scroll", throttledCheck);
    return () => {
      window.removeEventListener("scroll", throttledCheck);
    };
  }, [sections, visibleSection]);
  useEffect(() => {
    sections.forEach(({id}) => {
      const section = document.getElementById(id);
      const sideMenuItem = document.getElementById(`side-menu-item-${id}`);
      if (section) {
        if (id === visibleSection) {
          section.classList.add("active-section");
        } else {
          section.classList.remove("active-section");
        }
      }
      if (sideMenuItem) {
        if (id === visibleSection) {
          sideMenuItem.classList.add("active-side-menu-item");
        } else {
          sideMenuItem.classList.remove("active-side-menu-item");
        }
      }
    });
  }, [visibleSection, sections]);
  return <div className="recipe-side-menu sticky px-2 py-1" style={{
    height: "calc(100vh - 7rem)",
    top: "7rem",
    scrollMarginTop: "var(--scroll-mt)"
  }}>
      {children.map(child => {
    if (child.props.id === visibleSection) {
      return child;
    }
    return null;
  })}
    </div>;
};

export const Section = ({id, title, stepNumber, children, isSingleColumn = false}) => {
  return <div id={id} className={`recipe-section flex flex-col transition-opacity duration-200`}>
      {}
      <Step title={title} stepNumber={stepNumber} titleSize="h3">
        {children}
      </Step>
    </div>;
};

export const Content = ({title, children}) => {
  return <div className="recipe-content flex flex-col">
      {title && <h1 className="text-3xl">{title}</h1>}
      {children}
    </div>;
};

export const Recipe = ({children, isSingleColumn = false}) => {
  return <div className={`pl-4 recipe-container mx-auto grid grid-cols-1 gap-10 relative ${isSingleColumn ? "md:grid-cols-1" : "md:grid-cols-2"}`}>
      {children}
    </div>;
};

<QuickstartButtons githubLink="https://github.com/auth0-samples/auth0-rubyonrails-api-samples/tree/master/01-Authentication-RS256" />

export const sections = [{
  id: "define-permissions",
  title: "Definir permisos"
}, {
  id: "install-dependencies",
  title: "Instalar las dependencias"
}, {
  id: "create-an-auth0client-class",
  title: "Crear la clase Auth0Client"
}, {
  id: "define-a-secured-concern",
  title: "Definir el concern Secured"
}, {
  id: "include-the-secure-concern-in-your-applicationcontroller",
  title: "Incluir el concern Secure en ApplicationController"
}, {
  id: "create-the-public-endpoint",
  title: "Crear el endpoint público"
}, {
  id: "create-the-private-endpoints",
  title: "Crear los endpoints privados"
}];

<Recipe>
  <Content>
    Este tutorial realiza la validación del token de acceso mediante la gema [jwt](https://github.com/jwt/ruby-jwt) dentro de una clase `Auth0Client` personalizada. Se utiliza un Concern llamado
    `Secured` para autorizar los endpoints que requieren autenticación mediante un token de acceso entrante.

    Si aún no has creado una API en tu Auth0 Dashboard, usa el selector interactivo para crear una nueva API de Auth0
    o selecciona una API existente para tu proyecto.

    Para configurar su primera API a través del Auth0 Dashboard, consulte [nuestra guía de introducción](https://auth0.com/docs/get-started/auth0-overview/set-up-apis).

    Cada API de Auth0 utiliza el identificador de API, que tu aplicación necesita para validar el token de acceso.

    <Info>
      **¿Eres nuevo en Auth0?** Aprende [cómo funciona Auth0](https://auth0.com/docs/overview)
      y lee sobre [cómo implementar la autenticación
      y autorización de API](https://auth0.com/docs/api-auth) con el marco OAuth 2.0.
    </Info>

    <Section id={sections[0].id} title={sections[0].title} stepNumber="1">
      Los permisos le permiten definir cómo se puede acceder a los recursos en nombre del usuario con un token de acceso determinado. Por
      ejemplo, puede conceder acceso de lectura al recurso `messages` si los usuarios tienen el nivel de acceso de gerente,
      y acceso de escritura a ese recurso si tienen el nivel de acceso de administrador.

      Puede definir los permisos permitidos en la vista **Permissions** de la sección [APIs](https://manage.auth0.com/#/apis) de Auth0 Dashboard.

      <Frame>
        <img src="https://mintcdn.com/translations/eVsQcTnbClN-oB7d/docs/images/cdy7uua7fh8z/1s3Yp5zqJiKiSWqbPSezNO/e61793a2822d095666002c3f65c71ac2/configure-permissions.png?fit=max&auto=format&n=eVsQcTnbClN-oB7d&q=85&s=99d1ad19f9c16ddbf99302447b61800a" width="1078" height="679" data-path="docs/images/cdy7uua7fh8z/1s3Yp5zqJiKiSWqbPSezNO/e61793a2822d095666002c3f65c71ac2/configure-permissions.png" />
      </Frame>

      <Info>
        Este ejemplo usa el scope `read:messages`.
      </Info>
    </Section>

    <Section id={sections[1].id} title={sections[1].title} stepNumber="2">
      Instala la \*\*gem jwt \*\*.

      ```bash lines theme={null}
      gem 'jwt'
      bundle install
      ```
    </Section>

    <Section id={sections[2].id} title={sections[2].title} stepNumber="3">
      Cree una clase llamada `Auth0Client`. Esta clase decodifica y verifica el token de acceso entrante obtenido
      del encabezado `Authorization` de la solicitud.

      La clase `Auth0Client` obtiene la clave pública de su inquilino de Auth0 y luego la usa para verificar la
      firma del token de acceso. La estructura `Token` define un método `validate_permissions` para
      buscar un `scope` específico en un token de acceso al proporcionar un array de alcances requeridos y comprobar si
      están presentes en la carga útil del token.
    </Section>

    <Section id={sections[3].id} title={sections[3].title} stepNumber="4">
      Cree un Concern llamado `Secured` que busque el token de acceso en el encabezado `Authorization`
      de una solicitud recibida.

      Si el token está presente, `Auth0Client.validate_token` usará la gema `jwt` para verificar
      la firma del token y validar sus claims.

      Además de verificar que el token de acceso sea válido, el Concern también incluye un mecanismo para confirmar que el
      token tiene el **scope** suficiente para acceder a los recursos solicitados. En este ejemplo, definimos un
      método `validate_permissions` que recibe un bloque y comprueba los permisos llamando al
      método `Token.validate_permissions` de la clase `Auth0Client`.

      Para la ruta `/private-scoped`, los alcances definidos se cruzarán con los alcances incluidos en la
      carga útil para determinar si contiene uno o más elementos del otro arreglo.
    </Section>

    <Section id={sections[4].id} title={sections[4].title} stepNumber="5">
      Al agregar el concern `Secure` al controlador de tu aplicación, solo tendrás que usar un
      filtro `before_action` en el controlador que requiera autorización.
    </Section>

    <Section id={sections[5].id} title={sections[5].title} stepNumber="6">
      Cree un controlador para manejar el endpoint público `/api/public`.

      El endpoint `/public` no requiere autorización, por lo que no hace falta `before_action`.
    </Section>

    <Section id={sections[6].id} title={sections[6].title} stepNumber="7">
      Crea un controlador para manejar los endpoints privados: `/api/private` y
      `/api/private-scoped`.

      `/api/private` está disponible para solicitudes autenticadas que contengan un token de acceso sin
      alcances adicionales.

      `/api/private-scoped` está disponible para solicitudes autenticadas que contengan un token de acceso con el scope
      `read:messages` concedido

      Los endpoints protegidos necesitan llamar al método `authorize` del concern `Secured`. Para ello, se usa `before_action :authorize`, lo que garantiza que el método `Secured.authorize` se llame antes de cada acción en el `PrivateController`.

      ### Realizar una llamada a tu API

      Para realizar llamadas a tu API, necesitas un Token de acceso. Puedes obtener un Token de acceso para pruebas desde la vista
      **Test** en la [configuración de tu
      API](https://manage.auth0.com/#/apis).

      <Frame>
        <img src="https://mintcdn.com/translations/6GE5Z24GDCZehiJ9/docs/images/cdy7uua7fh8z/6jeVBuypOGX5qMRXeJn5ow/5e79037f6c852d2043789d622bdb9562/Quickstart_Example_App_-_English.png?fit=max&auto=format&n=6GE5Z24GDCZehiJ9&q=85&s=c04195662abaa324608c5656a651ac37" width="1002" height="807" data-path="docs/images/cdy7uua7fh8z/6jeVBuypOGX5qMRXeJn5ow/5e79037f6c852d2043789d622bdb9562/Quickstart_Example_App_-_English.png" />
      </Frame>

      Proporciona el Token de acceso como encabezado `Authorization` en tus solicitudes.

      <AuthCodeGroup>
        ```bash cURL lines theme={null}
        curl --request get \
        --url 'http:///%7ByourDomain%7D/api_path' \
        --header 'authorization: Bearer YOUR_ACCESS_TOKEN_HERE'
        ```

        ```csharp C# lines theme={null}
        var client = new RestClient("http:///%7ByourDomain%7D/api_path");
        var request = new RestRequest(Method.GET);
        request.AddHeader("authorization", "Bearer YOUR_ACCESS_TOKEN_HERE");
        IRestResponse response = client.Execute(request);
        ```

        ```go Go lines theme={null}
        package main
        import (
        "fmt"
        "net/http"
        "io/ioutil"
        )
        func main() {
        url := "http:///%7ByourDomain%7D/api_path"
        req, _ := http.NewRequest("get", url, nil)
        req.Header.Add("authorization", "Bearer YOUR_ACCESS_TOKEN_HERE")
        res, _ := http.DefaultClient.Do(req)
        defer res.Body.Close()
        body, _ := ioutil.ReadAll(res.Body)
        fmt.Println(res)
        fmt.Println(string(body))
        }
        ```

        ```java Java lines theme={null}
        HttpResponse<String> response = Unirest.get("http:///%7ByourDomain%7D/api_path")
        .header("authorization", "Bearer YOUR_ACCESS_TOKEN_HERE")
        .asString();
        ```

        ```javascript Node.JS lines theme={null}
        var axios = require("axios").default;
        var options = {
        method: 'get',
        url: 'http:///%7ByourDomain%7D/api_path',
        headers: {authorization: 'Bearer YOUR_ACCESS_TOKEN_HERE'}
        };
        axios.request(options).then(function (response) {
        console.log(response.data);
        }).catch(function (error) {
        console.error(error);
        });
        ```

        ```objc Obj-C lines theme={null}
        #import <Foundation/Foundation.h>
        NSDictionary *headers = @{ @"authorization": @"Bearer YOUR_ACCESS_TOKEN_HERE" };
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http:///%7ByourDomain%7D/api_path"]
                                                        cachePolicy:NSURLRequestUseProtocolCachePolicy

                                                    timeoutInterval:10.0];

        [request setHTTPMethod:@"get"];
        [request setAllHTTPHeaderFields:headers];
        NSURLSession *session = [NSURLSession sharedSession];
        NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
                                                completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

                                                    if (error) {

                                                        NSLog(@&quot;%@&quot;, error);

                                                    } else {

                                                        NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;

                                                        NSLog(@&quot;%@&quot;, httpResponse);

                                                    }

                                                }];

        [dataTask resume];
        ```

        ```php PHP lines theme={null}
        #import <Foundation/Foundation.h>
        NSDictionary *headers = @{ @"authorization": @"Bearer YOUR_ACCESS_TOKEN_HERE" };
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http:///%7ByourDomain%7D/api_path"]
                                                        cachePolicy:NSURLRequestUseProtocolCachePolicy

                                                    timeoutInterval:10.0];

        [request setHTTPMethod:@"get"];
        [request setAllHTTPHeaderFields:headers];
        NSURLSession *session = [NSURLSession sharedSession];
        NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
                                                completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

                                                    if (error) {

                                                        NSLog(@&quot;%@&quot;, error);

                                                    } else {

                                                        NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;

                                                        NSLog(@&quot;%@&quot;, httpResponse);

                                                    }

                                                }];

        [dataTask resume];
        ```

        ```python Python lines theme={null}
        import http.client
        conn = http.client.HTTPConnection("")
        headers = { 'authorization': "Bearer YOUR_ACCESS_TOKEN_HERE" }
        conn.request("get", "/%7ByourDomain%7D/api_path", headers=headers)
        res = conn.getresponse()
        data = res.read()
        print(data.decode("utf-8"))
        ```

        ```ruby Ruby lines theme={null}
        require 'uri'
        require 'net/http'
        url = URI("http:///%7ByourDomain%7D/api_path")
        http = Net::HTTP.new(url.host, url.port)
        request = Net::HTTP::Get.new(url)
        request["authorization"] = 'Bearer YOUR_ACCESS_TOKEN_HERE'
        response = http.request(request)
        puts response.read_body
        ```

        ```swift Swift lines theme={null}
        import Foundation
        let headers = ["authorization": "Bearer YOUR_ACCESS_TOKEN_HERE"]
        let request = NSMutableURLRequest(url: NSURL(string: "http:///%7ByourDomain%7D/api_path")! as URL,
                                            cachePolicy: .useProtocolCachePolicy,

                                        timeoutInterval: 10.0)

        request.httpMethod = "get"
        request.allHTTPHeaderFields = headers
        let session = URLSession.shared
        let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
        if (error != nil) {
        print(error)

        } else {
        let httpResponse = response as? HTTPURLResponse

        print(httpResponse)

        }
        })
        dataTask.resume()
        ```
      </AuthCodeGroup>

      <Note>
        ##### Punto de control

        Ahora que ha configurado su aplicación, ejecútela para verificar que:

        * `GET /api/public`esté disponible para solicitudes no autenticadas.
        * `GET /api/private`esté disponible para solicitudes autenticadas.
        * `GET /api/private-scoped`esté disponible para solicitudes autenticadas que contengan un
          token de acceso con el `scope` `read:messages`.
      </Note>
    </Section>

    ## Próximos pasos

    ¡Excelente trabajo! Si llegaste hasta aquí, ya deberías tener el inicio de sesión, el cierre de sesión y la información del perfil de usuario funcionando en tu aplicación.

    Aquí concluye nuestro tutorial de inicio rápido, pero hay mucho más por explorar. Para saber más sobre lo que puedes hacer con Auth0, consulta:

    * [Auth0 Dashboard](https://manage.auth0.com/dashboard/us/dev-gja8kxz4ndtex3rq) - Aprenda a configurar y administrar su tenant de Auth0 y sus aplicaciones
    * [SDK omniauth-auth0](https://github.com/auth0/omniauth-auth0) - Explora en más detalle el SDK utilizado en este tutorial
    * [Auth0 Marketplace](https://marketplace.auth0.com/) - Descubra las integraciones que puede habilitar para ampliar las funcionalidades de Auth0
  </Content>

  <SideMenu sections={sections}>
    <SideMenuSectionItem id={sections[0].id}>
      <SignUpForm />
    </SideMenuSectionItem>

    <SideMenuSectionItem id={sections[1].id}>
      <SignUpForm />
    </SideMenuSectionItem>

    <SideMenuSectionItem id={sections[2].id}>
      <AuthCodeGroup>
        ```rb app/controllers/concerns/secured.rb lines highlight={} theme={null}
        # frozen_string_literal: true

            module Secured
              extend ActiveSupport::Concern

              REQUIRES_AUTHENTICATION = { message: 'Requires authentication' }.freeze
              BAD_CREDENTIALS = {
        message: 'Bad credentials'
              }.freeze
              MALFORMED_AUTHORIZATION_HEADER = {
        error: 'invalid_request',
        error_description: 'Authorization header value must follow this format: Bearer access-token',
        message: 'Bad credentials'
              }.freeze
              INSUFFICIENT_PERMISSIONS = {
        error: 'insufficient_permissions',
        error_description: 'The access token does not contain the required permissions',
        message: 'Permission denied'
              }.freeze

              def authorize
        token = token_from_request

        return if performed?

        validation_response = Auth0Client.validate_token(token)

        @decoded_token = validation_response.decoded_token

        return unless (error = validation_response.error)

        render json: { message: error.message }, status: error.status
              end

              def validate_permissions(permissions)
        raise 'validate_permissions needs to be called with a block' unless block_given?
        return yield if @decoded_token.validate_permissions(permissions)

        render json: INSUFFICIENT_PERMISSIONS, status: :forbidden
              end

              private

              def token_from_request
        authorization_header_elements = request.headers['Authorization']&.split

        render json: REQUIRES_AUTHENTICATION, status: :unauthorized and return unless authorization_header_elements

        unless authorization_header_elements.length == 2
          render json: MALFORMED_AUTHORIZATION_HEADER,
                 status: :unauthorized and return
        end

        scheme, token = authorization_header_elements

        render json: BAD_CREDENTIALS, status: :unauthorized and return unless scheme.downcase == 'bearer'

        token
              end
            end
        ```

        ```rb app/controllers/application_controller.rb lines highlight={} theme={null}
        # frozen_string_literal: true

            class ApplicationController < ActionController::API
              include Secured
            end
        ```

        ```rb app/lib/auth0_client.rb lines highlight={} theme={null}
        ```

        ```rb app/controllers/public_controller.rb lines highlight={} theme={null}
        # frozen_string_literal: true
            class PublicController < ApplicationController
              def public
        render json: { message: 'All good. You don\'t need to be authenticated to call this.' }
              end
            end
        ```

        ```rb app/controllers/private_controller.rb lines highlight={} theme={null}
        # frozen_string_literal: true
            class PublicController < ApplicationController
              def public
        render json: { message: 'All good. You don\'t need to be authenticated to call this.' }
              end
        end
        ```
      </AuthCodeGroup>
    </SideMenuSectionItem>

    <SideMenuSectionItem id={sections[3].id}>
      <AuthCodeGroup>
        ```rb app/controllers/concerns/secured.rb lines highlight={} theme={null}
        # frozen_string_literal: true

            module Secured
              extend ActiveSupport::Concern

              REQUIRES_AUTHENTICATION = { message: 'Requires authentication' }.freeze
              BAD_CREDENTIALS = {
        message: 'Bad credentials'
              }.freeze
              MALFORMED_AUTHORIZATION_HEADER = {
        error: 'invalid_request',
        error_description: 'Authorization header value must follow this format: Bearer access-token',
        message: 'Bad credentials'
              }.freeze
              INSUFFICIENT_PERMISSIONS = {
        error: 'insufficient_permissions',
        error_description: 'The access token does not contain the required permissions',
        message: 'Permission denied'
              }.freeze

              def authorize
        token = token_from_request

        return if performed?

        validation_response = Auth0Client.validate_token(token)

        @decoded_token = validation_response.decoded_token

        return unless (error = validation_response.error)

        render json: { message: error.message }, status: error.status
              end

              def validate_permissions(permissions)
        raise 'validate_permissions needs to be called with a block' unless block_given?
        return yield if @decoded_token.validate_permissions(permissions)

        render json: INSUFFICIENT_PERMISSIONS, status: :forbidden
              end

              private

              def token_from_request
        authorization_header_elements = request.headers['Authorization']&.split

        render json: REQUIRES_AUTHENTICATION, status: :unauthorized and return unless authorization_header_elements

        unless authorization_header_elements.length == 2
          render json: MALFORMED_AUTHORIZATION_HEADER,
                 status: :unauthorized and return
        end

        scheme, token = authorization_header_elements

        render json: BAD_CREDENTIALS, status: :unauthorized and return unless scheme.downcase == 'bearer'

        token
              end
            end
        ```

        ```rb app/controllers/application_controller.rb lines highlight={} theme={null}
        # frozen_string_literal: true

            class ApplicationController < ActionController::API
              include Secured
            end
        ```

        ```rb app/lib/auth0_client.rb lines highlight={} theme={null}
        ```

        ```rb app/controllers/public_controller.rb lines highlight={} theme={null}
        # frozen_string_literal: true
            class PublicController < ApplicationController
              def public
        render json: { message: 'All good. You don\'t need to be authenticated to call this.' }
              end
            end
        ```

        ```rb app/controllers/private_controller.rb lines highlight={} theme={null}
        # frozen_string_literal: true
            class PublicController < ApplicationController
              def public
        render json: { message: 'All good. You don\'t need to be authenticated to call this.' }
              end
        end
        ```
      </AuthCodeGroup>
    </SideMenuSectionItem>

    <SideMenuSectionItem id={sections[4].id}>
      <AuthCodeGroup>
        ```rb app/controllers/application_controller.rb lines highlight={} theme={null}
        # frozen_string_literal: true

            class ApplicationController < ActionController::API
              include Secured
            end
        ```

        ```rb app/lib/auth0_client.rb lines highlight={} theme={null}
        ```

        ```rb app/controllers/concerns/secured.rb lines highlight={} theme={null}
        # frozen_string_literal: true

            module Secured
              extend ActiveSupport::Concern

              REQUIRES_AUTHENTICATION = { message: 'Requires authentication' }.freeze
              BAD_CREDENTIALS = {
        message: 'Bad credentials'
              }.freeze
              MALFORMED_AUTHORIZATION_HEADER = {
        error: 'invalid_request',
        error_description: 'Authorization header value must follow this format: Bearer access-token',
        message: 'Bad credentials'
              }.freeze
              INSUFFICIENT_PERMISSIONS = {
        error: 'insufficient_permissions',
        error_description: 'The access token does not contain the required permissions',
        message: 'Permission denied'
              }.freeze

              def authorize
        token = token_from_request

        return if performed?

        validation_response = Auth0Client.validate_token(token)

        @decoded_token = validation_response.decoded_token

        return unless (error = validation_response.error)

        render json: { message: error.message }, status: error.status
              end

              def validate_permissions(permissions)
        raise 'validate_permissions needs to be called with a block' unless block_given?
        return yield if @decoded_token.validate_permissions(permissions)

        render json: INSUFFICIENT_PERMISSIONS, status: :forbidden
              end

              private

              def token_from_request
        authorization_header_elements = request.headers['Authorization']&.split

        render json: REQUIRES_AUTHENTICATION, status: :unauthorized and return unless authorization_header_elements

        unless authorization_header_elements.length == 2
          render json: MALFORMED_AUTHORIZATION_HEADER,
                 status: :unauthorized and return
        end

        scheme, token = authorization_header_elements

        render json: BAD_CREDENTIALS, status: :unauthorized and return unless scheme.downcase == 'bearer'

        token
              end
            end
        ```

        ```rb app/controllers/public_controller.rb lines highlight={} theme={null}
        # frozen_string_literal: true
            class PublicController < ApplicationController
              def public
        render json: { message: 'All good. You don\'t need to be authenticated to call this.' }
              end
            end
        ```

        ```rb app/controllers/private_controller.rb lines highlight={} theme={null}
        # frozen_string_literal: true
            class PublicController < ApplicationController
              def public
        render json: { message: 'All good. You don\'t need to be authenticated to call this.' }
              end
        end
        ```
      </AuthCodeGroup>
    </SideMenuSectionItem>

    <SideMenuSectionItem id={sections[5].id}>
      <AuthCodeGroup>
        ```rb app/controllers/public_controller.rb lines highlight={} theme={null}
        # frozen_string_literal: true
            class PublicController < ApplicationController
              def public
        render json: { message: 'All good. You don\'t need to be authenticated to call this.' }
              end
            end
        ```

        ```rb app/controllers/application_controller.rb lines highlight={} theme={null}
        # frozen_string_literal: true

            class ApplicationController < ActionController::API
              include Secured
            end
        ```

        ```rb app/lib/auth0_client.rb lines highlight={} theme={null}
        ```

        ```rb app/controllers/concerns/secured.rb lines highlight={} theme={null}
        # frozen_string_literal: true

            module Secured
              extend ActiveSupport::Concern

              REQUIRES_AUTHENTICATION = { message: 'Requires authentication' }.freeze
              BAD_CREDENTIALS = {
        message: 'Bad credentials'
              }.freeze
              MALFORMED_AUTHORIZATION_HEADER = {
        error: 'invalid_request',
        error_description: 'Authorization header value must follow this format: Bearer access-token',
        message: 'Bad credentials'
              }.freeze
              INSUFFICIENT_PERMISSIONS = {
        error: 'insufficient_permissions',
        error_description: 'The access token does not contain the required permissions',
        message: 'Permission denied'
              }.freeze

              def authorize
        token = token_from_request

        return if performed?

        validation_response = Auth0Client.validate_token(token)

        @decoded_token = validation_response.decoded_token

        return unless (error = validation_response.error)

        render json: { message: error.message }, status: error.status
              end

              def validate_permissions(permissions)
        raise 'validate_permissions needs to be called with a block' unless block_given?
        return yield if @decoded_token.validate_permissions(permissions)

        render json: INSUFFICIENT_PERMISSIONS, status: :forbidden
              end

              private

              def token_from_request
        authorization_header_elements = request.headers['Authorization']&.split

        render json: REQUIRES_AUTHENTICATION, status: :unauthorized and return unless authorization_header_elements

        unless authorization_header_elements.length == 2
          render json: MALFORMED_AUTHORIZATION_HEADER,
                 status: :unauthorized and return
        end

        scheme, token = authorization_header_elements

        render json: BAD_CREDENTIALS, status: :unauthorized and return unless scheme.downcase == 'bearer'

        token
              end
            end
        ```

        ```rb app/controllers/private_controller.rb lines highlight={} theme={null}
        # frozen_string_literal: true
            class PublicController < ApplicationController
              def public
        render json: { message: 'All good. You don\'t need to be authenticated to call this.' }
              end
        end
        ```
      </AuthCodeGroup>
    </SideMenuSectionItem>

    <SideMenuSectionItem id={sections[6].id}>
      <AuthCodeGroup>
        ```rb app/controllers/private_controller.rb lines highlight={} theme={null}
        # frozen_string_literal: true
            class PublicController < ApplicationController
              def public
        render json: { message: 'All good. You don\'t need to be authenticated to call this.' }
              end
        end
        ```

        ```rb app/controllers/application_controller.rb lines highlight={} theme={null}
        # frozen_string_literal: true

            class ApplicationController < ActionController::API
              include Secured
            end
        ```

        ```rb app/lib/auth0_client.rb lines highlight={} theme={null}
        ```

        ```rb app/controllers/concerns/secured.rb lines highlight={} theme={null}
        # frozen_string_literal: true

            module Secured
              extend ActiveSupport::Concern

              REQUIRES_AUTHENTICATION = { message: 'Requires authentication' }.freeze
              BAD_CREDENTIALS = {
        message: 'Bad credentials'
              }.freeze
              MALFORMED_AUTHORIZATION_HEADER = {
        error: 'invalid_request',
        error_description: 'Authorization header value must follow this format: Bearer access-token',
        message: 'Bad credentials'
              }.freeze
              INSUFFICIENT_PERMISSIONS = {
        error: 'insufficient_permissions',
        error_description: 'The access token does not contain the required permissions',
        message: 'Permission denied'
              }.freeze

              def authorize
        token = token_from_request

        return if performed?

        validation_response = Auth0Client.validate_token(token)

        @decoded_token = validation_response.decoded_token

        return unless (error = validation_response.error)

        render json: { message: error.message }, status: error.status
              end

              def validate_permissions(permissions)
        raise 'validate_permissions needs to be called with a block' unless block_given?
        return yield if @decoded_token.validate_permissions(permissions)

        render json: INSUFFICIENT_PERMISSIONS, status: :forbidden
              end

              private

              def token_from_request
        authorization_header_elements = request.headers['Authorization']&.split

        render json: REQUIRES_AUTHENTICATION, status: :unauthorized and return unless authorization_header_elements

        unless authorization_header_elements.length == 2
          render json: MALFORMED_AUTHORIZATION_HEADER,
                 status: :unauthorized and return
        end

        scheme, token = authorization_header_elements

        render json: BAD_CREDENTIALS, status: :unauthorized and return unless scheme.downcase == 'bearer'

        token
              end
            end
        ```

        ```rb app/controllers/public_controller.rb lines highlight={} theme={null}
        # frozen_string_literal: true
            class PublicController < ApplicationController
              def public
        render json: { message: 'All good. You don\'t need to be authenticated to call this.' }
              end
            end
        ```
      </AuthCodeGroup>
    </SideMenuSectionItem>
  </SideMenu>
</Recipe>
