Implementing the authentication and middleware routes
- Expressjs
- Go
- Next.js
Add login route
This route generates PKCE code verifiers and state tokens for secure authentication, then redirects the user to Ory's authorization server.
// Login route
app.get("/login", async (req, res) => {
if (!config) {
return res
.status(500)
.send("Configuration not ready yet. Please try again in a moment.")
}
try {
// Generate PKCE code verifier
const code_verifier = client.randomPKCECodeVerifier()
// Calculate code challenge from verifier
const code_challenge =
await client.calculatePKCECodeChallenge(code_verifier)
// Generate state for CSRF protection
const state = client.randomState()
// Store in session for later verification
req.session.codeVerifier = code_verifier
req.session.state = state
// Build authorization parameters
const parameters = {
redirect_uri: "http://localhost:3000/callback",
scope: "openid email offline_access",
code_challenge,
code_challenge_method: "S256",
state,
}
// Build authorization URL
const redirectTo = client.buildAuthorizationUrl(config, parameters)
// Redirect user to authorization server
console.log("Redirecting to:", redirectTo.href)
res.redirect(redirectTo.href)
} catch (error) {
console.error("Login error:", error)
res.status(500).send(`Login error: ${error.message}`)
}
})
Add logout route
This route destroys the user's session and redirects them back to the home page. It cleans up all authentication tokens stored in the session.
// Logout route
app.get("/logout", (req, res) => {
// Clear session
req.session.destroy((err) => {
if (err) {
console.error("Error destroying session:", err)
}
res.redirect("/")
})
})
Create middleware to check if the user is authenticated
This middleware checks if the user has a valid access token and redirects to login if not. It also handles token expiration by attempting to refresh expired tokens before redirecting.
const requireAuth = (req, res, next) => {
if (!req.session.tokens || !req.session.tokens.access_token) {
// User is not authenticated, redirect to login
return res.redirect("/login")
}
// Check if the token is expired
if (req.session.tokens.expiresIn && req.session.tokens.expiresIn() <= 0) {
// Token is expired, we need to refresh it if we have a refresh token
if (req.session.tokens.refresh_token) {
// We'll handle refresh in a separate middleware
return refreshToken(req, res, next)
} else {
// No refresh token, redirect to login
return res.redirect("/login")
}
}
// User is authenticated with a valid token
next()
}
Add refresh token middleware
This middleware automatically renews expired access credentials by using the long-lived refresh token to obtain a new set of tokens. If successful, it updates your session with the fresh credentials; if it fails (e.g., refresh token expired), it logs you out and redirects to the login page.
// Middleware to refresh tokens
const refreshToken = async (req, res, next) => {
if (!config) {
return res
.status(500)
.send("Configuration not ready yet. Please try again in a moment.")
}
try {
// Use the refresh token to get new tokens
const tokens = await client.refreshTokenGrant(
config,
req.session.tokens.refresh_token,
)
// Update the tokens in the session
req.session.tokens = tokens
// Continue with the request
next()
} catch (error) {
console.error("Token refresh error:", error)
// Clear session and redirect to login
req.session.destroy()
res.redirect("/login")
}
}
Protect routes with middleware
This code shows how to create a protected route that requires authentication using the middleware. It fetches and displays user profile information from the userinfo endpoint using the access token.
app.get("/profile", requireAuth, async (req, res) => {
try {
// Get user info
const userInfo = await client.fetchUserInfo(
config,
req.session.tokens.access_token,
client.skipSubjectCheck,
)
res.json({
message: "This is protected data from the resource server",
email: userInfo.email,
sub: userInfo.sub,
})
} catch (error) {
console.error("Profile error:", error)
res.status(500).json({ error: `Error accessing profile: ${error.message}` })
}
})
How to check for session in an unprotected route
This code demonstrates how to handle a public route that adapts its behavior based on whether the user is authenticated.
// Home route
app.get("/", async (req, res) => {
if (req.session.tokens) {
// If we have tokens, try to get user info
let userInfo = null
try {
if (req.session.tokens.access_token) {
// For userInfo we need expectedSubject or skipSubjectCheck
userInfo = await client.fetchUserInfo(
config,
req.session.tokens.access_token,
client.skipSubjectCheck,
)
}
} catch (error) {
console.error("Error fetching user info:", error)
}
res.send(`
<html lang='en'>
<body>
<h1>Authenticated!</h1>
<h2>Tokens:</h2>
<pre>${JSON.stringify(req.session.tokens, null, 2)}</pre>
${userInfo ? `<h2>User Info:</h2><pre>${JSON.stringify(userInfo, null, 2)}</pre>` : ""}
<p><a href="/profile">View Profile API</a></p>
<p><a href="/logout">Logout</a></p>
</body>
</html>
`)
} else {
res.send(`
<html lang='en'>
<body>
<h1>Welcome</h1>
<a href="/login">Login</a>
</body>
</html>
`)
}
})
Add login route
This route generates PKCE code verifiers and state tokens for secure authentication, then redirects the user to Ory's authorization server.
// Login handler
func handleLogin(w http.ResponseWriter, r *http.Request) {
// Generate random state parameter
state, err := generateRandomString(32)
if err != nil {
http.Error(w, "Failed to generate state parameter", http.StatusInternalServerError)
return
}
// Generate code verifier for PKCE
codeVerifier := oauth2.GenerateVerifier()
// Create a new session
sessionID, err := generateRandomString(32)
if err != nil {
http.Error(w, "Failed to generate session ID", http.StatusInternalServerError)
return
}
// Store session
sessions[sessionID] = Session{
State: state,
CodeVerifier: codeVerifier,
}
// Set session cookie
http.SetCookie(w, &http.Cookie{
Name: "session_id",
Value: sessionID,
Path: "/",
HttpOnly: true,
Secure: r.TLS != nil,
MaxAge: int(24 * time.Hour.Seconds()),
})
// Generate authorization URL with PKCE challenge
authURL := oauthConfig.AuthCodeURL(
state,
oauth2.S256ChallengeOption(codeVerifier),
)
// Redirect to authorization server
http.Redirect(w, r, authURL, http.StatusSeeOther)
}
// Generate a random string for state parameter
func generateRandomString(length int) (string, error) {
b := make([]byte, length)
if _, err := rand.Read(b); err != nil {
return "", err
}
return base64.RawURLEncoding.EncodeToString(b), nil
}
Add logout route
This route destroys the user's session and redirects them back to the home page. It cleans up all authentication tokens stored in the session.
func handleLogout(w http.ResponseWriter, r *http.Request) {
// Get session cookie
cookie, err := r.Cookie("session_id")
if err == nil {
// Delete session
delete(sessions, cookie.Value)
}
// Clear cookie
http.SetCookie(w, &http.Cookie{
Name: "session_id",
Value: "",
Path: "/",
HttpOnly: true,
Secure: r.TLS != nil,
MaxAge: -1,
})
// Redirect to home page
http.Redirect(w, r, "/", http.StatusSeeOther)
}
Create middleware to check if the user is authenticated
This middleware checks if the user has a valid access token and redirects to login if not. It also handles token expiration by attempting to refresh expired tokens before redirecting.
// RequireAuth middleware with explicit token refresh
func RequireAuth(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Get session cookie
cookie, err := r.Cookie("session_id")
if err != nil {
// No session cookie, redirect to login
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
// Get session from store
session, ok := sessions[cookie.Value]
if !ok {
// Session not found, redirect to login
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
// Check if token is valid
if session.Token == nil || !session.Token.Valid() {
// Token expired, try to refresh it
if session.Token != nil && session.Token.RefreshToken != "" {
// Call the dedicated refresh function
newToken, err := RefreshToken(&session)
if err != nil {
// Refresh failed, redirect to login
log.Printf("Token refresh failed: %v", err)
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
// Update session with new token
session.Token = newToken
sessions[cookie.Value] = session
} else {
// No refresh token, redirect to login
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
}
// Token is valid, proceed to the next handler
next(w, r)
}
}
Add refresh token middleware
This middleware automatically renews expired access credentials by using the long-lived refresh token to obtain a new set of tokens. If successful, it updates your session with the fresh credentials; if it fails (e.g., refresh token expired), it logs you out and redirects to the login page.
// RefreshToken refreshes an expired token if a refresh token is available
func RefreshToken(session *Session) (*oauth2.Token, error) {
if session.Token == nil || session.Token.RefreshToken == "" {
return nil, fmt.Errorf("no refresh token available")
}
// Create a TokenSource with the current token
tokenSource := oauthConfig.TokenSource(context.Background(), session.Token)
// Get a new token (this will use the refresh token if the access token is expired)
newToken, err := tokenSource.Token()
if err != nil {
return nil, fmt.Errorf("failed to refresh token: %w", err)
}
// Log successful token refresh (optional)
log.Println("Token refreshed successfully")
return newToken, nil
}
Protect routes with middleware
This code shows how to create a protected route that requires authentication using the middleware. It fetches and displays user profile information from the userinfo endpoint using the access token.
// Profile handler (protected route)
func handleProfile(w http.ResponseWriter, r *http.Request) {
// Get session cookie
cookie, err := r.Cookie("session_id")
if err != nil {
http.Error(w, "No session found", http.StatusUnauthorized)
return
}
// Get session from store
session, ok := sessions[cookie.Value]
if !ok {
http.Error(w, "Invalid session", http.StatusUnauthorized)
return
}
// Return profile data
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"message": "This is protected data from the resource server",
"user": session.UserInfo,
})
}
How to check for session in an unprotected route
This code demonstrates how to handle a public route that adapts its behavior based on whether the user is authenticated.
func handleHome(w http.ResponseWriter, r *http.Request) {
cookie, err := r.Cookie("session_id")
var loggedIn bool
var userInfo map[string]interface{}
if err == nil {
if session, ok := sessions[cookie.Value]; ok && session.Token != nil && session.Token.Valid() {
loggedIn = true
userInfo = session.UserInfo
}
}
w.Header().Set("Content-Type", "text/html")
if loggedIn {
// Display logged-in page with user info
fmt.Fprintf(w, `
<html>
<head><title>OAuth2 Test</title></head>
<body>
<h1>Welcome!</h1>
<p>You are logged in.</p>
<h2>User Info:</h2>
<pre>%v</pre>
<p><a href="/profile">View Profile</a></p>
<p><a href="/logout">Logout</a></p>
</body>
</html>
`, userInfo)
} else {
// Display login page
fmt.Fprintf(w, `
<html>
<head><title>OAuth2 Test</title></head>
<body>
<h1>Welcome</h1>
<p>You are not logged in.</p>
<p><a href="/login">Login</a></p>
</body>
</html>
`)
}
}