OAuth krok po kroku
Przepływ jest taki sam jak w Allegro — zmieniasz tylko adresy na https://api.allee.pl/auth/oauth/…. Obowiązkowe PKCE z metodą S256.
1. Przekierowanie sprzedawcy
Wygeneruj losowy code_verifier (43–128 znaków) i code_challenge = BASE64URL(SHA256(code_verifier)). Skieruj przeglądarkę sprzedawcy na:
curl -sS -X GET "${ALLEE_API_URL:-https://api.allee.pl}/auth/oauth/authorize?response_type=code&client_id=36d6ed33acd3fc480a9270abe6421e9c&redirect_uri=https%3A%2F%2Fintegrator.example.com%2Fallee%2Fpowrot&state=st-7f3a9c&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256&scope=allegro%3Aapi%3Aprofile%3Aread%20allegro%3Aapi%3Asale%3Aoffers%3Aread%20allegro%3Aapi%3Asale%3Aoffers%3Awrite" \
-H "Accept: text/html" \
-w '\nHTTP %{http_code}\n'<?php
// allee API: GET /auth/oauth/authorize (PHP 8, rozszerzenie curl)
$base = getenv('ALLEE_API_URL') ?: 'https://api.allee.pl';
$ch = curl_init($base . '/auth/oauth/authorize?response_type=code&client_id=36d6ed33acd3fc480a9270abe6421e9c&redirect_uri=https%3A%2F%2Fintegrator.example.com%2Fallee%2Fpowrot&state=st-7f3a9c&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256&scope=allegro%3Aapi%3Aprofile%3Aread%20allegro%3Aapi%3Asale%3Aoffers%3Aread%20allegro%3Aapi%3Asale%3Aoffers%3Awrite');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Accept: text/html',
],
CURLOPT_RETURNTRANSFER => true,
]);
$odp = curl_exec($ch);
echo 'HTTP ', curl_getinfo($ch, CURLINFO_RESPONSE_CODE), "\n", $odp, "\n";# allee API: GET /auth/oauth/authorize (Python 3, biblioteka requests)
import os
import requests
BASE = os.environ.get("ALLEE_API_URL", "https://api.allee.pl")
headers = {
"Accept": "text/html",
}
r = requests.request("GET", BASE + "/auth/oauth/authorize?response_type=code&client_id=36d6ed33acd3fc480a9270abe6421e9c&redirect_uri=https%3A%2F%2Fintegrator.example.com%2Fallee%2Fpowrot&state=st-7f3a9c&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256&scope=allegro%3Aapi%3Aprofile%3Aread%20allegro%3Aapi%3Asale%3Aoffers%3Aread%20allegro%3Aapi%3Asale%3Aoffers%3Awrite", headers=headers, timeout=60, allow_redirects=False)
print("HTTP", r.status_code)
print(r.text)// allee API: GET /auth/oauth/authorize (Node.js 18+, wbudowany fetch; plik .mjs)
const BASE = process.env.ALLEE_API_URL ?? "https://api.allee.pl";
const res = await fetch(`${BASE}/auth/oauth/authorize?response_type=code&client_id=36d6ed33acd3fc480a9270abe6421e9c&redirect_uri=https%3A%2F%2Fintegrator.example.com%2Fallee%2Fpowrot&state=st-7f3a9c&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256&scope=allegro%3Aapi%3Aprofile%3Aread%20allegro%3Aapi%3Asale%3Aoffers%3Aread%20allegro%3Aapi%3Asale%3Aoffers%3Awrite`, {
method: "GET",
headers: {
"Accept": "text/html",
},
redirect: "manual",
});
console.log("HTTP", res.status);
console.log(await res.text());// allee API: GET /auth/oauth/authorize (C#, .NET 8+, HttpClient)
using System.Net.Http.Headers;
using System.Text;
var baseUrl = Environment.GetEnvironmentVariable("ALLEE_API_URL") ?? "https://api.allee.pl";
using var http = new HttpClient(new HttpClientHandler { AllowAutoRedirect = false });
using var req = new HttpRequestMessage(new HttpMethod("GET"), baseUrl + "/auth/oauth/authorize?response_type=code&client_id=36d6ed33acd3fc480a9270abe6421e9c&redirect_uri=https%3A%2F%2Fintegrator.example.com%2Fallee%2Fpowrot&state=st-7f3a9c&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256&scope=allegro%3Aapi%3Aprofile%3Aread%20allegro%3Aapi%3Asale%3Aoffers%3Aread%20allegro%3Aapi%3Asale%3Aoffers%3Awrite");
req.Headers.Accept.ParseAdd("text/html");
using var res = await http.SendAsync(req);
Console.WriteLine($"HTTP {(int)res.StatusCode}");
Console.WriteLine(await res.Content.ReadAsStringAsync());// allee API: GET /auth/oauth/authorize (Java 11+, java.net.http; uruchom: java Main.java)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Main {
public static void main(String[] args) throws Exception {
String base = System.getenv().getOrDefault("ALLEE_API_URL", "https://api.allee.pl");
HttpRequest req = HttpRequest.newBuilder(URI.create(base + "/auth/oauth/authorize?response_type=code&client_id=36d6ed33acd3fc480a9270abe6421e9c&redirect_uri=https%3A%2F%2Fintegrator.example.com%2Fallee%2Fpowrot&state=st-7f3a9c&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256&scope=allegro%3Aapi%3Aprofile%3Aread%20allegro%3Aapi%3Asale%3Aoffers%3Aread%20allegro%3Aapi%3Asale%3Aoffers%3Awrite"))
.header("Accept", "text/html")
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
HttpClient http = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build();
HttpResponse<String> res = http.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println("HTTP " + res.statusCode());
System.out.println(res.body());
}
}// allee API: GET /auth/oauth/authorize (Go 1.20+, net/http; uruchom: go run main.go)
package main
import (
"fmt"
"io"
"net/http"
"os"
)
func main() {
base := os.Getenv("ALLEE_API_URL")
if base == "" {
base = "https://api.allee.pl"
}
req, err := http.NewRequest("GET", base+"/auth/oauth/authorize?response_type=code&client_id=36d6ed33acd3fc480a9270abe6421e9c&redirect_uri=https%3A%2F%2Fintegrator.example.com%2Fallee%2Fpowrot&state=st-7f3a9c&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256&scope=allegro%3Aapi%3Aprofile%3Aread%20allegro%3Aapi%3Asale%3Aoffers%3Aread%20allegro%3Aapi%3Asale%3Aoffers%3Awrite", nil)
if err != nil {
panic(err)
}
req.Header.Set("Accept", "text/html")
// nie podążamy za przekierowaniami (authorize odpowiada 302)
klient := &http.Client{CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
res, err := klient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
dane, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
fmt.Println("HTTP", res.StatusCode)
fmt.Println(string(dane))
}Po kliknięciu „Zezwól” sprzedawca wraca na redirect_uri?code=…&state=…. Sprawdź, że state jest Twój.
redirect_uri porównujemy bajt w bajt z zarejestrowanym (ukośnik na końcu ma znaczenie). Zły adres daje stronę błędu bez przekierowania.
2. Wymiana kodu na tokeny
curl -sS -X POST "${ALLEE_API_URL:-https://api.allee.pl}/auth/oauth/token" \
-u "$ALLEE_CLIENT_ID:$ALLEE_CLIENT_SECRET" \
-H "Accept: application/json" \
--data-urlencode "grant_type=authorization_code" \
--data-urlencode "code={kod_z_przekierowania}" \
--data-urlencode "redirect_uri=https://integrator.example.com/allee/powrot" \
--data-urlencode "code_verifier={code_verifier}" \
-w '\nHTTP %{http_code}\n'<?php
// allee API: POST /auth/oauth/token (PHP 8, rozszerzenie curl)
$base = getenv('ALLEE_API_URL') ?: 'https://api.allee.pl';
$ch = curl_init($base . '/auth/oauth/token');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Accept: application/json',
],
CURLOPT_USERPWD => getenv('ALLEE_CLIENT_ID') . ':' . getenv('ALLEE_CLIENT_SECRET'),
CURLOPT_POSTFIELDS => http_build_query([
'grant_type' => 'authorization_code',
'code' => '{kod_z_przekierowania}',
'redirect_uri' => 'https://integrator.example.com/allee/powrot',
'code_verifier' => '{code_verifier}',
]),
CURLOPT_RETURNTRANSFER => true,
]);
$odp = curl_exec($ch);
echo 'HTTP ', curl_getinfo($ch, CURLINFO_RESPONSE_CODE), "\n", $odp, "\n";# allee API: POST /auth/oauth/token (Python 3, biblioteka requests)
import os
import requests
BASE = os.environ.get("ALLEE_API_URL", "https://api.allee.pl")
headers = {
"Accept": "application/json",
}
form = {
"grant_type": "authorization_code",
"code": "{kod_z_przekierowania}",
"redirect_uri": "https://integrator.example.com/allee/powrot",
"code_verifier": "{code_verifier}",
}
r = requests.request("POST", BASE + "/auth/oauth/token", headers=headers, data=form, auth=(os.environ["ALLEE_CLIENT_ID"], os.environ["ALLEE_CLIENT_SECRET"]), timeout=60, allow_redirects=False)
print("HTTP", r.status_code)
print(r.text)// allee API: POST /auth/oauth/token (Node.js 18+, wbudowany fetch; plik .mjs)
const BASE = process.env.ALLEE_API_URL ?? "https://api.allee.pl";
const form = new URLSearchParams({
"grant_type": "authorization_code",
"code": "{kod_z_przekierowania}",
"redirect_uri": "https://integrator.example.com/allee/powrot",
"code_verifier": "{code_verifier}"
});
const res = await fetch(`${BASE}/auth/oauth/token`, {
method: "POST",
headers: {
Authorization: "Basic " + Buffer.from(`${process.env.ALLEE_CLIENT_ID}:${process.env.ALLEE_CLIENT_SECRET}`).toString("base64"),
"Accept": "application/json",
},
body: form,
redirect: "manual",
});
console.log("HTTP", res.status);
console.log(await res.text());// allee API: POST /auth/oauth/token (C#, .NET 8+, HttpClient)
using System.Net.Http.Headers;
using System.Text;
var baseUrl = Environment.GetEnvironmentVariable("ALLEE_API_URL") ?? "https://api.allee.pl";
using var http = new HttpClient(new HttpClientHandler { AllowAutoRedirect = false });
using var req = new HttpRequestMessage(new HttpMethod("POST"), baseUrl + "/auth/oauth/token");
var klient = Environment.GetEnvironmentVariable("ALLEE_CLIENT_ID") + ":" + Environment.GetEnvironmentVariable("ALLEE_CLIENT_SECRET");
req.Headers.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes(klient)));
req.Headers.Accept.ParseAdd("application/json");
req.Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "authorization_code",
["code"] = "{kod_z_przekierowania}",
["redirect_uri"] = "https://integrator.example.com/allee/powrot",
["code_verifier"] = "{code_verifier}",
});
using var res = await http.SendAsync(req);
Console.WriteLine($"HTTP {(int)res.StatusCode}");
Console.WriteLine(await res.Content.ReadAsStringAsync());// allee API: POST /auth/oauth/token (Java 11+, java.net.http; uruchom: java Main.java)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class Main {
public static void main(String[] args) throws Exception {
String base = System.getenv().getOrDefault("ALLEE_API_URL", "https://api.allee.pl");
String form = "grant_type=authorization_code&code=%7Bkod_z_przekierowania%7D&redirect_uri=https%3A%2F%2Fintegrator.example.com%2Fallee%2Fpowrot&code_verifier=%7Bcode_verifier%7D";
String klient = System.getenv("ALLEE_CLIENT_ID") + ":" + System.getenv("ALLEE_CLIENT_SECRET");
HttpRequest req = HttpRequest.newBuilder(URI.create(base + "/auth/oauth/token"))
.header("Authorization", "Basic " + Base64.getEncoder().encodeToString(klient.getBytes(StandardCharsets.UTF_8)))
.header("Accept", "application/json")
.header("Content-Type", "application/x-www-form-urlencoded")
.method("POST", HttpRequest.BodyPublishers.ofString(form))
.build();
HttpClient http = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build();
HttpResponse<String> res = http.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println("HTTP " + res.statusCode());
System.out.println(res.body());
}
}// allee API: POST /auth/oauth/token (Go 1.20+, net/http; uruchom: go run main.go)
package main
import (
"fmt"
"io"
"net/http"
"net/url"
"os"
"strings"
)
func main() {
base := os.Getenv("ALLEE_API_URL")
if base == "" {
base = "https://api.allee.pl"
}
form := url.Values{}
form.Set("grant_type", "authorization_code")
form.Set("code", "{kod_z_przekierowania}")
form.Set("redirect_uri", "https://integrator.example.com/allee/powrot")
form.Set("code_verifier", "{code_verifier}")
req, err := http.NewRequest("POST", base+"/auth/oauth/token", strings.NewReader(form.Encode()))
if err != nil {
panic(err)
}
req.SetBasicAuth(os.Getenv("ALLEE_CLIENT_ID"), os.Getenv("ALLEE_CLIENT_SECRET"))
req.Header.Set("Accept", "application/json")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
klient := &http.Client{CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
res, err := klient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
dane, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
fmt.Println("HTTP", res.StatusCode)
fmt.Println(string(dane))
}Kod jest jednorazowy i ważny 10 minut — nieudana wymiana też go zużywa. Zapisz scope z odpowiedzi: nieznane zakresy są po cichu pomijane.
3. Odświeżanie (rotacja)
Każde odświeżenie wydaje nową parę, a stary refresh przestaje działać. Ponowne użycie starego refresh tokenu poza 60-sekundowym oknem łaski unieważnia całą rodzinę tokenów (ochrona przed kradzieżą, RFC 9700) — odświeżaj więc pojedynczo (blokada na połączenie) i zapisuj nową parę atomowo.
4. Token aplikacji (client_credentials)
Bez sprzedawcy — tylko dane publiczne: kategorie, parametry, metody dostawy, przewoźnicy. Na innych trasach: 403 USER_TOKEN_REQUIRED.
5. Odwołanie
curl -sS -X POST "${ALLEE_API_URL:-https://api.allee.pl}/auth/oauth/revoke" \
-u "$ALLEE_CLIENT_ID:$ALLEE_CLIENT_SECRET" \
-H "Accept: application/json" \
--data-urlencode "token={refresh_token}" \
--data-urlencode "token_type_hint=refresh_token" \
-w '\nHTTP %{http_code}\n'<?php
// allee API: POST /auth/oauth/revoke (PHP 8, rozszerzenie curl)
$base = getenv('ALLEE_API_URL') ?: 'https://api.allee.pl';
$ch = curl_init($base . '/auth/oauth/revoke');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Accept: application/json',
],
CURLOPT_USERPWD => getenv('ALLEE_CLIENT_ID') . ':' . getenv('ALLEE_CLIENT_SECRET'),
CURLOPT_POSTFIELDS => http_build_query([
'token' => '{refresh_token}',
'token_type_hint' => 'refresh_token',
]),
CURLOPT_RETURNTRANSFER => true,
]);
$odp = curl_exec($ch);
echo 'HTTP ', curl_getinfo($ch, CURLINFO_RESPONSE_CODE), "\n", $odp, "\n";# allee API: POST /auth/oauth/revoke (Python 3, biblioteka requests)
import os
import requests
BASE = os.environ.get("ALLEE_API_URL", "https://api.allee.pl")
headers = {
"Accept": "application/json",
}
form = {
"token": "{refresh_token}",
"token_type_hint": "refresh_token",
}
r = requests.request("POST", BASE + "/auth/oauth/revoke", headers=headers, data=form, auth=(os.environ["ALLEE_CLIENT_ID"], os.environ["ALLEE_CLIENT_SECRET"]), timeout=60, allow_redirects=False)
print("HTTP", r.status_code)
print(r.text)// allee API: POST /auth/oauth/revoke (Node.js 18+, wbudowany fetch; plik .mjs)
const BASE = process.env.ALLEE_API_URL ?? "https://api.allee.pl";
const form = new URLSearchParams({
"token": "{refresh_token}",
"token_type_hint": "refresh_token"
});
const res = await fetch(`${BASE}/auth/oauth/revoke`, {
method: "POST",
headers: {
Authorization: "Basic " + Buffer.from(`${process.env.ALLEE_CLIENT_ID}:${process.env.ALLEE_CLIENT_SECRET}`).toString("base64"),
"Accept": "application/json",
},
body: form,
redirect: "manual",
});
console.log("HTTP", res.status);
console.log(await res.text());// allee API: POST /auth/oauth/revoke (C#, .NET 8+, HttpClient)
using System.Net.Http.Headers;
using System.Text;
var baseUrl = Environment.GetEnvironmentVariable("ALLEE_API_URL") ?? "https://api.allee.pl";
using var http = new HttpClient(new HttpClientHandler { AllowAutoRedirect = false });
using var req = new HttpRequestMessage(new HttpMethod("POST"), baseUrl + "/auth/oauth/revoke");
var klient = Environment.GetEnvironmentVariable("ALLEE_CLIENT_ID") + ":" + Environment.GetEnvironmentVariable("ALLEE_CLIENT_SECRET");
req.Headers.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes(klient)));
req.Headers.Accept.ParseAdd("application/json");
req.Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["token"] = "{refresh_token}",
["token_type_hint"] = "refresh_token",
});
using var res = await http.SendAsync(req);
Console.WriteLine($"HTTP {(int)res.StatusCode}");
Console.WriteLine(await res.Content.ReadAsStringAsync());// allee API: POST /auth/oauth/revoke (Java 11+, java.net.http; uruchom: java Main.java)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class Main {
public static void main(String[] args) throws Exception {
String base = System.getenv().getOrDefault("ALLEE_API_URL", "https://api.allee.pl");
String form = "token=%7Brefresh_token%7D&token_type_hint=refresh_token";
String klient = System.getenv("ALLEE_CLIENT_ID") + ":" + System.getenv("ALLEE_CLIENT_SECRET");
HttpRequest req = HttpRequest.newBuilder(URI.create(base + "/auth/oauth/revoke"))
.header("Authorization", "Basic " + Base64.getEncoder().encodeToString(klient.getBytes(StandardCharsets.UTF_8)))
.header("Accept", "application/json")
.header("Content-Type", "application/x-www-form-urlencoded")
.method("POST", HttpRequest.BodyPublishers.ofString(form))
.build();
HttpClient http = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build();
HttpResponse<String> res = http.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println("HTTP " + res.statusCode());
System.out.println(res.body());
}
}// allee API: POST /auth/oauth/revoke (Go 1.20+, net/http; uruchom: go run main.go)
package main
import (
"fmt"
"io"
"net/http"
"net/url"
"os"
"strings"
)
func main() {
base := os.Getenv("ALLEE_API_URL")
if base == "" {
base = "https://api.allee.pl"
}
form := url.Values{}
form.Set("token", "{refresh_token}")
form.Set("token_type_hint", "refresh_token")
req, err := http.NewRequest("POST", base+"/auth/oauth/revoke", strings.NewReader(form.Encode()))
if err != nil {
panic(err)
}
req.SetBasicAuth(os.Getenv("ALLEE_CLIENT_ID"), os.Getenv("ALLEE_CLIENT_SECRET"))
req.Header.Set("Accept", "application/json")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
klient := &http.Client{CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
res, err := klient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
dane, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
fmt.Println("HTTP", res.StatusCode)
fmt.Println(string(dane))
}Sprzedawca może też cofnąć zgodę w swoim koncie (allee.pl → Konto → Aplikacje) — wtedy wszystkie tokeny pary przestają działać od razu (401, refresh: invalid_grant).