Integration
Go
AgeOnce integration with Go (net/http, Gin, Echo)
Go Integration
Complete AgeOnce integration example with Go.
Install dependencies
go get github.com/golang-jwt/jwt/v5
go get github.com/gorilla/sessionsConfiguration
// config.go
package main
import "os"
type Config struct {
ClientID string
ClientSecret string
RedirectURI string
ApiURL string
}
func LoadConfig() *Config {
apiURL := os.Getenv("AGEONCE_API_URL")
if apiURL == "" {
apiURL = "https://app.ageonce.com"
}
return &Config{
ClientID: os.Getenv("AGEONCE_CLIENT_ID"),
ClientSecret: os.Getenv("AGEONCE_CLIENT_SECRET"),
RedirectURI: os.Getenv("AGEONCE_REDIRECT_URI"),
ApiURL: apiURL,
}
}AgeOnce client
// ageonce.go
package main
import (
"bytes"
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"net/url"
)
type AgeOnce struct {
config *Config
}
type TokenResponse struct {
AgeToken string `json:"age_token"`
TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in"`
}
type ValidateResponse struct {
Valid bool `json:"valid"`
Payload map[string]interface{} `json:"payload"`
}
func NewAgeOnce(config *Config) *AgeOnce {
return &AgeOnce{config: config}
}
func (a *AgeOnce) GenerateVerifyURL() (string, string, error) {
stateBytes := make([]byte, 16)
if _, err := rand.Read(stateBytes); err != nil {
return "", "", err
}
state := hex.EncodeToString(stateBytes)
params := url.Values{}
params.Set("client_id", a.config.ClientID)
params.Set("redirect_uri", a.config.RedirectURI)
params.Set("state", state)
verifyURL := fmt.Sprintf("%s/verify?%s", a.config.ApiURL, params.Encode())
return verifyURL, state, nil
}
func (a *AgeOnce) ExchangeCodeForToken(code string) (*TokenResponse, error) {
body := map[string]string{
"client_id": a.config.ClientID,
"client_secret": a.config.ClientSecret,
"code": code,
"redirect_uri": a.config.RedirectURI,
}
jsonBody, _ := json.Marshal(body)
resp, err := http.Post(
a.config.ApiURL+"/api/oauth/token",
"application/json",
bytes.NewBuffer(jsonBody),
)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var tokenResp TokenResponse
if err := json.NewDecoder(resp.Body).Decode(&tokenResp); err != nil {
return nil, err
}
return &tokenResp, nil
}
func (a *AgeOnce) ValidateToken(token string) (*ValidateResponse, error) {
body := map[string]string{"token": token}
jsonBody, _ := json.Marshal(body)
resp, err := http.Post(
a.config.ApiURL+"/api/oauth/validate",
"application/json",
bytes.NewBuffer(jsonBody),
)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var validateResp ValidateResponse
if err := json.NewDecoder(resp.Body).Decode(&validateResp); err != nil {
return nil, err
}
return &validateResp, nil
}Usage examples
// main.go
package main
import (
"log"
"net/http"
"github.com/gorilla/sessions"
)
var store = sessions.NewCookieStore([]byte("your-secret-key"))
var ageonce *AgeOnce
func main() {
config := LoadConfig()
ageonce = NewAgeOnce(config)
http.HandleFunc("/verify", verifyHandler)
http.HandleFunc("/callback", callbackHandler)
http.HandleFunc("/protected", protectedHandler)
log.Println("Server starting on :3000")
log.Fatal(http.ListenAndServe(":3000", nil))
}
func verifyHandler(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, "session")
verifyURL, state, err := ageonce.GenerateVerifyURL()
if err != nil {
http.Error(w, "Failed to generate URL", http.StatusInternalServerError)
return
}
session.Values["oauth_state"] = state
session.Save(r, w)
http.Redirect(w, r, verifyURL, http.StatusFound)
}
func callbackHandler(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, "session")
code := r.URL.Query().Get("code")
state := r.URL.Query().Get("state")
// Verify state
if state != session.Values["oauth_state"] {
http.Error(w, "Invalid state", http.StatusBadRequest)
return
}
tokenResp, err := ageonce.ExchangeCodeForToken(code)
if err != nil {
http.Error(w, "Token exchange failed", http.StatusInternalServerError)
return
}
validateResp, err := ageonce.ValidateToken(tokenResp.AgeToken)
if err != nil {
http.Error(w, "Token validation failed", http.StatusInternalServerError)
return
}
if validateResp.Valid {
ageVerified, _ := validateResp.Payload["age_verified"].(bool)
if ageVerified {
session.Values["age_verified"] = true
session.Save(r, w)
http.Redirect(w, r, "/protected", http.StatusFound)
return
}
}
http.Error(w, "Age verification failed", http.StatusForbidden)
}
func protectedHandler(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, "session")
if session.Values["age_verified"] != true {
http.Redirect(w, r, "/verify", http.StatusFound)
return
}
w.Write([]byte("Welcome to age-restricted content!"))
}package main
import (
"net/http"
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
)
var ageonce *AgeOnce
func main() {
config := LoadConfig()
ageonce = NewAgeOnce(config)
r := gin.Default()
store := cookie.NewStore([]byte("secret"))
r.Use(sessions.Sessions("session", store))
r.GET("/verify", verifyHandler)
r.GET("/callback", callbackHandler)
r.GET("/protected", requireAgeVerification(), protectedHandler)
r.Run(":3000")
}
func verifyHandler(c *gin.Context) {
session := sessions.Default(c)
verifyURL, state, _ := ageonce.GenerateVerifyURL()
session.Set("oauth_state", state)
session.Save()
c.Redirect(http.StatusFound, verifyURL)
}
func callbackHandler(c *gin.Context) {
session := sessions.Default(c)
code := c.Query("code")
state := c.Query("state")
if state != session.Get("oauth_state") {
c.String(http.StatusBadRequest, "Invalid state")
return
}
tokenResp, _ := ageonce.ExchangeCodeForToken(code)
validateResp, _ := ageonce.ValidateToken(tokenResp.AgeToken)
if validateResp.Valid {
if ageVerified, ok := validateResp.Payload["age_verified"].(bool); ok && ageVerified {
session.Set("age_verified", true)
session.Save()
c.Redirect(http.StatusFound, "/protected")
return
}
}
c.String(http.StatusForbidden, "Age verification failed")
}
func requireAgeVerification() gin.HandlerFunc {
return func(c *gin.Context) {
session := sessions.Default(c)
if session.Get("age_verified") != true {
c.Redirect(http.StatusFound, "/verify")
c.Abort()
return
}
c.Next()
}
}
func protectedHandler(c *gin.Context) {
c.String(http.StatusOK, "Welcome to age-restricted content!")
}package main
import (
"net/http"
"github.com/gorilla/sessions"
"github.com/labstack/echo-contrib/session"
"github.com/labstack/echo/v4"
)
var ageonce *AgeOnce
func main() {
config := LoadConfig()
ageonce = NewAgeOnce(config)
e := echo.New()
e.Use(session.Middleware(sessions.NewCookieStore([]byte("secret"))))
e.GET("/verify", verifyHandler)
e.GET("/callback", callbackHandler)
protected := e.Group("/protected")
protected.Use(requireAgeVerification)
protected.GET("", protectedHandler)
e.Start(":3000")
}
func verifyHandler(c echo.Context) error {
sess, _ := session.Get("session", c)
verifyURL, state, _ := ageonce.GenerateVerifyURL()
sess.Values["oauth_state"] = state
sess.Save(c.Request(), c.Response())
return c.Redirect(http.StatusFound, verifyURL)
}
func callbackHandler(c echo.Context) error {
sess, _ := session.Get("session", c)
code := c.QueryParam("code")
state := c.QueryParam("state")
if state != sess.Values["oauth_state"] {
return c.String(http.StatusBadRequest, "Invalid state")
}
tokenResp, _ := ageonce.ExchangeCodeForToken(code)
validateResp, _ := ageonce.ValidateToken(tokenResp.AgeToken)
if validateResp.Valid {
if ageVerified, ok := validateResp.Payload["age_verified"].(bool); ok && ageVerified {
sess.Values["age_verified"] = true
sess.Save(c.Request(), c.Response())
return c.Redirect(http.StatusFound, "/protected")
}
}
return c.String(http.StatusForbidden, "Age verification failed")
}
func requireAgeVerification(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
sess, _ := session.Get("session", c)
if sess.Values["age_verified"] != true {
return c.Redirect(http.StatusFound, "/verify")
}
return next(c)
}
}
func protectedHandler(c echo.Context) error {
return c.String(http.StatusOK, "Welcome to age-restricted content!")
}Go provides excellent performance for high-load scenarios.