Skip to content
Open
18 changes: 9 additions & 9 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ jobs:
# Steps to execute for this job
steps:
- name: Checkout code into go module directory
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0 # Full git history for accurate testing

Expand Down Expand Up @@ -125,7 +125,7 @@ jobs:

steps:
- name: Checkout code into go module directory
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0

Expand Down Expand Up @@ -173,11 +173,11 @@ jobs:
needs: [ Example-Unit-Testing,PKG-Unit-Testing ]
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v4
uses: actions/checkout@v5

# Download coverage reports from previous jobs
- name: Download Coverage Report
uses: actions/download-artifact@v4
uses: actions/download-artifact@v5
with:
path: artifacts

Expand Down Expand Up @@ -213,7 +213,7 @@ jobs:

steps:
- name: Checkout code into go module directory
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0

Expand Down Expand Up @@ -285,11 +285,11 @@ jobs:
# if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/development'}}
# steps:
# - name: Check out code into the Go module directory
# uses: actions/checkout@v4
# uses: actions/checkout@v5
#
# # Download coverage artifacts
# - name: Download Coverage Report
# uses: actions/download-artifact@v4
# uses: actions/download-artifact@v5
# with:
# path: artifacts
#
Expand Down Expand Up @@ -321,7 +321,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v4
uses: actions/checkout@v5

- name: Set up Go environment
uses: actions/setup-go@v5
Expand Down Expand Up @@ -368,7 +368,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4
uses: actions/checkout@v5

- name: Set up Go environment
uses: actions/setup-go@v5
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/typos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ jobs:
contents: read
steps:
- name: Checkout Code
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: typos-action
uses: crate-ci/[email protected].1
uses: crate-ci/[email protected].3
6 changes: 3 additions & 3 deletions .github/workflows/website.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
name: 🐳 Dockerize
steps:
- name: Checkout Code
uses: actions/checkout@v4
uses: actions/checkout@v5

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
Expand Down Expand Up @@ -87,7 +87,7 @@ jobs:

steps:
- name: Checkout Code
uses: actions/checkout@v4
uses: actions/checkout@v5

- name: Authorize to GCP service account
uses: google-github-actions/auth@v2
Expand All @@ -114,7 +114,7 @@ jobs:

steps:
- name: Checkout Code
uses: actions/checkout@v4
uses: actions/checkout@v5

- name: Authorize to GCP service account
uses: google-github-actions/auth@v2
Expand Down
180 changes: 180 additions & 0 deletions docs/advanced-guide/oidc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@

# OpenID Connect (OIDC) Middleware in GoFr

This guide shows how to add OpenID Connect (OIDC) authentication to your GoFr applications using the new OIDC middleware and per-provider dynamic discovery with robust caching.

---

## Overview

The OIDC middleware and helpers provide:

- **Dynamic OIDC Discovery:** Per-provider discovery of issuer, JWKS URI, and userinfo endpoint, with robust caching in a struct.
- **JWT Validation:** Out-of-the-box via GoFr’s `EnableOAuth` middleware, including claim checks and JWKS key rotation.
- **Userinfo Fetch Middleware:** Fetches user profile data from the OIDC `userinfo` endpoint, injecting it into the request context.
- **Context Helper:** Retrieves user info easily in handlers.

---

## 1. Set Up OIDC Discovery with Caching (Per-Provider)

Instead of using a single cached global function, create a **DiscoveryCache** for each OIDC provider. You must also use a `context.Context` for timeouts/cancellation.

```

import (
"context"
"time"
"gofr.dev/pkg/gofr/http/middleware"
)

// Create a cache for Google's OIDC provider discovery
cache := middleware.NewDiscoveryCache(
"https://accounts.google.com/.well-known/openid-configuration",
10 * time.Minute, // cache duration
)

// Fetch metadata with context
meta, err := cache.GetMetadata(context.Background())
if err != nil {
// handle error on startup
}

```

This returns per-URL/discovery cached metadata:
- `meta.Issuer`
- `meta.JWKSURI`
- `meta.UserInfoEndpoint`

---

## 2. Enable OAuth Middleware with Discovered Metadata

Apply the discovered JWKS URI and issuer with GoFr's built-in OAuth middleware:

```

app.EnableOAuth(
meta.JWKSURI,
300, // JWKS refresh interval in seconds
jwt.WithIssuer(meta.Issuer),
// jwt.WithAudience("your-audience") // Optional
)

```

- Handles Bearer token extraction, JWT validation, JWKS caching/key rotation.

---

## 3. Register OIDC Userinfo Middleware

After the OAuth middleware, register the userinfo middleware to fetch profile info from the `userinfo` endpoint.

```

app.UseMiddleware(middleware.OIDCUserInfoMiddleware(meta.UserInfoEndpoint))

```

- Userinfo middleware uses the verified Bearer token to call the endpoint and attaches the user info to the request context.

---

