Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 20 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
name: Ci

on:
pull_request:
branches: [ main, master, development ]

jobs:

lint:
name: Lint go code with golangci
runs-on: ubuntu-latest
Expand All @@ -21,7 +18,25 @@ jobs:
ls -la
make lint
shell: bash


build:
name: Build project
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Build example

runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: setup Go
uses: actions/setup-go@v5
with:
go-version: '1.23.4'
- name: Make repo safe
run: git config --global --add safe.directory /__w/gauth/gauth
- name: Build
run: |
make build-examples
shell: bash

test:
name: Run ci-tests
Expand All @@ -34,7 +49,7 @@ jobs:
- name: setup Go
uses: actions/setup-go@v5
with:
go-version: '1.22.2' #
go-version: '1.23.4' #
- name: Make repo safe
run: git config --global --add safe.directory /__w/gauth/gauth
- name: Run tests
Expand Down
54 changes: 49 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,50 @@
.PHONY: test
test:
go test ./... -v
lint:
GOFLAGS=-buildvcs=false golangci-lint run --timeout 5m0s -v
.DEFAULT_GOAL := dev

.PHONY: build-basic
build-basic:
cd examples/basic && go build ./...

.PHONY: build-validation
build-validation:
cd examples/validation && go build ./...

.PHONY: build-m2m
build-m2m:
cd examples/m2m && go build ./...

.PHONY: test-basic
test-basic:
cd examples/basic && go test ./... -v

.PHONY: test-validation
test-validation:
cd examples/validation && go test ./... -v

.PHONY: test-m2m
test-m2m:
cd examples/m2m && go test ./... -v

.PHONY: lint-basic
lint-basic:
cd examples/basic && GOFLAGS=-buildvcs=false golangci-lint run --timeout 5m0s -v

.PHONY: lint-validation
lint-validation:
cd examples/validation && GOFLAGS=-buildvcs=false golangci-lint run --timeout 5m0s -v

.PHONY: lint-m2m
lint-m2m:
cd examples/m2m && GOFLAGS=-buildvcs=false golangci-lint run --timeout 5m0s -v

.PHONY: build-examples
build-examples: build-basic build-validation build-m2m

.PHONY: test
test: test-basic test-validation test-m2m

.PHONY: lint
lint: lint-basic lint-validation lint-m2m

.PHONY: dev
dev:
@echo "Development target (customize as needed)"
5 changes: 4 additions & 1 deletion examples/basic/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,8 @@ func main() {
})
})

router.Run(":8080")
err = router.Run(":8080")
if err != nil {
log.Fatal("Could not start server")
}
}
3 changes: 3 additions & 0 deletions examples/m2m/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module example

go 1.23.4
125 changes: 125 additions & 0 deletions examples/m2m/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package main

import (
"crypto/tls"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"net/url"
"os"
"strconv"
"strings"
)

// TokenConfig represents the configuration for token retrieval
type TokenConfig struct {
BaseURL string
ClientID string
ServiceAccount string
ServiceToken string
SkipTLSVerify bool
}

// TokenResponse represents the structure of the token response
type TokenResponse struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in"`
}

// NewTokenConfig creates a configuration from environment variables
func NewTokenConfig() *TokenConfig {
skipTlsVerify, _ := strconv.ParseBool(getEnv("SKIP_TLS_VERIFY", "false"))
return &TokenConfig{
BaseURL: getEnv("BASE_URL", "https://localhost:9443"),
ClientID: getEnv("CLIENT_ID", ""),
ServiceAccount: getEnv("SERVICE_ACCOUNT", ""),
ServiceToken: getEnv("SERVICE_TOKEN", ""),
SkipTLSVerify: skipTlsVerify,
}
}

// getEnv retrieves an environment variable with a default value
func getEnv(key, defaultValue string) string {
value := os.Getenv(key)
if value == "" {
return defaultValue
}
return value
}

