NAV

AuthBricks

AuthBricks is a Go library for building Identity and Access Management solutions. It aims to provide simple primitives and APIs that comply with the best practices in the industry, while remaining flexible enough to be used in a wide range of use cases.

At the moment it implements the following RFCs (planning to get to full OIDC compliance):

Get Started

Basic Setup

First, you need to set up the database and API server. Then you can use the client to manage your services and applications.

package main

import (
	"context"
	
	"go.authbricks.com/bricks/api"
	"go.authbricks.com/bricks/client"
	"go.authbricks.com/bricks/database"
	"github.com/hashicorp/go-hclog"
)

func main() {
	ctx := context.Background()
	
	// 1. Create database connection
	db, err := database.NewPostgres(ctx, "postgres://user:password@localhost:5432/db")
	if err != nil {
		panic(err)
	}
	
	// 2. Create API server with functional options
	a, err := api.New(db, ":8080",
		api.WithLogger(hclog.Default()),
		api.WithBaseURL("https://api.example.com"),
	)
	if err != nil {
		panic(err)
	}
	
	// 3. Create client
	c := client.New(db)
	
	// 4. Run the API server
	if err := a.Run(ctx); err != nil {
		panic(err)
	}
	
	// Now you can use the client to manage your services and applications
	// See the Services section for examples
}

Custom Routes

You can add custom routes to the API server using the WithRoutes option. This allows you to extend the API with your own endpoints and middleware.

package main

import (
	"context"
	"net/http"
	
	"go.authbricks.com/bricks/api"
	"go.authbricks.com/bricks/database"
	"github.com/hashicorp/go-hclog"
	"github.com/labstack/echo/v4"
	"github.com/labstack/echo/v4/middleware"
)

func main() {
	ctx := context.Background()
	
	db, err := database.NewPostgres(ctx, "postgres://user:password@localhost:5432/db")
	if err != nil {
		panic(err)
	}
	
	// Define custom routes
	routes := []api.Route{
		{
			Method: http.MethodGet,
			Path:   "/custom",
			Handler: echo.HandlerFunc(func(c echo.Context) error {
				return c.JSON(http.StatusTeapot, "custom route")
			}),
			Middlewares: []echo.MiddlewareFunc{},
		},
		{
			Method: http.MethodGet,
			Path:   "/custom/with/middleware",
			Handler: echo.HandlerFunc(func(c echo.Context) error {
				return c.JSON(http.StatusTeapot, "custom route with middleware")
			}),
			Middlewares: []echo.MiddlewareFunc{
				middleware.BasicAuth(func(username, password string, c echo.Context) (bool, error) {
					if username == "toni" && password == "hunter2" {
						return true, nil
					}
					return false, nil
				}),
			},
		},
	}
	
	// Create API server with custom routes using functional options
	a, err := api.New(db, ":8080",
		api.WithRoutes(routes),
		api.WithLogger(hclog.Default()),
		api.WithBaseURL("https://api.example.com"),
	)
	if err != nil {
		panic(err)
	}
	
	if err := a.Run(ctx); err != nil {
		panic(err)
	}
}

Each route can specify:

Postgres

Connect to a local postgres database, and start the API server on port 8080.

package main

import (
	"context"
	
	"go.authbricks.com/bricks/api"
	"go.authbricks.com/bricks/database"
	"github.com/hashicorp/go-hclog"
)

func main() {
	ctx := context.Background()
	
	db, err := database.NewPostgres(ctx, "postgres://user:password@localhost:5432/db")
	if err != nil {
		panic(err)
	}
	
	a, err := api.New(db, ":8080",
		api.WithLogger(hclog.Default()),
		api.WithBaseURL("https://api.example.com"),
	)
	if err != nil {
		panic(err)
	}
	
	if err := a.Run(ctx); err != nil {
		panic(err)
	}
}

MySQL

Connect to a local MySQL database, and start the API server on port 8080.

package main

import (
	"context"
	
	"go.authbricks.com/bricks/api"
	"go.authbricks.com/bricks/database"
	"github.com/hashicorp/go-hclog"
)

