Haijun Platform Docs
EN

Okta dapat bertindak sebagai penyedia identitas workload dengan menerbitkan token akses OIDC ke service application (aplikasi layanan) melalui grant OAuth 2.0 client_credentials. Workload Anda melakukan autentikasi ke Okta (biasanya dengan private_key_jwt, sehingga tidak ada rahasia bersama yang disimpan), menerima JSON Web Token (JWT) yang ditandatangani, dan menukarkan JWT tersebut dengan Juglow untuk mendapatkan token akses berumur pendek.

URL issuer server otorisasi Okta berbentuk https://.okta.com/oauth2/. Jika Anda menggunakan server default bawaan, path-nya adalah /oauth2/default.

Note: Anda harus menggunakan custom authorization server (server otorisasi kustom) Okta (termasuk yang default). Token yang diterbitkan langsung oleh server otorisasi org Okta (endpoint /oauth2/v1/token tanpa ID server otorisasi di path) tidak dapat divalidasi oleh pihak eksternal karena Okta tidak memublikasikan kunci penandatanganan untuk token tersebut.

Ada banyak cara untuk mengonfigurasi dan melakukan autentikasi ke Okta yang berada di luar cakupan dokumentasi ini. Pastikan bahwa konfigurasi dan mekanisme autentikasi Anda mengikuti panduan dan praktik keamanan perusahaan Anda.

Prasyarat

  • Pemahaman tentang konsep WIF: service account, federation issuer, dan federation rule.
  • Organisasi Okta dengan API Access Management yang diaktifkan (diperlukan untuk server otorisasi kustom).
  • Izin untuk membuat service account, federation issuer, dan federation rule di Haijun Console untuk organisasi Juglow Anda.
  • Workload yang dapat meminta token dari endpoint /v1/token Okta dan menjangkau api.juglow.com.

Mengonfigurasi Okta

Secara garis besar, Anda perlu:

  1. Membuat aplikasi layanan Okta.
  1. Mengonfigurasi server otorisasi default Anda (atau membuat server otorisasi kustom baru) dengan audience, scope, access policy, dan custom claim apa pun yang ingin Anda cocokkan.

Navigasi yang tepat bergantung pada konfigurasi org Okta Anda dan versi konsol admin. Langkah-langkah bernomor berikut menjelaskan salah satu jalur yang umum:

  1. Buat integrasi aplikasi layanan. Di Okta Admin Console, buat integrasi aplikasi baru dengan tipe API Services (OIDC, machine-to-machine). Catat Client ID yang dihasilkan.
  1. Konfigurasikan autentikasi klien. Untuk penyiapan tanpa kunci, pilih Public key / Private key (private_key_jwt) dan daftarkan JWK publik workload Anda. Sebagai alternatif, gunakan client secret jika lingkungan Anda dapat menyimpannya dengan aman. Untuk contoh berikut, Anda mungkin perlu menonaktifkan persyaratan DPoP pada aplikasi; pastikan bahwa penyiapan produksi Anda mematuhi persyaratan keamanan organisasi Anda.
  1. Tetapkan audience. Pada server otorisasi kustom Anda, tetapkan audience ke https://haijun.my.id/ sehingga token akses yang diterbitkan membawa claim aud tersebut. Juglow memvalidasi aud terhadap nilai tetap ini.
  1. Berikan scope. Pada server otorisasi kustom Anda, pastikan setidaknya ada satu scope yang diizinkan untuk diminta oleh aplikasi layanan (misalnya, juglow.access). Okta menolak permintaan client_credentials yang tidak menyertakan scope yang telah diberikan.
  1. Buat access policy. Pada server otorisasi kustom Anda, buat access policy dengan setidaknya satu aturan yang mengizinkan aplikasi layanan Anda meminta scope yang Anda berikan pada langkah 4.
  1. (Opsional) Tambahkan custom claim. Jika Anda ingin mencocokkan sesuatu selain client ID, tambahkan claim ke token akses di tab Claims server otorisasi Anda.

