Implementing Google OAuth Authentication in Deno Fresh
This guide will walk you through implementing Google OAuth authentication in your Deno Fresh project.
Prerequisites
- A Google Cloud Console project
- OAuth 2.0 Client credentials
- Deno and Fresh installed
Step 1: Set Up Google Cloud Console
- Go to Google Cloud Console
- Create a new project or select an existing one
- Enable the Google OAuth2 API
- Configure the OAuth consent screen
- Create OAuth 2.0 Client credentials
- Set authorized redirect URIs (e.g.,
http://localhost:8000/api/auth/callback
) - Download the client configuration JSON
- Set authorized redirect URIs (e.g.,
Step 2: Project Configuration
Create a .env
file in your project root:
GOOGLE_CLIENT_ID=your_client_id
GOOGLE_CLIENT_SECRET=your_client_secret
GOOGLE_REDIRECT_URI=http://localhost:8000/api/auth/callback
Step 3: Implementation
1. Create Auth Types
Create types/auth.ts
:
export interface GoogleUser {
id: string;
email: string;
name: string;
picture: string;
}
export interface Session {
user?: GoogleUser;
accessToken?: string;
refreshToken?: string;
}
2. Create Auth Utils
Create utils/auth.ts
:
import { OAuth2Client } from "https://deno.land/x/oauth2_client@v1.0.2/mod.ts";
export function createOAuthClient() {
return new OAuth2Client({
clientId: Deno.env.get("GOOGLE_CLIENT_ID")!,
clientSecret: Deno.env.get("GOOGLE_CLIENT_SECRET")!,
authorizationEndpointUri: "https://accounts.google.com/o/oauth2/v2/auth",
tokenUri: "https://oauth2.googleapis.com/token",
redirectUri: Deno.env.get("GOOGLE_REDIRECT_URI")!,
defaults: {
scope: ["https://www.googleapis.com/auth/userinfo.profile",
"https://www.googleapis.com/auth/userinfo.email"],
},
});
}
export async function getUserInfo(accessToken: string): Promise<GoogleUser> {
const response = await fetch(
`https://www.googleapis.com/oauth2/v1/userinfo?alt=json&access_token=${accessToken}`,
);
if (!response.ok) {
throw new Error("Failed to fetch user info");
}
return await response.json();
}
3. Create Auth Routes
Create routes/api/auth/signin.ts
:
import { HandlerContext } from "$fresh/server.ts";
import { createOAuthClient } from "../../../utils/auth.ts";
export async function handler(req: Request, ctx: HandlerContext) {
const oauth2Client = createOAuthClient();
const url = await oauth2Client.code.getAuthorizationUri();
return new Response(null, {
status: 302,
headers: {
Location: url.toString(),
},
});
}
Create routes/api/auth/callback.ts
:
import { HandlerContext } from "$fresh/server.ts";
import { createOAuthClient, getUserInfo } from "../../../utils/auth.ts";
import { setCookie } from "https://deno.land/std@0.181.0/http/cookie.ts";
export async function handler(req: Request, ctx: HandlerContext) {
const oauth2Client = createOAuthClient();
const tokens = await oauth2Client.code.getToken(req.url);
const userInfo = await getUserInfo(tokens.accessToken);
const response = new Response(null, {
status: 302,
headers: {
Location: "/",
},
});
// Set session cookie
setCookie(response.headers, {
name: "session",
value: JSON.stringify({
user: userInfo,
accessToken: tokens.accessToken,
refreshToken: tokens.refreshToken,
}),
path: "/",
httpOnly: true,
secure: true,
sameSite: "Lax",
});
return response;
}
4. Create Auth Middleware
Create routes/_middleware.ts
:
import { FreshContext } from "$fresh/server.ts";
import { getCookies } from "https://deno.land/std@0.181.0/http/cookie.ts";
import { Session } from "../types/auth.ts";
interface State {
session?: Session;
}
export async function handler(
req: Request,
ctx: FreshContext<State>
) {
// Only apply middleware to route handlers, not static files or internal requests
if (ctx.destination !== "route") {
return await ctx.next();
}
const cookies = getCookies(req.headers);
const sessionCookie = cookies.session;
if (sessionCookie) {
try {
const session: Session = JSON.parse(sessionCookie);
// Modify state directly instead of replacing the entire object
ctx.state.session = session;
} catch {
// Invalid session cookie
console.error("Invalid session cookie");
}
}
const resp = await ctx.next();
return resp;
}
For protected routes, create routes/admin/_middleware.ts
:
import { FreshContext } from "$fresh/server.ts";
import { Session } from "../../types/auth.ts";
interface State {
session?: Session;
}
export async function handler(
req: Request,
ctx: FreshContext<State>
) {
// Check if user is authenticated
if (!ctx.state.session?.user) {
return new Response("Unauthorized", {
status: 401,
headers: {
"Location": "/api/auth/signin",
},
});
}
return await ctx.next();
}
5. Add Auth UI Components
Create components/AuthButton.tsx
:
import { Session } from "../types/auth.ts";
interface AuthButtonProps {
session?: Session;
}
export default function AuthButton({ session }: AuthButtonProps) {
return (
<div>
{session?.user ? (
<div class="flex items-center gap-2">
<img
src={session.user.picture}
alt={session.user.name}
class="w-8 h-8 rounded-full"
/>
<span>{session.user.name}</span>
<a
href="/api/auth/signout"
class="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600 transition-colors"
>
Sign Out
</a>
</div>
) : (
<a
href="/api/auth/signin"
class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors"
>
Sign in with Google
</a>
)}
</div>
);
}
Step 4: Usage
Update your navbar component (components/Navbar.tsx
):
import { PageProps } from "$fresh/server.ts";
import AuthButton from "./AuthButton.tsx";
import { Session } from "../types/auth.ts";
interface NavbarProps {
session?: Session;
}
export default function Navbar({ session }: NavbarProps) {
return (
<nav class="w-full px-8 py-4 bg-white border-b flex items-center justify-between">
<div class="flex items-center gap-6">
<a href="/" class="text-2xl font-bold">
Your App
</a>
{/* Add your other nav links here */}
</div>
<AuthButton session={session} />
</nav>
);
}
Security Considerations
- Always use HTTPS in production
- Store sensitive data in secure cookies
- Implement CSRF protection
- Regularly rotate OAuth client secrets
- Validate all user input and tokens
- Implement proper session management
Error Handling
Add proper error handling for:
- Invalid tokens
- Network failures
- API rate limits
- Invalid/expired sessions
Testing
- Create test OAuth credentials
- Mock OAuth responses
- Test authentication flow
- Test session management
- Test error scenarios
Deployment
- Update OAuth redirect URIs for production
- Set up environment variables
- Enable HTTPS
- Configure session storage
- Set up monitoring