OAuth Apps¶
OAuth apps represent client applications that can request access to your API.
Schema¶
The cf_oauth_apps table contains:
| Field | Type | Description |
|---|---|---|
id |
UUID | Internal ID |
client_id |
string | Public identifier |
name |
string | Display name |
description |
string | App description |
logo_url |
string | App logo |
app_type |
enum | web, spa, native, service, machine |
owner_id |
UUID | Creating user |
organization_id |
UUID | Owning organization |
redirect_uris |
[]string | Allowed redirects |
allowed_scopes |
[]string | Requestable scopes |
allowed_grants |
[]string | Allowed grant types |
access_token_ttl |
int | Token lifetime (seconds) |
refresh_token_ttl |
int | Refresh lifetime (seconds) |
first_party |
bool | Skip consent screen |
public |
bool | No client secret |
active |
bool | App status |
App Types¶
| Type | Description | Secret | PKCE |
|---|---|---|---|
web |
Traditional server-rendered apps | Required | Optional |
spa |
Single-page JavaScript apps | None | Required |
native |
Mobile or desktop apps | None | Required |
service |
Backend services | Required | No |
machine |
Automated systems/CI | Required | No |
Creating Apps¶
First-Party SPA¶
app, err := client.OAuthApp.Create().
SetClientID(generateClientID()).
SetName("My Frontend App").
SetAppType("spa").
SetPublic(true).
SetFirstParty(true).
SetRedirectUris([]string{
"http://localhost:3000/callback",
"https://app.example.com/callback",
}).
SetAllowedScopes([]string{"openid", "profile", "email", "api"}).
SetAllowedGrants([]string{"authorization_code", "refresh_token"}).
SetAllowedResponseTypes([]string{"code"}).
SetOwnerID(adminUserID).
Save(ctx)
Third-Party Web App¶
app, err := client.OAuthApp.Create().
SetClientID(generateClientID()).
SetName("Partner Integration").
SetDescription("Integration by Acme Corp").
SetAppType("web").
SetPublic(false). // Requires secret
SetFirstParty(false). // Shows consent screen
SetRedirectUris([]string{"https://partner.com/oauth/callback"}).
SetAllowedScopes([]string{"read:data"}).
SetAllowedGrants([]string{"authorization_code", "refresh_token"}).
SetOwnerID(developerUserID).
SetOrganizationID(partnerOrgID).
Save(ctx)
// Generate and store secret
secret := generateSecret()
hash := hashSecret(secret)
_, err = client.OAuthAppSecret.Create().
SetAppID(app.ID).
SetSecretHash(hash).
SetSecretPrefix(secret[:8]).
Save(ctx)
// Return secret to developer (only time visible)
return app, secret
Service Account App¶
app, err := client.OAuthApp.Create().
SetClientID("my-service").
SetName("Background Worker").
SetAppType("service").
SetPublic(false).
SetAllowedScopes([]string{"admin:all"}).
SetAllowedGrants([]string{"client_credentials"}).
SetOwnerID(systemUserID).
Save(ctx)
Client ID Generation¶
Generate unique, URL-safe client IDs:
import (
"crypto/rand"
"encoding/base64"
)
func generateClientID() string {
b := make([]byte, 16)
rand.Read(b)
return base64.RawURLEncoding.EncodeToString(b)
}
Secret Management¶
Generate Secret¶
func generateSecret() string {
b := make([]byte, 32)
rand.Read(b)
return base64.RawURLEncoding.EncodeToString(b)
}
Hash Secret¶
import "golang.org/x/crypto/argon2"
func hashSecret(secret string) string {
salt := make([]byte, 16)
rand.Read(salt)
hash := argon2.IDKey([]byte(secret), salt, 3, 64*1024, 4, 32)
// Return encoded hash with parameters
return fmt.Sprintf("$argon2id$v=19$m=65536,t=3,p=4$%s$%s",
base64.RawStdEncoding.EncodeToString(salt),
base64.RawStdEncoding.EncodeToString(hash),
)
}
Rotate Secret¶
// Generate new secret
newSecret := generateSecret()
newHash := hashSecret(newSecret)
// Add new secret (old one still works)
_, err := client.OAuthAppSecret.Create().
SetAppID(appID).
SetSecretHash(newHash).
SetSecretPrefix(newSecret[:8]).
Save(ctx)
// Later: revoke old secret
_, err = client.OAuthAppSecret.Update().
Where(oauthappsecret.IDEQ(oldSecretID)).
SetRevoked(true).
SetRevokedAt(time.Now()).
Save(ctx)
Querying Apps¶
By Client ID¶
User's Apps¶
Organization's Apps¶
Token Configuration¶
Custom TTLs¶
app, err := client.OAuthApp.Create().
// ...
SetAccessTokenTTL(3600). // 1 hour
SetRefreshTokenTTL(2592000). // 30 days
SetRefreshTokenRotation(true).
Save(ctx)
Disable Refresh Tokens¶
app, err := client.OAuthApp.Create().
// ...
SetAllowedGrants([]string{"authorization_code"}). // No refresh_token
Save(ctx)
Deactivating Apps¶
// Soft-deactivate (tokens remain valid)
_, err := client.OAuthApp.UpdateOneID(appID).
SetActive(false).
Save(ctx)
// Hard revoke (invalidate all tokens)
_, err = client.OAuthApp.UpdateOneID(appID).
SetActive(false).
SetRevokedAt(time.Now()).
Save(ctx)
// Also revoke all tokens
_, err = client.OAuthToken.Update().
Where(oauthtoken.AppIDEQ(appID)).
SetRevoked(true).
Save(ctx)