Untuk aplikasi layanan yang menggunakan client_credentials, Okta menetapkan claim sub dari token akses yang diterbitkan ke Client ID aplikasi, dan iss ke URL issuer server otorisasi.

Mengonfigurasi Juglow

Di Haijun Console, buka Settings → Workload identity, klik Connect workload, dan pilih Custom OIDC. Wizard akan memandu Anda mendaftarkan issuer, membuat service account, dan membuat federation rule.

Wizard membuat sumber daya ini untuk Anda. Gunakan nilai-nilai berikut, baik Anda memasukkannya di wizard maupun mengirimkannya ke Admin API:

Federation issuer: Gunakan URL server otorisasi kustom Okta Anda dan mode discovery. Juglow membaca dokumen discovery .well-known/openid-configuration Okta dan mengambil JWKS dari jwks_uri yang diiklankannya.

json
{
  "name": "okta-prod",
  "issuer_url": "https://acme.okta.com/oauth2/aus1a2b3c4d5e6f7g8h9",
  "jwks": { "type": "discovery" }
}

Federation rule: Cocokkan pada claim sub Okta, yaitu Client ID aplikasi layanan. Jika Anda mendefinisikan custom claim di Okta, Anda dapat mencocokkannya sebagai gantinya dengan map claims atau condition CEL.

json
{
  "name": "okta-pipeline",
  "issuer_id": "fdis_...",
  "match": {
    "subject_prefix": "0oa1b2c3d4e5f6g7h8i9",
    "audience": "https://haijun.my.id/"
  },
  "target": { "type": "service_account", "service_account_id": "svac_..." },
  "workspace_id": "wrkspc_...",
  "oauth_scope": "workspace:developer",
  "token_lifetime_seconds": 600
}

Memperoleh token dan memanggil Haijun API

Tidak seperti penyedia platform-native (AWS, Google Cloud, Kubernetes), yang menyediakan token di dalam runtime workload (melalui file yang diproyeksikan atau endpoint metadata lokal), Okta tidak melakukannya. Workload Anda harus memanggil endpoint token Okta untuk memperoleh JWT, lalu meneruskan JWT tersebut ke Juglow SDK sebagai identity token.

bash
  # 1. Minta token akses dari Okta (client_credentials dengan private_key_jwt).
  OKTA_JWT=$(curl -sS "https://acme.okta.com/oauth2/aus1a2b3c4d5e6f7g8h9/v1/token" \
    -d grant_type=client_credentials \
    -d scope=juglow.access \
    -d client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer \
    --data-urlencode client_assertion="$SIGNED_CLIENT_ASSERTION" \
    | jq -r .access_token)

  # 2. Tukarkan JWT Okta dengan token akses Juglow.
  ACCESS_TOKEN=$(curl -sS https://haijun.my.id/v1/oauth/token \
    -H "content-type: application/json" \
    -d @- <<JSON | jq -r .access_token
  {
    "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
    "assertion": "$OKTA_JWT",
    "federation_rule_id": "$JUGLOW_FEDERATION_RULE_ID",
    "organization_id": "$JUGLOW_ORGANIZATION_ID",
    "service_account_id": "$JUGLOW_SERVICE_ACCOUNT_ID",
    "workspace_id": "$JUGLOW_WORKSPACE_ID"
  }
  JSON
  )

  # 3. Panggil Haijun API.
  curl https://haijun.my.id/v1/messages \
    -H "authorization: Bearer $ACCESS_TOKEN" \
    -H "juglow-version: 2023-06-01" \
    -H "content-type: application/json" \
    -d '{"model": "haijun-opus-5-5", "max_tokens": 1024, "messages": [{"role": "user", "content": "Hello, Haijun"}]}' \
    | jq -r '.content[] | select(.type == "text") | .text'