## 4. Access User Info in Handlers

Inside your GoFr route handlers, retrieve user info using the helper:

```

userInfo, ok := middleware.GetOIDCUserInfo(ctx.Request().Context())
if !ok {
// Handle missing user info
}
// Use userInfo map for claims like "email", "name", "sub", etc.

```

Example handler returning user info:

```

app.GET("/profile", func(ctx *gofr.Context) (any, error) {
userInfo, ok := middleware.GetOIDCUserInfo(ctx.Request().Context())
if !ok {
return nil, fmt.Errorf("user info not found")
}
return userInfo, nil
})

```

---

## 5. Notes & Best Practices

- **Discovery Cache is per-provider:** Instantiate `DiscoveryCache` for each provider if you work with more than one.
- **Always pass a context:** `GetMetadata` must be called with a valid `context.Context` (e.g., `context.Background()` at startup, request context elsewhere).
- **Middleware order:** Register OAuth middleware before the userinfo middleware.
- **Bearer token extraction:** Follows Go best practices—uses `strings.CutPrefix` and validates non-empty tokens.
- **Documentation and tests:** See codebase and test files (`oidc_test.go`, `discovery_test.go`) for example coverage.

---

## 6. Quick Integration Example

```

import (
"context"
"time"
"gofr.dev/pkg/gofr/http/middleware"
"github.com/golang-jwt/jwt/v5"
)

cache := middleware.NewDiscoveryCache(
"https://accounts.google.com/.well-known/openid-configuration",
10*time.Minute,
)
meta, err := cache.GetMetadata(context.Background())
if err != nil {
log.Fatalf("OIDC discovery failed: %v", err)
}

app.EnableOAuth(
meta.JWKSURI,
300,
jwt.WithIssuer(meta.Issuer),
)

app.UseMiddleware(middleware.OIDCUserInfoMiddleware(meta.UserInfoEndpoint))

app.GET("/profile", func(ctx *gofr.Context) (any, error) {
userInfo, ok := middleware.GetOIDCUserInfo(ctx.Request().Context())
if !ok {
return nil, fmt.Errorf("user info not found")
}
return userInfo, nil
})

```

---

## 7. Summary Table

| Component | Use |
|--------------------------|-----------------------------------------------------------------------------------|
| DiscoveryCache | Per-provider discovery and caching (thread-safe/isolated) |
| GetMetadata (with ctx) | Fetch/cached OIDC metadata |
| EnableOAuth | Configure JWT validation and key management |
| OIDCUserInfoMiddleware | Fetch and inject user profile info |
| GetOIDCUserInfo | Access user info in handlers |

---

This guide covers how to use your contributed OIDC middleware cleanly and idiomatically within GoFr. For more details, check the middleware source files and tests.

---

18 changes: 10 additions & 8 deletions examples/using-add-filestore/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ toolchain go1.24.1
require (
github.com/stretchr/testify v1.10.0
go.uber.org/mock v0.5.2
gofr.dev v1.42.5
gofr.dev v1.43.0
gofr.dev/pkg/gofr/datasource/file/ftp v0.2.1
)

Expand All @@ -34,13 +34,14 @@ require (
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-sql-driver/mysql v1.9.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v5 v5.2.3 // indirect
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
Expand All @@ -56,10 +57,11 @@ require (
github.com/pierrec/lz4/v4 v4.1.22 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.22.0 // indirect
github.com/prometheus/client_golang v1.23.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.65.0 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/prometheus/otlptranslator v0.0.0-20250717125610-8549f4ab4f8f // indirect
github.com/prometheus/procfs v0.17.0 // indirect
github.com/redis/go-redis/extra/rediscmd/v9 v9.11.0 // indirect
github.com/redis/go-redis/extra/redisotel/v9 v9.11.0 // indirect
github.com/redis/go-redis/v9 v9.11.0 // indirect
Expand All @@ -76,7 +78,7 @@ require (
go.opentelemetry.io/otel v1.37.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect
go.opentelemetry.io/otel/exporters/prometheus v0.59.0 // indirect
go.opentelemetry.io/otel/exporters/prometheus v0.59.1 // indirect
go.opentelemetry.io/otel/exporters/zipkin v1.37.0 // indirect
go.opentelemetry.io/otel/metric v1.37.0 // indirect
go.opentelemetry.io/otel/sdk v1.37.0 // indirect
Expand All @@ -92,15 +94,15 @@ require (
golang.org/x/term v0.33.0 // indirect
golang.org/x/text v0.27.0 // indirect
golang.org/x/time v0.12.0 // indirect
google.golang.org/api v0.243.0 // indirect
google.golang.org/api v0.244.0 // indirect
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250715232539-7130f93afb79 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 // indirect
google.golang.org/grpc v1.74.2 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.66.3 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
modernc.org/sqlite v1.38.1 // indirect
modernc.org/sqlite v1.38.2 // indirect
)
Loading