google-agents-cli-scaffold
Project scaffolding, deployment configuration, and CI/CD setup for Google ADK agents.
inertia-rails-auth
Implement authentication and authorization in Inertia Rails applications. Use when setting up login, sessions, permissions, and access control with Devise, has_secure_password, or other auth solutions.
Full skill instructions
Guide to implementing authentication and authorization in Inertia Rails applications.
Inertia uses your existing Rails authentication infrastructure. No special OAuth or token-based auth required. Since your frontend and backend share the same domain, session-based auth works seamlessly.
# Gemfile
gem 'devise'
bundle install
rails generate devise:install
rails generate devise User
rails db:migrate
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
inertia_share do
{
auth: {
user: current_user&.as_json(only: [:id, :name, :email, :avatar_url]),
signed_in: user_signed_in?
}
}
end
end
# app/controllers/sessions_controller.rb
class SessionsController < Devise::SessionsController
def new
render inertia: {}
end
def create
self.resource = warden.authenticate(auth_options)
if resource
sign_in(resource_name, resource)
redirect_to after_sign_in_path_for(resource), notice: 'Signed in successfully!'
else
redirect_to new_session_path(resource_name), inertia: {
errors: { email: 'Invalid email or password' }
}
end
end
def destroy
sign_out(resource_name)
redirect_to root_path, notice: 'Signed out successfully!'
end
end
<!-- app/frontend/pages/sessions/new.vue -->
<script setup>
import { useForm, Link } from '@inertiajs/vue3'
const form = useForm({
email: '',
password: '',
remember: false,
})
function submit() {
form.post('/users/sign_in', {
onSuccess: () => form.reset('password'),
})
}
</script>
<template>
<form @submit.prevent="submit">
<h1>Sign In</h1>
<div>
<label>Email</label>
<input v-model="form.email" type="email" autofocus />
<span v-if="form.errors.email" class="error">{{ form.errors.email }}</span>
</div>
<div>
<label>Password</label>
<input v-model="form.password" type="password" />
</div>
<div>
<label>
<input v-model="form.remember" type="checkbox" />
Remember me
</label>
</div>
<button type="submit" :disabled="form.processing">
{{ form.processing ? 'Signing in...' : 'Sign In' }}
</button>
<p>
<Link href="/users/sign_up">Create an account</Link>
<Link href="/users/password/new">Forgot password?</Link>
</p>
</form>
</template>
// app/frontend/pages/sessions/new.jsx
import { useForm, Link } from '@inertiajs/react'
export default function Login() {
const { data, setData, post, processing, errors, reset } = useForm({
email: '',
password: '',
remember: false,
})
function submit(e) {
e.preventDefault()
post('/users/sign_in', {
onSuccess: () => reset('password'),
})
}
return (
<form onSubmit={submit}>
<h1>Sign In</h1>
<div>
<label>Email</label>
<input
type="email"
value={data.email}
onChange={(e) => setData('email', e.target.value)}
autoFocus
/>
{errors.email && <span className="error">{errors.email}</span>}
</div>
<div>
<label>Password</label>
<input
type="password"
value={data.password}
onChange={(e) => setData('password', e.target.value)}
/>
</div>
<div>
<label>
<input
type="checkbox"
checked={data.remember}
onChange={(e) => setData('remember', e.target.checked)}
/>
Remember me
</label>
</div>
<button type="submit" disabled={processing}>
{processing ? 'Signing in...' : 'Sign In'}
</button>
<p>
<Link href="/users/sign_up">Create an account</Link>
<Link href="/users/password/new">Forgot password?</Link>
</p>
</form>
)
}
# app/models/user.rb
class User < ApplicationRecord
has_secure_password
validates :email, presence: true, uniqueness: true
validates :password, length: { minimum: 8 }, allow_nil: true
end
# app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
skip_before_action :authenticate!, only: [:new, :create]
def new
render inertia: {}
end
def create
user = User.find_by(email: params[:email])
if user&.authenticate(params[:password])
session[:user_id] = user.id
redirect_to dashboard_path, notice: 'Welcome back!'
else
redirect_to login_path, inertia: {
errors: { email: 'Invalid email or password' }
}
end
end
def destroy
session.delete(:user_id)
redirect_to root_path, notice: 'Signed out successfully!'
end
end
# Routes
# config/routes.rb
get 'login', to: 'sessions#new'
post 'login', to: 'sessions#create'
delete 'logout', to: 'sessions#destroy'
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
before_action :authenticate!
helper_method :current_user, :user_signed_in?
inertia_share do
{
auth: {
user: current_user&.as_json(only: [:id, :name, :email]),
signed_in: user_signed_in?
}
}
end
private
def current_user
@current_user ||= User.find_by(id: session[:user_id])
end
def user_signed_in?
current_user.present?
end
def authenticate!
unless user_signed_in?
redirect_to login_path, alert: 'Please sign in to continue'
end
end
end
Since frontend components can't access server-side authorization helpers, pass permission results as props:
# app/controllers/users_controller.rb
class UsersController < ApplicationController
def index
render inertia: {
can: {
create_user: policy(User).create?
},
users: User.all.map do |user|
serialize_user(user)
end
}
end
def show
user = User.find(params[:id])
render inertia: {
user: serialize_user(user),
can: {
edit: policy(user).edit?,
delete: policy(user).destroy?
}
}
end
private
def serialize_user(user)
user.as_json(only: [:id, :name, :email]).merge(
can: {
edit: policy(user).edit?,
delete: policy(user).destroy?
}
)
end
end
# Gemfile
gem 'pundit'
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
include Pundit::Authorization
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
private
def user_not_authorized
redirect_to root_path, alert: 'You are not authorized to perform this action'
end
end
# app/policies/user_policy.rb
class UserPolicy < ApplicationPolicy
def index?
true
end
def show?
true
end
def create?
user.admin?
end
def update?
user.admin? || record == user
end
def destroy?
user.admin? && record != user
end
end
# Gemfile
gem 'action_policy'
# app/controllers/users_controller.rb
class UsersController < ApplicationController
def index
render inertia: {
can: {
create_user: allowed_to?(:create?, User)
},
users: User.all.map do |user|
user.as_json(only: [:id, :name]).merge(
can: {
edit: allowed_to?(:edit?, user),
delete: allowed_to?(:destroy?, user)
}
)
end
}
end
end
<script setup>
import { Link, usePage } from '@inertiajs/vue3'
const props = defineProps(['users', 'can'])
const { auth } = usePage().props
</script>
<template>
<div>
<h1>Users</h1>
<!-- Global permission -->
<Link v-if="can.create_user" href="/users/new" class="btn">
Create User
</Link>
<ul>
<li v-for="user in users" :key="user.id">
{{ user.name }}
<!-- Per-record permissions -->
<Link v-if="user.can.edit" :href="`/users/${user.id}/edit`">
Edit
</Link>
<Link
v-if="user.can.delete"
:href="`/users/${user.id}`"
method="delete"
as="button"
>
Delete
</Link>
</li>
</ul>
</div>
</template>
import { Link, usePage } from '@inertiajs/react'
export default function UsersIndex({ users, can }) {
const { auth } = usePage().props
return (
<div>
<h1>Users</h1>
{can.create_user && (
<Link href="/users/new" className="btn">
Create User
</Link>
)}
<ul>
{users.map((user) => (
<li key={user.id}>
{user.name}
{user.can.edit && (
<Link href={`/users/${user.id}/edit`}>Edit</Link>
)}
{user.can.delete && (
<Link
href={`/users/${user.id}`}
method="delete"
as="button"
>
Delete
</Link>
)}
</li>
))}
</ul>
</div>
)
}
Prevent sensitive data exposure via browser back button after logout:
# config/initializers/inertia_rails.rb
InertiaRails.configure do |config|
# Enable globally
config.encrypt_history = true
end
# Or per-controller for sensitive areas
class Admin::BaseController < ApplicationController
inertia_config(encrypt_history: true)
end
# Or per-request
def show
render inertia: { secret_data: data }, encrypt_history: true
end
def destroy
sign_out(current_user)
# Clear encrypted history (rotates encryption key)
redirect_to root_path, inertia: { clear_history: true }
end
Client-side:
import { router } from '@inertiajs/vue3'
function logout() {
router.clearHistory()
router.post('/logout')
}
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
before_action :authenticate!
private
def authenticate!
unless user_signed_in?
redirect_to login_path, alert: 'Please sign in'
end
end
end
# Skip for public controllers
class PagesController < ApplicationController
skip_before_action :authenticate!, only: [:home, :about]
end
class Admin::BaseController < ApplicationController
before_action :require_admin!
private
def require_admin!
unless current_user&.admin?
redirect_to root_path, alert: 'Admin access required'
end
end
end
Inertia handles CSRF automatically. Ensure Rails is configured:
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
end
Axios (used by Inertia) automatically:
XSRF-TOKEN cookie set by RailsX-XSRF-TOKEN header with requests# app/controllers/registrations_controller.rb
class RegistrationsController < ApplicationController
skip_before_action :authenticate!
def new
render inertia: {}
end
def create
user = User.new(user_params)
if user.save
session[:user_id] = user.id
redirect_to dashboard_path, notice: 'Welcome!'
else
redirect_to register_path, inertia: { errors: user.errors }
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password, :password_confirmation)
end
end
# app/controllers/password_resets_controller.rb
class PasswordResetsController < ApplicationController
skip_before_action :authenticate!
def new
render inertia: {}
end
def create
user = User.find_by(email: params[:email])
user&.send_password_reset_email
# Always show success to prevent email enumeration
redirect_to login_path, notice: 'Check your email for reset instructions'
end
def edit
user = User.find_by(password_reset_token: params[:token])
if user&.password_reset_valid?
render inertia: { token: params[:token] }
else
redirect_to login_path, alert: 'Invalid or expired reset link'
end
end
def update
user = User.find_by(password_reset_token: params[:token])
if user&.password_reset_valid? && user.update(password_params)
user.clear_password_reset!
session[:user_id] = user.id
redirect_to dashboard_path, notice: 'Password updated!'
else
redirect_to edit_password_reset_path(token: params[:token]),
inertia: { errors: user&.errors || { token: 'Invalid token' } }
end
end
private
def password_params
params.permit(:password, :password_confirmation)
end
end
Always verify permissions server-side:
def destroy
user = User.find(params[:id])
authorize user # Pundit/ActionPolicy check
user.destroy
redirect_to users_path
end
# Bad
inertia_share auth: { user: current_user }
# Good
inertia_share auth: {
user: current_user&.as_json(only: [:id, :name, :email])
}
# config/initializers/session_store.rb
Rails.application.config.session_store :cookie_store,
key: '_myapp_session',
secure: Rails.env.production?,
httponly: true,
same_site: :lax
// Check auth state on each navigation
router.on('navigate', () => {
const { auth } = usePage().props
if (!auth.signed_in && requiresAuth(page.component)) {
router.visit('/login')
}
})
# Gemfile
gem 'rack-attack'
# config/initializers/rack_attack.rb
Rack::Attack.throttle('login attempts', limit: 5, period: 60) do |req|
req.ip if req.path == '/login' && req.post?
end
Project scaffolding, deployment configuration, and CI/CD setup for Google ADK agents.
Set up tracing, logging, and monitoring for deployed ADK agents across Cloud Trace, BigQuery, and third-party platforms.
Enterprise Azure infrastructure architect generating Bicep or Terraform from workload descriptions.
Plan and configure production-ready Azure Kubernetes Service clusters with Day-0 and Day-1 best practices.
Raw mechanical interfaces fusing Swiss typographic print with military terminal aesthetics. Rigid grids, extreme type scale contrast, utilitarian color, analog degradation effects. For data-heavy dashboards, portfolios, or editorial sites that need to feel like declassified blueprints.
Web search, scraping, extraction, crawling, and monitoring via ScrapeGraph AI CLI.
Skill for working with Firebase Hosting (Classic). Use this when you want to deploy static web apps, Single Page Apps (SPAs), or simple microservices. Do NOT use for Firebase App Hosting.
Deploy and manage web apps with Firebase App Hosting. Use this skill when deploying Next.js/Angular apps with backends.
Deploy applications and websites to Vercel. Use when the user requests deployment actions like "deploy my app", "deploy and give me the link", "push this live", or "create a preview deployment".
Build SEO-optimized pages at scale using templates, data, and proven playbook patterns.
Design and build isolated, reusable Convex backend components with clear boundaries and app-facing wrappers.
Deploy and manage projects on Vercel using token-based authentication. Use when working with Vercel CLI using access tokens rather than interactive login — e.g. "deploy to vercel", "set up vercel", "add environment variables to vercel".
Opus.ai: Revolutionize Your Web Experience
Build a no-code AI app in minutes.
An IDE for code migration from legacy to modern frameworks through coding agents.
Automate CGI animation in live-action scenes
Branded artistic QR-code concepts
Launch a website in seconds with AI.
Streamline Your Coding Experience with AI Code Helper
Convert any screenshot or design to clean code.