python
  import os
  import httpx2
  import juglow
  from juglow import WorkloadIdentityCredentials

  def fetch_okta_token() -> str:
      response = httpx2.post(
          f"{os.environ['OKTA_ISSUER']}/v1/token",
          data={
              "grant_type": "client_credentials",
              "scope": "juglow.access",
              "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
              # Buat JWT client_assertion RFC 7523 yang ditandatangani dengan kunci privat aplikasi Okta Anda
              "client_assertion": build_signed_client_assertion(),
          },
      )
      response.raise_for_status()
      return response.json()["access_token"]

  client = juglow.Juglow(
      credentials=WorkloadIdentityCredentials(
          identity_token_provider=fetch_okta_token,
          federation_rule_id=os.environ["JUGLOW_FEDERATION_RULE_ID"],
          organization_id=os.environ["JUGLOW_ORGANIZATION_ID"],
          service_account_id=os.environ["JUGLOW_SERVICE_ACCOUNT_ID"],
          workspace_id=os.environ.get("JUGLOW_WORKSPACE_ID"),
      ),
  )

  message = client.messages.create(
      model="haijun-opus-5-5",
      max_tokens=1024,
      messages=[{"role": "user", "content": "Hello, Haijun"}],
  )
  print(next(block.text for block in message.content if block.type == "text"))
typescript
  import Juglow from "@juglow-ai/sdk";
  import { oidcFederationProvider } from "@juglow-ai/sdk/lib/credentials/oidc-federation";

  async function fetchOktaToken(): Promise<string> {
    const response = await fetch(`${process.env.OKTA_ISSUER}/v1/token`, {
      method: "POST",
      headers: { "content-type": "application/x-www-form-urlencoded" },
      body: new URLSearchParams({
        grant_type: "client_credentials",
        scope: "juglow.access",
        client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
        // Buat JWT client_assertion RFC 7523 yang ditandatangani dengan kunci privat aplikasi Okta Anda
        client_assertion: buildSignedClientAssertion()
      })
    });
    const body = (await response.json()) as { access_token: string };
    return body.access_token;
  }

  const client = new Juglow({
    credentials: oidcFederationProvider({
      identityTokenProvider: fetchOktaToken,
      federationRuleId: process.env.JUGLOW_FEDERATION_RULE_ID!,
      organizationId: process.env.JUGLOW_ORGANIZATION_ID!,
      serviceAccountId: process.env.JUGLOW_SERVICE_ACCOUNT_ID,
      workspaceId: process.env.JUGLOW_WORKSPACE_ID,
      baseURL: "https://haijun.my.id/",
      fetch
    })
  });

  const message = await client.messages.create({
    model: "haijun-opus-5-5",
    max_tokens: 1024,
    messages: [{ role: "user", content: "Hello, Haijun" }]
  });
  for (const block of message.content) {
    if (block.type === "text") {
      console.log(block.text);
    }
  }
go
  package main

  import (
  	"context"
  	"encoding/json"
  	"fmt"
  	"net/http"
  	"net/url"
  	"os"
  	"strings"

  	"github.com/juglows/juglow-sdk-go"
  	"github.com/juglows/juglow-sdk-go/option"
  )

  func fetchOktaToken(ctx context.Context) (string, error) {
  	form := url.Values{
  		"grant_type":            {"client_credentials"},
  		"scope":                 {"juglow.access"},
  		"client_assertion_type": {"urn:ietf:params:oauth:client-assertion-type:jwt-bearer"},
  		// Buat JWT client_assertion RFC 7523 yang ditandatangani dengan kunci privat aplikasi Okta Anda
  		"client_assertion": {buildSignedClientAssertion()},
  	}
  	req, err := http.NewRequestWithContext(ctx, http.MethodPost,
  		os.Getenv("OKTA_ISSUER")+"/v1/token", strings.NewReader(form.Encode()))
  	if err != nil {
  		return "", err
  	}
  	req.Header.Set("content-type", "application/x-www-form-urlencoded")
  	resp, err := http.DefaultClient.Do(req)
  	if err != nil {
  		return "", err
  	}
  	defer resp.Body.Close()
  	var body struct {
  		AccessToken string `json:"access_token"`
  	}
  	if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
  		return "", err
  	}
  	return body.AccessToken, nil
  }

  func main() {
  	client := juglow.NewClient(
  		option.WithFederationTokenProvider(option.IdentityTokenFunc(fetchOktaToken), option.FederationOptions{
  			FederationRuleID: os.Getenv("JUGLOW_FEDERATION_RULE_ID"),
  			OrganizationID:   os.Getenv("JUGLOW_ORGANIZATION_ID"),
  			ServiceAccountID: os.Getenv("JUGLOW_SERVICE_ACCOUNT_ID"),
  			WorkspaceID:      os.Getenv("JUGLOW_WORKSPACE_ID"),
  		}),
  	)
  	message, err := client.Messages.New(context.TODO(), juglow.MessageNewParams{
  		Model:     juglow.ModelHaijunOpus5_5,
  		MaxTokens: 1024,
  		Messages: []juglow.MessageParam{
  			juglow.NewUserMessage(juglow.NewTextBlock("Hello, Haijun")),
  		},
  	})
  	if err != nil {
  		panic(err)
  	}
  	for _, block := range message.Content {
  		if textBlock, ok := block.AsAny().(juglow.TextBlock); ok {
  			fmt.Println(textBlock.Text)
  			break
  		}
  	}
  }