func main() {
	ctx := context.Background()
	
	db, err := database.NewMySQL(ctx, "user:password@tcp(localhost:3306)/db")
	if err != nil {
		panic(err)
	}
	
	a, err := api.New(db, ":8080",
		api.WithLogger(hclog.Default()),
		api.WithBaseURL("https://api.example.com"),
	)
	if err != nil {
		panic(err)
	}
	
	if err := a.Run(ctx); err != nil {
		panic(err)
	}
}

SQLite

Connect to a SQLite database, and start the API server on port 8080.

package main 

import (
	"context"
	
	"go.authbricks.com/bricks/api"
	"go.authbricks.com/bricks/database"
	"github.com/hashicorp/go-hclog"
)

func main() {
	ctx := context.Background()
	
	db, err := database.NewSQLite(ctx, "file:file.db?_fk=1")
	if err != nil {
		panic(err)
	}
	
	a, err := api.New(db, ":8080",
		api.WithLogger(hclog.Default()),
		api.WithBaseURL("https://api.example.com"),
	)
	if err != nil {
		panic(err)
	}
	
	if err := a.Run(ctx); err != nil {
		panic(err)
	}
}

Define a new Service

All the examples so far have simply started the API server on port 8080. The server does not actually have any endpoints yet. In this section, we will define a new service.

In terms of OAuth / OIDC specifications, you can think of a service as your Authorization server. For example, if your business needs to authenticate both your customers and your employees, you could define two different services called customers and employees.

package main

import (
    "context"
    "crypto/rsa"
    "crypto/rand"
    
    "go.authbricks.com/bricks/api"
    "go.authbricks.com/bricks/client"
    "go.authbricks.com/bricks/config"
    "go.authbricks.com/bricks/config/options/service"
    "go.authbricks.com/bricks/database"
    "github.com/hashicorp/go-hclog"
)

func main() {
    ctx := context.Background()
    
    // 1. Create database connection
    db, err := database.NewPostgres(ctx, "postgres://user:password@localhost:5432/db")
    if err != nil {
        panic(err)
    }
    
    // 2. Create API server with functional options
    a, err := api.New(db, ":8080",
        api.WithLogger(hclog.Default()),
        api.WithBaseURL("https://api.example.com"),
    )
    if err != nil {
        panic(err)
    }
    
    // 3. Create client
    c := client.New(db)
    
    // 4. Generate RSA key for signing
    privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
    if err != nil {
        panic(err)
    }
    
    // 5. Create service configuration using functional options
    serviceConfig := service.New(
        service.WithName("customers"),
        service.WithIdentifier("customers"),
        service.WithDescription("Service for authenticating customers"),
        service.WithServiceMetadataEntry("version", "1.0.0"),
        service.WithServiceMetadataEntry("environment", "production"),
        service.WithAllowedClientMetadata("logo_uri", "client_uri", "policy_uri"),
        service.WithScopes("openid", "profile", "email", "read", "write"),
        service.WithGrantTypes(
            config.GrantTypeAuthorizationCode,
            config.GrantTypeClientCredentials,
            config.GrantTypeRefreshToken,
        ),
        service.WithResponseTypes(
            config.ResponseTypeCode,
            config.ResponseTypeIDToken,
            config.ResponseTypeCodeIDToken,
        ),
        service.WithAuthorizationEndpoint(config.AuthorizationEndpoint{
            Enabled: true,
        }),
        service.WithTokenEndpoint(config.TokenEndpoint{
            Enabled: true,
        }),
        service.WithUserInfoEndpoint(config.UserInfoEndpoint{
            Enabled: true,
        }),
        service.WithJWKSEndpoint(config.JWKSEndpoint{
            Enabled: true,
        }),
        service.WithWellKnownEndpoint(config.WellKnownEndpoint{
            Enabled: true,
        }),
        service.WithLoginEndpoint(config.LoginEndpoint{
            Enabled: true,
        }),
        service.WithConnection(config.Connection{
            EmailPassword: &config.EmailPasswordConnection{
                Enabled: true,
            },
            OIDC: []config.OIDCConnection{
                {
                    Name: "google",
                    Enabled: true,
                    ClientID: "your-google-client-id",
                    ClientSecret: "your-google-client-secret",
                    Scopes: []string{"openid", "profile", "email"},
                    RedirectURI: "http://localhost:8080/callback",
                    WellKnownEndpoint: "https://accounts.google.com/.well-known/openid-configuration",
                },
                {
                    Name: "github",
                    Enabled: true,
                    ClientID: "your-github-client-id",
                    ClientSecret: "your-github-client-secret",
                    Scopes: []string{"read:user", "user:email"},
                    RedirectURI: "http://localhost:8080/callback",
                    WellKnownEndpoint: "https://github.com/.well-known/openid-configuration",
                },
            },
        }),
        service.WithKey(privateKey),
    )
    
    // 6. Create service using the client
    svc, err := c.CreateOrUpdateService(context.Background(), serviceConfig)
    if err != nil {
        panic(err)
    }
    
    // 7. Start the API server
    if err := a.Run(ctx); err != nil {
        panic(err)
    }
}