// getAccessToken retrieves an access token from the specified endpoint
func getAccessToken(config *TokenConfig) (*TokenResponse, error) {
// Validate required configuration
if config.ClientID == "" || config.ServiceAccount == "" || config.ServiceToken == "" {
return nil, fmt.Errorf("missing required configuration")
}

// Create a custom HTTP client with optional TLS verification
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: config.SkipTLSVerify},
}
client := &http.Client{Transport: tr}

// Prepare the request body
data := url.Values{
"grant_type": {"client_credentials"},
"client_id": {config.ClientID},
"username": {config.ServiceAccount},
"password": {config.ServiceToken},
"scope": {"profile"},
}

// Create the HTTP request
fullURL := strings.TrimRight(config.BaseURL, "/") + "/application/o/token/"
req, err := http.NewRequest("POST", fullURL, strings.NewReader(data.Encode()))
if err != nil {
return nil, fmt.Errorf("error creating request: %w", err)
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

// Send the request
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("error sending request: %w", err)
}
defer resp.Body.Close()

// Read the response body
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("error reading response: %w", err)
}

// Check for successful response
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("token retrieval failed (status %d): %s",
resp.StatusCode, string(body))
}

// Parse the JSON response
var tokenResp TokenResponse
if err := json.Unmarshal(body, &tokenResp); err != nil {
return nil, fmt.Errorf("error parsing token response: %w\nRaw response: %s", err, string(body))
}

return &tokenResp, nil
}

func main() {
// Load configuration from environment
config := NewTokenConfig()

// Retrieve access token
tokenResp, err := getAccessToken(config)
if err != nil {
log.Fatalf("Failed to obtain access token: %v", err)
}

// Print token details
fmt.Println("Access Token:", tokenResp.AccessToken)
fmt.Println("Token Type:", tokenResp.TokenType)
fmt.Println("Expires In:", tokenResp.ExpiresIn, "seconds")
}
68 changes: 68 additions & 0 deletions examples/m2m/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@

# Authentik OAuth Token Retrieval Client (M2M)

This repository provides an example implementation of a Machine-to-Machine (M2M) application using the [Authentik OAuth2 Client Credentials](https://docs.goauthentik.io/docs/add-secure-apps/providers/oauth2/client_credentials) provider.

## Configuration

### Environment Variables

The following environment variables must be set to configure the application:

- **`BASE_URL`**:
Base URL of the authentication server.
*Default*: `https://localhost:9443`
*Example*: `https://your-auth-server.com`

- **`CLIENT_ID`**:
OAuth client identifier.
*Required* (no default value).
*Example*: `WxUcBMGZdI7c0e5oYp6mYdEd64acpXSuWKh8zBH5`

- **`SERVICE_ACCOUNT`**:
Service account username.
*Required* (no default value).
*Example*: `test2test`

- **`SERVICE_TOKEN`**:
Service account password/token.
*Required* (no default value).
*Example*: `2Dzvbs5O7wjfaUj1k1YSqctRgVA5hDtsi18xIrmKeIn1pV0rn4G5nuuFQUwH`

- **`SKIP_TLS_VERIFY`**:
Disable TLS certificate verification.
*Default*: `false`

## Usage

### Setting Environment Variables

Set the required environment variables before running the application. For example:

```bash
export BASE_URL=https://localhost:9443
export CLIENT_ID=WxUcBMGZdI7c0e5oYp6mYdEd64acpXSuWKh8zBH5
export SERVICE_ACCOUNT=test2test
export SERVICE_TOKEN=2Dzvbs5O7wjfaUj1k1YSqctRgVA5hDtsi18xIrmKeIn1pV0rn4G5nuuFQUwH
export SKIP_TLS_VERIFY=true
```

## Running the script

Run the script using the following command:

`go run main.go`

Example Output

When the application is successfully run, you will see output similar to the following:

```
Access Token: <retrieved-access-token>
Token Type: Bearer
Expires In: 3600 seconds
```

The retrieved access token can then be used for authorized access to other resources.

For more details, refer to the [Authentik](https://goauthentik.io/) Documentation.
5 changes: 4 additions & 1 deletion examples/validation/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,8 @@ func main() {
})
})

router.Run(":8080")
err = router.Run(":8080")
if err != nil {
log.Fatal("Could not start server")
}
}
Loading