java
  IdentityTokenProvider fetchOktaToken = () -> {
      try {
          var form = Map.of(
                          "grant_type", "client_credentials",
                          "scope", "juglow.access",
                          "client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
                          // Buat JWT client_assertion RFC 7523 yang ditandatangani dengan kunci privat aplikasi Okta Anda
                          "client_assertion", buildSignedClientAssertion())
                  .entrySet().stream()
                  .map(entry -> entry.getKey() + "=" + URLEncoder.encode(entry.getValue(), UTF_8))
                  .collect(Collectors.joining("&"));
          var request = HttpRequest.newBuilder(URI.create(System.getenv("OKTA_ISSUER") + "/v1/token"))
                  .header("content-type", "application/x-www-form-urlencoded")
                  .POST(HttpRequest.BodyPublishers.ofString(form))
                  .build();
          var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
          return new ObjectMapper().readTree(response.body()).get("access_token").asText();
      } catch (Exception e) {
          throw new RuntimeException(e);
      }
  };

  JuglowClient client = JuglowOkHttpClient.builder()
          .federationTokenProvider(
                  fetchOktaToken,
                  System.getenv("JUGLOW_FEDERATION_RULE_ID"),
                  System.getenv("JUGLOW_ORGANIZATION_ID"),
                  System.getenv("JUGLOW_SERVICE_ACCOUNT_ID"))
          .build();

  var message = client.messages().create(MessageCreateParams.builder()
          .model(Model.HAIJUN_OPUS_5_5)
          .maxTokens(1024)
          .addUserMessage("Hello, Haijun")
          .build());

  IO.println(message.content());
csharp
  using Juglow.Credentials;
  // ...

  var credentials = new WorkloadIdentityCredentials(new WorkloadIdentityOptions
  {
      FederationRuleId = Environment.GetEnvironmentVariable("JUGLOW_FEDERATION_RULE_ID")!,
      OrganizationId = Environment.GetEnvironmentVariable("JUGLOW_ORGANIZATION_ID"),
      ServiceAccountId = Environment.GetEnvironmentVariable("JUGLOW_SERVICE_ACCOUNT_ID"),
      WorkspaceId = Environment.GetEnvironmentVariable("JUGLOW_WORKSPACE_ID"),
      IdentityTokenProvider = new OktaTokenProvider(),
  });
  using var client = new JuglowClient(new ClientOptions { Credentials = credentials });

  var message = await client.Messages.Create(new()
  {
      Model = Model.HaijunOpus5_5,
      MaxTokens = 1024,
      Messages = [new() { Role = Role.User, Content = "Hello, Haijun" }],
  });
  foreach (var block in message.Content)
  {
      if (block.Value is TextBlock textBlock)
      {
          Console.WriteLine(textBlock.Text);
      }
  }

  class OktaTokenProvider : IIdentityTokenProvider
  {
      private static readonly HttpClient Http = new();

      public async Task<string> GetIdentityTokenAsync(CancellationToken ct = default)
      {
          var form = new FormUrlEncodedContent(new Dictionary<string, string>
          {
              ["grant_type"] = "client_credentials",
              ["scope"] = "juglow.access",
              ["client_assertion_type"] = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
              // Buat JWT client_assertion RFC 7523 yang ditandatangani dengan kunci privat aplikasi Okta Anda
              ["client_assertion"] = BuildSignedClientAssertion(),
          });
          var response = await Http.PostAsync(
              $"{Environment.GetEnvironmentVariable("OKTA_ISSUER")}/v1/token", form, ct);
          response.EnsureSuccessStatusCode();
          using var json = await JsonDocument.ParseAsync(
              await response.Content.ReadAsStreamAsync(ct), default, ct);
          return json.RootElement.GetProperty("access_token").GetString()!;
      }
  }