Define an Application

Once you have defined a service, you can create a new application (also known as OAuth Client) for that service.

package main

import (
    "context"
    
    "go.authbricks.com/bricks/api"
    "go.authbricks.com/bricks/client"
    "go.authbricks.com/bricks/config"
    "go.authbricks.com/bricks/config/options/application"
    "go.authbricks.com/bricks/database"
    "github.com/hashicorp/go-hclog"
)

func main() {
    ctx := context.Background()
    
    // 1. Create database connection
    db, err := database.NewPostgres(ctx, "postgres://user:password@localhost:5432/db")
    if err != nil {
        panic(err)
    }
    
    // 2. Create API server with functional options
    a, err := api.New(db, ":8080",
        api.WithLogger(hclog.Default()),
        api.WithBaseURL("https://api.example.com"),
    )
    if err != nil {
        panic(err)
    }
    
    // 3. Create client
    c := client.New(db)
    
    // 4. Get service
    svc, err := c.GetService(context.Background(), "customers")
    if err != nil {
        panic(err)
    }
    
    // 5. Create application configuration using functional options
    appConfig := application.New(
        application.WithName("myapp"),
        application.WithService("customers"),
        application.WithDescription("My OAuth2/OIDC Application"),
        application.WithPublic(false),
        application.WithRedirectURIs(
            "http://localhost:8080/callback",
            "https://myapp.com/callback",
        ),
        application.WithResponseTypes(
            config.ResponseTypeCode,
            config.ResponseTypeIDToken,
            config.ResponseTypeCodeIDToken,
        ),
        application.WithGrantTypes(
            config.GrantTypeAuthorizationCode,
            config.GrantTypeRefreshToken,
        ),
        application.WithPKCERequired(true),
        application.WithS256CodeChallengeMethodRequired(true),
        application.WithAllowedAuthenticationMethods("client_secret_basic", "client_secret_post"),
        application.WithScopes("openid", "profile", "email", "read", "write"),
    )
    
    // 6. Create application using the client
    app, err := c.CreateOrUpdateApplication(context.Background(), appConfig)
    if err != nil {
        panic(err)
    }
    
    // 7. Start the API server
    if err := a.Run(ctx); err != nil {
        panic(err)
    }
}

Credentials

Once you have created an application, you can generate credentials for it.

package main

import (
    "context"
    
    "go.authbricks.com/bricks/api"
    "go.authbricks.com/bricks/client"
    "go.authbricks.com/bricks/config"
    "go.authbricks.com/bricks/config/options/credentials"
    "go.authbricks.com/bricks/crypto"
    "go.authbricks.com/bricks/database"
    "github.com/hashicorp/go-hclog"
)

func main() {
    ctx := context.Background()
    
    // 1. Create database connection
    db, err := database.NewPostgres(ctx, "postgres://user:password@localhost:5432/db")
    if err != nil {
        panic(err)
    }
    
    // 2. Create API server with functional options
    a, err := api.New(db, ":8080",
        api.WithLogger(hclog.Default()),
        api.WithBaseURL("https://api.example.com"),
    )
    if err != nil {
        panic(err)
    }
    
    // 3. Create client
    c := client.New(db)
    
    // 4. Get application
    app, err := c.GetApplication(context.Background(), "myapp")
    if err != nil {
        panic(err)
    }
    
    // 5. Create credentials configuration using functional options
    credentialsConfig := credentials.New(
        credentials.WithApplication("myapp"),
        credentials.WithClientID(crypto.GenerateClientID()),
        credentials.WithClientSecret(crypto.GenerateClientSecret()),
    )
    
    // 6. Create credentials using the client
    creds, err := c.CreateOrUpdateCredentials(context.Background(), credentialsConfig)
    if err != nil {
        panic(err)
    }
    
    // 7. Start the API server
    if err := a.Run(ctx); err != nil {
        panic(err)
    }
}

