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):
- OAuth 2.0 Authorization Code Grant (RFC 6749)
- OAuth 2.0 Client Credentials Grant (RFC 6749)
- OAuth 2.0 Refresh Token Grant (RFC 6749)
- OIDC Hybrid Flow (OIDC Core 1.0)
- PKCE Support (RFC 7636)
- JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens (RFC 9068)
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:
Method
: The HTTP method (GET, POST, etc.)Path
: The URL path for the routeHandler
: The Echo handler function that processes the requestMiddlewares
: Optional middleware functions to apply to the route
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:
- Readability: Options clearly describe what they configure
- Flexibility: Only specify the options you need
- Extensibility: New options can be added without breaking existing code
- Type Safety: Compile-time checking of configuration parameters
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:
WithName(string)
- Set the service nameWithIdentifier(string)
- Set the service identifierWithDescription(string)
- Set the service descriptionWithServiceMetadata(ServiceMetadata)
- Set all service metadataWithServiceMetadataEntry(key, value string)
- Add a single metadata entryWithAllowedClientMetadata(...string)
- Set allowed client metadata fieldsWithAllowedClientMetadataEntry(string)
- Add a single allowed metadata fieldWithScopes(...string)
- Set all supported scopesWithScope(string)
- Add a single scopeWithGrantTypes(...string)
- Set all supported grant typesWithGrantType(string)
- Add a single grant typeWithResponseTypes(...string)
- Set all supported response typesWithResponseType(string)
- Add a single response typeWithAuthorizationEndpoint(AuthorizationEndpoint)
- Configure authorization endpointWithIntrospectionEndpoint(IntrospectionEndpoint)
- Configure introspection endpointWithTokenEndpoint(TokenEndpoint)
- Configure token endpointWithUserInfoEndpoint(UserInfoEndpoint)
- Configure userinfo endpointWithJWKSEndpoint(JWKSEndpoint)
- Configure JWKS endpointWithWellKnownEndpoint(WellKnownEndpoint)
- Configure well-known endpointWithLoginEndpoint(LoginEndpoint)
- Configure login endpointWithConnection(Connection)
- Set connection configurationWithKeys(...crypto.PrivateKey)
- Set all signing keysWithKey(crypto.PrivateKey)
- Add a single signing key
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:
WithName(string)
- Set the application nameWithService(string)
- Set the parent service nameWithDescription(string)
- Set the application descriptionWithPublic(bool)
- Set whether the application is public (no client secret)WithRedirectURIs(...string)
- Set all redirect URIsWithRedirectURI(string)
- Add a single redirect URIWithResponseTypes(...string)
- Set all supported response typesWithResponseType(string)
- Add a single response typeWithGrantTypes(...string)
- Set all supported grant typesWithGrantType(string)
- Add a single grant typeWithPKCERequired(bool)
- Set whether PKCE is requiredWithS256CodeChallengeMethodRequired(bool)
- Set whether S256 PKCE method is requiredWithAllowedAuthenticationMethods(...string)
- Set all allowed authentication methodsWithAllowedAuthenticationMethod(string)
- Add a single authentication methodWithScopes(...string)
- Set all allowed scopesWithScope(string)
- Add a single scope
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:
WithApplication(string)
- Set the application nameWithClientID(string)
- Set the OAuth2 client IDWithClientSecret(string)
- Set the OAuth2 client secret
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:
WithLogger(hclog.Logger)
- Set custom loggerWithBaseURL(string)
- Set the base URL for the APIWithTLSEnabled(bool)
- Enable/disable TLSWithCertificate([]byte)
- Set TLS certificate directlyWithKey([]byte)
- Set TLS private key directlyWithCertificateFilePath(string)
- Set path to TLS certificate fileWithKeyFilePath(string)
- Set path to TLS private key fileWithRoutes([]Route)
- Add custom routesWithAccessTokenActions([]Action)
- Set access token actionsWithIDTokenActions([]Action)
- Set ID token actions
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 Code | Meaning |
---|---|
400 | Bad Request – The request was malformed or contained invalid parameters |
401 | Unauthorized – The request requires authentication |
403 | Forbidden – The authenticated user does not have permission to access the requested resource |
404 | Not Found – The requested resource could not be found |
405 | Method Not Allowed – The HTTP method is not supported for the requested resource |
406 | Not Acceptable – The requested response format is not supported |
409 | Conflict – The request conflicts with the current state of the resource |
415 | Unsupported Media Type – The request media type is not supported |
429 | Too Many Requests – The request rate limit has been exceeded |
5xx
Error Code | Meaning |
---|---|
500 | Internal Server Error – An unexpected error occurred on the server |
503 | Service Unavailable – The service is temporarily unavailable |