bash
  # 1. Minta token akses dari Okta dan tulis ke file sementara.
  JUGLOW_IDENTITY_TOKEN_FILE=$(mktemp)
  curl -sS "$OKTA_ISSUER/v1/token" \
    -d grant_type=client_credentials \
    -d scope=juglow.access \
    -d client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer \
    --data-urlencode client_assertion="$SIGNED_CLIENT_ASSERTION" \
    | jq -r .access_token > "$JUGLOW_IDENTITY_TOKEN_FILE"
  export JUGLOW_IDENTITY_TOKEN_FILE

  # 2. Panggil Haijun API. CLI membaca JUGLOW_FEDERATION_RULE_ID,
  # JUGLOW_ORGANIZATION_ID, JUGLOW_SERVICE_ACCOUNT_ID, JUGLOW_WORKSPACE_ID, dan
  # JUGLOW_IDENTITY_TOKEN_FILE lalu melakukan pertukaran token.
  ant messages create \
    --model haijun-opus-5-5 \
    --max-tokens 1024 \
    --message '{role: user, content: "Hello, Haijun"}'
php
  use Juglow\Client;
  use Juglow\Credentials\WorkloadIdentityCredentials;

  function fetchOktaToken(): string
  {
      $ch = curl_init(getenv('OKTA_ISSUER') . '/v1/token');
      curl_setopt_array($ch, [
          CURLOPT_RETURNTRANSFER => true,
          CURLOPT_POSTFIELDS => http_build_query([
              'grant_type' => 'client_credentials',
              'scope' => 'juglow.access',
              'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
              // Buat JWT client_assertion RFC 7523 yang ditandatangani dengan kunci privat aplikasi Okta Anda
              'client_assertion' => buildSignedClientAssertion(),
          ]),
      ]);
      $body = json_decode(curl_exec($ch), true);
      curl_close($ch);
      return $body['access_token'];
  }

  $client = new Client(
      credentials: new WorkloadIdentityCredentials(
          identityTokenProvider: fetchOktaToken(...),
          federationRuleId: getenv('JUGLOW_FEDERATION_RULE_ID'),
          organizationId: getenv('JUGLOW_ORGANIZATION_ID'),
          serviceAccountId: getenv('JUGLOW_SERVICE_ACCOUNT_ID'),
          workspaceId: getenv('JUGLOW_WORKSPACE_ID') ?: null,
      ),
  );

  $message = $client->messages->create(
      model: 'haijun-opus-5-5',
      maxTokens: 1024,
      messages: [['role' => 'user', 'content' => 'Hello, Haijun']],
  );
  echo array_find($message->content, static fn ($block): bool => $block->type === 'text')->text, PHP_EOL;