Configuration Patterns

AuthBricks provides flexible configuration patterns to help you set up services, applications, and credentials. This section covers the different approaches available.

Functional Options Pattern

AuthBricks implements the functional options pattern, which provides a clean and extensible way to configure components. This pattern offers several advantages:

Service Configuration

The service functional options are available in the go.authbricks.com/bricks/config/options/service package:

import "go.authbricks.com/bricks/config/options/service"

serviceConfig := service.New(
    service.WithName("my-service"),
    service.WithIdentifier("my-service-id"),
    service.WithDescription("My OAuth2 service"),
    service.WithScopes("openid", "profile", "email"),
    service.WithGrantTypes(
        config.GrantTypeAuthorizationCode,
        config.GrantTypeRefreshToken,
    ),
    service.WithResponseTypes(config.ResponseTypeCode),
    // Add more options as needed
)

Available service options:

Application Configuration

The application functional options are available in the go.authbricks.com/bricks/config/options/application package:

import "go.authbricks.com/bricks/config/options/application"

appConfig := application.New(
    application.WithName("my-app"),
    application.WithService("my-service"),
    application.WithDescription("My OAuth2 application"),
    application.WithPublic(false),
    application.WithRedirectURIs("https://example.com/callback"),
    application.WithPKCERequired(true),
    // Add more options as needed
)

Available application options:

Credentials Configuration

The credentials functional options are available in the go.authbricks.com/bricks/config/options/credentials package:

import "go.authbricks.com/bricks/config/options/credentials"

credentialsConfig := credentials.New(
    credentials.WithApplication("my-app"),
    credentials.WithClientID("my-client-id"),
    credentials.WithClientSecret("my-client-secret"),
)

Available credentials options:

API Configuration

The API functional options are available directly in the go.authbricks.com/bricks/api package:

import (
    "context"
    
    "go.authbricks.com/bricks/api"
    "go.authbricks.com/bricks/database"
    "github.com/hashicorp/go-hclog"
)

func main() {
    db, err := database.NewPostgres(context.Background(), "postgres://user:password@localhost:5432/db")
    if err != nil {
        panic(err)
    }
    
    // Create API server with functional options
    a, err := api.New(db, ":8080",
        api.WithLogger(hclog.Default()),
        api.WithBaseURL("https://api.example.com"),
        api.WithTLSEnabled(true),
        api.WithCertificateFilePath("/path/to/cert.pem"),
        api.WithKeyFilePath("/path/to/key.pem"),
    )
    if err != nil {
        panic(err)
    }
    
    a.ListenAndServe()
}

Available API options:

Direct Configuration

For cases where you prefer or need direct struct initialization, you can still use the config structs directly:

import "go.authbricks.com/bricks/config"

func main() {
serviceConfig := config.Service{
Name:        "my-service",
Identifier:  "my-service-id",
Description: "My OAuth2 service",
Scopes:      []string{"openid", "profile", "email"},
GrantTypes: []string{
        config.GrantTypeAuthorizationCode,
		config.GrantTypeRefreshToken,
    },
    ResponseTypes: []string{config.ResponseTypeCode},
    // ... other fields
    }
}

Errors

The AuthBricks API uses the following error codes:

4xx

Error CodeMeaning
400Bad Request – The request was malformed or contained invalid parameters
401Unauthorized – The request requires authentication
403Forbidden – The authenticated user does not have permission to access the requested resource
404Not Found – The requested resource could not be found
405Method Not Allowed – The HTTP method is not supported for the requested resource
406Not Acceptable – The requested response format is not supported
409Conflict – The request conflicts with the current state of the resource
415Unsupported Media Type – The request media type is not supported
429Too Many Requests – The request rate limit has been exceeded

5xx

Error CodeMeaning
500Internal Server Error – An unexpected error occurred on the server
503Service Unavailable – The service is temporarily unavailable