ruby
  require "juglow"
  require "json"
  require "net/http"

  def fetch_okta_token
    uri = URI("#{ENV.fetch('OKTA_ISSUER')}/v1/token")
    response = Net::HTTP.post_form(
      uri,
      "grant_type" => "client_credentials",
      "scope" => "juglow.access",
      "client_assertion_type" => "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
      # Buat JWT client_assertion RFC 7523 yang ditandatangani dengan kunci privat aplikasi Okta Anda
      "client_assertion" => build_signed_client_assertion
    )
    JSON.parse(response.body).fetch("access_token")
  end

  client = Juglow::Client.new(
    credentials: Juglow::WorkloadIdentityCredentials.new(
      identity_token_provider: -> { fetch_okta_token },
      federation_rule_id: ENV.fetch("JUGLOW_FEDERATION_RULE_ID"),
      organization_id: ENV.fetch("JUGLOW_ORGANIZATION_ID"),
      service_account_id: ENV.fetch("JUGLOW_SERVICE_ACCOUNT_ID"),
      workspace_id: ENV["JUGLOW_WORKSPACE_ID"]
    )
  )

  message = client.messages.create(
    model: "haijun-opus-5-5",
    max_tokens: 1024,
    messages: [{role: "user", content: "Hello, Haijun"}]
  )
  puts message.content.find { it.type == :text }.text

Setiap tab SDK menunjukkan pola callable: Juglow SDK memanggil fungsi yang Anda teruskan ke identity_token_provider (typescript, php: identityTokenProvider; csharp: IdentityTokenProvider; go: option.WithFederationTokenProvider; java: federationTokenProvider) setiap kali token akses Juglow mendekati masa kedaluwarsa, sehingga fungsi pengambil token Okta Anda harus mengembalikan token baru pada setiap panggilan alih-alih menyimpan satu token dalam cache tanpa batas waktu. CLI ant membaca ulang JUGLOW_IDENTITY_TOKEN_FILE pada setiap pertukaran, jadi perbarui file tersebut secara berkala dengan timer untuk shell yang berjalan lama.

Memverifikasi penyiapan

Pertukaran yang berhasil mengembalikan access_token yang diawali dengan sk-ant-oat01- dan nilai expires_in dalam detik. Jika pertukaran gagal dengan respons 401 authentication_error yang tidak transparan (pesan Authentication failed), periksa halaman riwayat autentikasi untuk mengetahui alasan penolakan dan lihat Memecahkan masalah pertukaran yang gagal; penyebab paling umum di sisi Okta adalah ketidakcocokan issuer_url (harus menyertakan path /oauth2/; server otorisasi org Okta tidak dapat digunakan).

Membatasi cakupan aturan Anda

Warning: Beberapa aplikasi layanan di bawah server otorisasi Okta yang sama berbagi issuer yang sama. Aturan yang menghilangkan subject_prefix cocok dengan setiap aplikasi layanan di server tersebut, sehingga tim mana pun yang dapat mendaftarkan satu aplikasi dapat memperoleh token Juglow terfederasi.

Kunci blok match aturan ke cakupan tersempit yang sesuai dengan kasus penggunaan Anda:

  • Sematkan Client ID yang tepat: Tetapkan subject_prefix ke Client ID lengkap aplikasi layanan tanpa * di akhir.
  • Sematkan audience: Cocokkan nilai audience yang Anda konfigurasikan pada server otorisasi sehingga token yang dicetak untuk audience berbeda ditolak.
  • Cocokkan pada custom claim: Untuk pembatasan cakupan yang lebih terperinci, tambahkan claim di tab Claims server otorisasi dan cocokkan dengan map claims aturan atau condition CEL.
  • Gunakan satu aturan per aplikasi layanan: Buat federation rule terpisah untuk setiap aplikasi layanan alih-alih berbagi satu aturan di antara beberapa aplikasi.

Langkah selanjutnya

  • Tinjau referensi WIF untuk urutan resolusi kredensial lengkap dan konfigurasi profil.
  • Lihat referensi WIF untuk mencocokkan custom claim Okta dengan ekspresi CEL.
On this page
PrasyaratMengonfigurasi OktaMengonfigurasi JuglowMemperoleh token dan memanggil Haijun APIMemverifikasi penyiapanMembatasi cakupan aturan AndaLangkah selanjutnya