OAuth Flow Quickstart
This guide provides a complete walkthrough with working code examples for implementing the OAuth flow. For a conceptual overview of how OAuth works, see Getting Credentials via OAuth.
Prerequisites
Before you begin, ensure you have:
- Your own integrator service account credentials and an authenticated Noon session for your backend. If you have not set this up yet, start with Getting Your Credentials and Authenticating Your Requests.
- OAuth client credentials (client_id and client_secret) provided by Noon
- A redirect URI registered with Noon for receiving authorization codes
- Basic understanding of OAuth 2.0 authorization code flow
When your backend calls POST /identity/oauth/v1/token/create and POST /identity/oauth/v1/token/exchange, those requests must be sent with an authenticated Noon session created from your integrator service account credentials.
The OAuth client_id and client_secret identify your application during the code exchange, but they do not make the request authenticated on their own. The seller-scoped credential is returned only after the exchange succeeds.
Implementation Guide
Step 1: Redirect User to Authorization URL
When a seller wants to connect their Noon account to your platform, redirect them to the Noon authorization endpoint:
https://oauth.noon.partners/?client_id=abc123xyz&state=random-state-string-123
Required Query Parameters:
| Parameter | Description | Example |
|---|---|---|
client_id | Your OAuth client ID | abc123xyz |
state | Random string for CSRF protection | random-state-string-123 |
The seller will be presented with a consent screen showing what permissions your application is requesting. They must approve before proceeding.
Step 2: Handle Authorization Callback
After the seller approves, Noon redirects them back to your registered redirect URI with the authorization code:
https://yourapp.com/callback?code=AUTH_CODE_HERE&state=random-state-string-123
Your callback handler must:
- Verify the state parameter matches what you sent to prevent CSRF attacks
- Extract the authorization code from the
codeparameter
Example callback handling:
from flask import Flask, request, redirect
app = Flask(__name__)
@app.route('/callback')
def oauth_callback():
# Verify state parameter
state = request.args.get('state')
if state != session.get('oauth_state'): # session refers to any server-side storage you use to track state for that user request
return "Invalid state parameter", 400
# Get authorization code
auth_code = request.args.get('code')
if not auth_code:
return "No authorization code received", 400
# Proceed to exchange code for token
return exchange_code_for_token(auth_code)
Step 3: Exchange Authorization Code for Access Token
Make a server-to-server API call to exchange the authorization code for an access token.
Use the same authenticated integrator session for this request.
Endpoint: POST /identity/oauth/v1/token/create
Request Body:
{
"grant_type": "authorization_code",
"code": "AUTH_CODE_HERE",
"client_id": "your_client_id",
"client_secret": "your_client_secret"
}
Response:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "TOKEN_TYPE_BEARER",
"expires_in": "3600s",
"scopes": ["access:grant"],
"project_code": "PRJ12345"
}
Never expose your client_secret in client-side code. This exchange must happen on your backend server.
Important Response Fields:
access_token: JWT token needed for the next step (valid for 1 hour and single-use)project_code: The seller's project code to which the service account will have accessexpires_in: Token validity duration
Step 4: Create Service Account and Receive Credentials
Finally, use the access token to create the service account and receive its credentials.
This request must also reuse your authenticated integrator session.
Endpoint: POST /identity/oauth/v1/token/exchange
Request Body:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Response:
{
"status": {
"code": 0
},
"project_code": "PRJ12345",
"oauth_request_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"result": {
"key_id": "key-abc123",
"private_key": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQ...",
"channel_identifier": "[email protected]",
"project_code": "PRJ12345",
"type": "apijwt",
"issued_at": "2026-04-20T12:00:00Z"
}
}
Important Response Fields:
status: Indicates whether the workflow executed successfully (code: 0means success)project_code: The seller's project codeoauth_request_id: A unique identifier for tracking this OAuth exchange request — store this to monitor the service account creation progressresult: The service account credentials, including the private key needed to authenticate API calls
The result contains the private key, which is only returned once — Noon does not store it. Store it in a secrets manager immediately. If you lose it, you can create a new credential via the API User Service.
Save the oauth_request_id from the response. You can use it to check the status of the request in the OAuth tab of the Noon Partners Access App. This is helpful for debugging or confirming that the workflow completed successfully.
Complete Code Examples
Here are complete, working examples in multiple programming languages:
These examples assume your backend has already authenticated with Noon using your integrator service account credentials and is reusing that authenticated client or session when calling POST /identity/oauth/v1/token/create and POST /identity/oauth/v1/token/exchange.
The OAuth client_id and client_secret shown in the request body identify your OAuth application, but they do not replace the authenticated integrator session. For the standard login flow, see Authenticating Your Requests.
- Python
- Go
- Node.js
- PHP
- Java
- .NET
- Curl/Bash
import json
import uuid
import requests
from flask import Flask, request, session, redirect
app = Flask(__name__)
app.secret_key = 'your-secret-key-here' # Use a secure secret key
# Load your OAuth client credentials
CLIENT_ID = 'your_client_id'
CLIENT_SECRET = 'your_client_secret'
# Step 1: Initiate OAuth flow - redirect user to authorization URL
@app.route('/connect-seller')
def connect_seller():
# Generate and store state for CSRF protection
state = str(uuid.uuid4())
session['oauth_state'] = state
authorization_url = f"https://oauth.noon.partners/?client_id={CLIENT_ID}&state={state}"
return redirect(authorization_url)
# Step 2: Handle callback and verify state
@app.route('/oauth/callback')
def oauth_callback():
# Verify state parameter to prevent CSRF attacks
received_state = request.args.get('state')
stored_state = session.get('oauth_state')
if not received_state or received_state != stored_state:
return "Invalid state parameter - possible CSRF attack", 400
# Clear the stored state
session.pop('oauth_state', None)
# Get authorization code
authorization_code = request.args.get('code')
if not authorization_code:
return "No authorization code received", 400
try:
# Step 3: Exchange authorization code for access token
token_response = get_access_token(authorization_code)
print(f"Access token obtained for project: {token_response['project_code']}")
# Step 4: Exchange access token to create service account and receive credentials
sa_response = create_service_account(token_response['access_token'])
credentials = sa_response['result']
print(f"Credentials received for project: {token_response['project_code']}")
# Store credentials['private_key'] securely — it is only returned once
return f"Successfully connected seller project: {token_response['project_code']}"
except Exception as e:
return f"Error: {e}", 500
# Step 3: Exchange authorization code for access token
def get_access_token(auth_code):
url = 'https://noon-api-gateway.noon.partners/identity/oauth/v1/token/create'
payload = {
'grant_type': 'authorization_code',
'code': auth_code,
'client_id': CLIENT_ID,
'client_secret': CLIENT_SECRET
}
response = requests.post(url, json=payload, headers={
'Content-Type': 'application/json',
'User-Agent': 'YourApp/1.0'
})
if response.status_code == 200:
return response.json()
else:
raise Exception(f"Failed to get access token: {response.text}")
# Step 4: Exchange access token to create service account and receive credentials
def create_service_account(access_token):
url = 'https://noon-api-gateway.noon.partners/identity/oauth/v1/token/exchange'
payload = {
'access_token': access_token
}
response = requests.post(url, json=payload, headers={
'Content-Type': 'application/json',
'User-Agent': 'YourApp/1.0'
})
if response.status_code == 200:
return response.json()
else:
raise Exception(f"Failed to create service account: {response.text}")
if __name__ == '__main__':
app.run(debug=True)
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
const (
ClientID = "your_client_id"
ClientSecret = "your_client_secret"
BaseURL = "https://noon-api-gateway.noon.partners/identity/oauth/v1"
)
type TokenRequest struct {
GrantType string `json:"grant_type"`
Code string `json:"code"`
ClientID string `json:"client_id"`
ClientSecret string `json:"client_secret"`
}
type TokenResponse struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
ExpiresIn string `json:"expires_in"`
Scopes []string `json:"scopes"`
ProjectCode string `json:"project_code"`
}
type ExchangeRequest struct {
AccessToken string `json:"access_token"`
}
type ExchangeResponse struct {
Status struct {
Code int `json:"code"`
Message string `json:"message"`
} `json:"status"`
ProjectCode string `json:"project_code"`
}
func getAccessToken(authCode string) (*TokenResponse, error) {
reqBody := TokenRequest{
GrantType: "authorization_code",
Code: authCode,
ClientID: ClientID,
ClientSecret: ClientSecret,
}
jsonData, err := json.Marshal(reqBody)
if err != nil {
return nil, err
}
resp, err := http.Post(
BaseURL+"/token/create",
"application/json",
bytes.NewBuffer(jsonData),
)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("failed to get token: %s", string(body))
}
var tokenResp TokenResponse
if err := json.NewDecoder(resp.Body).Decode(&tokenResp); err != nil {
return nil, err
}
return &tokenResp, nil
}
func createServiceAccount(accessToken string) (*ExchangeResponse, error) {
reqBody := ExchangeRequest{
AccessToken: accessToken,
}
jsonData, err := json.Marshal(reqBody)
if err != nil {
return nil, err
}
resp, err := http.Post(
BaseURL+"/token/exchange",
"application/json",
bytes.NewBuffer(jsonData),
)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("failed to create service account: %s", string(body))
}
var exchangeResp ExchangeResponse
if err := json.NewDecoder(resp.Body).Decode(&exchangeResp); err != nil {
return nil, err
}
return &exchangeResp, nil
}
func main() {
// Step 1: Direct user to authorization URL
authURL := fmt.Sprintf(
"https://oauth.noon.partners/?client_id=%s&state=%s",
ClientID,
uuid.New().String(),
)
fmt.Printf("Direct user to: %s\n", authURL)
// Step 2: Receive authorization code from callback
authCode := "AUTHORIZATION_CODE_FROM_CALLBACK"
// Step 3: Get access token
tokenResp, err := getAccessToken(authCode)
if err != nil {
fmt.Printf("Error getting access token: %v\n", err)
return
}
fmt.Printf("Access token obtained for project: %s\n", tokenResp.ProjectCode)
// Step 4: Create service account
saResp, err := createServiceAccount(tokenResp.AccessToken)
if err != nil {
fmt.Printf("Error creating service account: %v\n", err)
return
}
fmt.Printf("Service account created for project: %s\n", saResp.ProjectCode)
}
import express from "express";
import session from "express-session";
import axios from "axios";
import { randomBytes } from "crypto";
const app = express();
const CLIENT_ID = "your_client_id";
const CLIENT_SECRET = "your_client_secret";
const BASE_URL = "https://noon-api-gateway.noon.partners/identity/oauth/v1";
// Setup session middleware
app.use(session({
secret: 'your-secret-key-here',
resave: false,
saveUninitialized: false,
cookie: { secure: false } // Set to true in production with HTTPS
}));
// Step 1: Initiate OAuth flow
app.get('/connect-seller', (req, res) => {
// Generate and store state for CSRF protection
const state = randomBytes(16).toString('hex');
req.session.oauthState = state;
const authUrl = `https://oauth.noon.partners/?client_id=${CLIENT_ID}&state=${state}`;
res.redirect(authUrl);
});
// Step 2: Handle OAuth callback
app.get('/oauth/callback', async (req, res) => {
// Verify state parameter
const receivedState = req.query.state;
const storedState = req.session.oauthState;
if (!receivedState || receivedState !== storedState) {
return res.status(400).send('Invalid state parameter - possible CSRF attack');
}
// Clear stored state
delete req.session.oauthState;
const authCode = req.query.code;
if (!authCode) {
return res.status(400).send('No authorization code received');
}
try {
// Step 3: Exchange code for access token
const tokenResponse = await getAccessToken(authCode);
console.log(`Access token obtained for project: ${tokenResponse.project_code}`);
// Step 4: Create service account
const saResponse = await createServiceAccount(tokenResponse.access_token);
console.log('Service account created:', saResponse);
res.send(`Successfully connected seller project: ${tokenResponse.project_code}`);
} catch (error) {
console.error('Error:', error.message);
res.status(500).send(`Error: ${error.message}`);
}
});
// Step 3: Exchange authorization code for access token
async function getAccessToken(authCode) {
try {
const response = await axios.post(
`${BASE_URL}/token/create`,
{
grant_type: "authorization_code",
code: authCode,
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
},
{
headers: {
"Content-Type": "application/json",
"User-Agent": "YourApp/1.0",
},
}
);
return response.data;
} catch (error) {
throw new Error(`Failed to get access token: ${error.response?.data || error.message}`);
}
}
// Step 4: Exchange access token to create service account
async function createServiceAccount(accessToken) {
try {
const response = await axios.post(
`${BASE_URL}/token/exchange`,
{
access_token: accessToken,
},
{
headers: {
"Content-Type": "application/json",
"User-Agent": "YourApp/1.0",
},
}
);
return response.data;
} catch (error) {
throw new Error(`Failed to create service account: ${error.response?.data || error.message}`);
}
}
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
console.log('Visit http://localhost:3000/connect-seller to start OAuth flow');
});
<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
$clientId = "your_client_id";
$clientSecret = "your_client_secret";
$baseUrl = "https://noon-api-gateway.noon.partners/identity/oauth/v1";
// Step 1: Initiate OAuth flow
// Generate state and store it in session for CSRF protection
session_start();
$state = bin2hex(random_bytes(16));
$_SESSION['oauth_state'] = $state;
$authUrl = sprintf(
"https://oauth.noon.partners/?client_id=%s&state=%s",
$clientId,
$state
);
// Redirect user to this URL
header("Location: " . $authUrl);
exit();
// Step 2: Handle OAuth callback (in your callback endpoint)
// Verify state parameter
session_start();
$receivedState = $_GET['state'] ?? '';
$storedState = $_SESSION['oauth_state'] ?? '';
if (empty($receivedState) || $receivedState !== $storedState) {
die("Invalid state parameter - possible CSRF attack");
}
// Clear stored state
unset($_SESSION['oauth_state']);
// Get authorization code
$authorizationCode = $_GET['code'] ?? '';
if (empty($authorizationCode)) {
die("No authorization code received");
}
$client = new Client();
try {
// Step 3: Exchange authorization code for access token
$tokenResponse = $client->post($baseUrl . '/token/create', [
'json' => [
'grant_type' => 'authorization_code',
'code' => $authorizationCode,
'client_id' => $clientId,
'client_secret' => $clientSecret,
],
'headers' => [
'Content-Type' => 'application/json',
'User-Agent' => 'YourApp/1.0',
]
]);
$tokenData = json_decode($tokenResponse->getBody(), true);
echo "Access token obtained for project: " . $tokenData['project_code'] . "\n";
// Step 4: Exchange access token to create service account
$saResponse = $client->post($baseUrl . '/token/exchange', [
'json' => [
'access_token' => $tokenData['access_token'],
],
'headers' => [
'Content-Type' => 'application/json',
'User-Agent' => 'YourApp/1.0',
]
]);
$saData = json_decode($saResponse->getBody(), true);
echo "Service account created: " . json_encode($saData) . "\n";
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
}
package com.noon.oauth;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.*;
import java.io.IOException;
public class OAuthExample {
private static final String CLIENT_ID = "your_client_id";
private static final String CLIENT_SECRET = "your_client_secret";
private static final String BASE_URL = "https://noon-api-gateway.noon.partners/identity/oauth/v1";
private static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
private final OkHttpClient client = new OkHttpClient();
private final ObjectMapper mapper = new ObjectMapper();
public static class TokenRequest {
public String grant_type = "authorization_code";
public String code;
public String client_id;
public String client_secret;
}
public static class TokenResponse {
public String access_token;
public String token_type;
public String expires_in;
public String[] scopes;
public String project_code;
}
public static class ExchangeRequest {
public String access_token;
}
public static class ExchangeResponse {
public static class Status {
public int code;
public String message;
}
public Status status;
public String project_code;
}
public TokenResponse getAccessToken(String authCode) throws IOException {
TokenRequest req = new TokenRequest();
req.code = authCode;
req.client_id = CLIENT_ID;
req.client_secret = CLIENT_SECRET;
String json = mapper.writeValueAsString(req);
RequestBody body = RequestBody.create(json, JSON);
Request request = new Request.Builder()
.url(BASE_URL + "/token/create")
.post(body)
.addHeader("User-Agent", "YourApp/1.0")
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new IOException("Failed to get token: " + response);
}
return mapper.readValue(response.body().string(), TokenResponse.class);
}
}
public ExchangeResponse createServiceAccount(String accessToken) throws IOException {
ExchangeRequest req = new ExchangeRequest();
req.access_token = accessToken;
String json = mapper.writeValueAsString(req);
RequestBody body = RequestBody.create(json, JSON);
Request request = new Request.Builder()
.url(BASE_URL + "/token/exchange")
.post(body)
.addHeader("User-Agent", "YourApp/1.0")
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new IOException("Failed to create service account: " + response);
}
return mapper.readValue(response.body().string(), ExchangeResponse.class);
}
}
public static void main(String[] args) {
// Step 1: Initiate OAuth flow
// Generate state and store it (e.g., in session/database) for CSRF protection
String state = UUID.randomUUID().toString();
// IMPORTANT: Store this state value to verify later in callback
// Example: session.setAttribute("oauth_state", state);
String authUrl = String.format(
"https://oauth.noon.partners/?client_id=%s&state=%s",
CLIENT_ID,
state
);
// Redirect user to authUrl
System.out.println("Redirect user to: " + authUrl);
// Step 2: Handle OAuth callback (in your callback endpoint)
// IMPORTANT: Verify state parameter matches stored value
// String receivedState = request.getParameter("state");
// String storedState = (String) session.getAttribute("oauth_state");
// if (!receivedState.equals(storedState)) {
// throw new SecurityException("Invalid state - possible CSRF attack");
// }
String authCode = "AUTHORIZATION_CODE_FROM_CALLBACK";
OAuthExample oauth = new OAuthExample();
try {
// Step 3: Get access token
TokenResponse tokenResp = oauth.getAccessToken(authCode);
System.out.println("Access token obtained for project: " + tokenResp.project_code);
// Step 4: Create service account
ExchangeResponse saResp = oauth.createServiceAccount(tokenResp.access_token);
System.out.println("Service account created for project: " + saResp.project_code);
} catch (IOException e) {
System.err.println("Error: " + e.getMessage());
}
}
}
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
class OAuthExample
{
private const string ClientId = "your_client_id";
private const string ClientSecret = "your_client_secret";
private const string BaseUrl = "https://noon-api-gateway.noon.partners/identity/oauth/v1";
private static readonly HttpClient client = new HttpClient();
public class TokenRequest
{
public string grant_type { get; set; } = "authorization_code";
public string code { get; set; }
public string client_id { get; set; }
public string client_secret { get; set; }
}
public class TokenResponse
{
public string access_token { get; set; }
public string token_type { get; set; }
public string expires_in { get; set; }
public string[] scopes { get; set; }
public string project_code { get; set; }
}
public class ExchangeRequest
{
public string access_token { get; set; }
}
public class ExchangeResponse
{
public class StatusInfo
{
public int code { get; set; }
public string message { get; set; }
}
public StatusInfo status { get; set; }
public string project_code { get; set; }
}
static async Task<TokenResponse> GetAccessToken(string authCode)
{
var request = new TokenRequest
{
code = authCode,
client_id = ClientId,
client_secret = ClientSecret
};
var json = JsonSerializer.Serialize(request);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync($"{BaseUrl}/token/create", content);
response.EnsureSuccessStatusCode();
var responseBody = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<TokenResponse>(responseBody);
}
static async Task<ExchangeResponse> CreateServiceAccount(string accessToken)
{
var request = new ExchangeRequest
{
access_token = accessToken
};
var json = JsonSerializer.Serialize(request);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync($"{BaseUrl}/token/exchange", content);
response.EnsureSuccessStatusCode();
var responseBody = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<ExchangeResponse>(responseBody);
}
static async Task Main(string[] args)
{
// Step 1: Initiate OAuth flow
// Generate state and store it (e.g., in session) for CSRF protection
var state = Guid.NewGuid().ToString();
// IMPORTANT: Store this state value to verify later in callback
// Example: HttpContext.Session.SetString("oauth_state", state);
var authUrl = $"https://oauth.noon.partners/?client_id={ClientId}&state={state}";
// Redirect user to authUrl
Console.WriteLine($"Redirect user to: {authUrl}");
// Step 2: Handle OAuth callback (in your callback endpoint)
// IMPORTANT: Verify state parameter matches stored value
// var receivedState = Request.Query["state"];
// var storedState = HttpContext.Session.GetString("oauth_state");
// if (receivedState != storedState) {
// throw new SecurityException("Invalid state - possible CSRF attack");
// }
var authCode = "AUTHORIZATION_CODE_FROM_CALLBACK";
try
{
// Step 3: Get access token
var tokenResponse = await GetAccessToken(authCode);
Console.WriteLine($"Access token obtained for project: {tokenResponse.project_code}");
// Step 4: Create service account
var saResponse = await CreateServiceAccount(tokenResponse.access_token);
Console.WriteLine($"Service account created for project: {saResponse.project_code}");
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
}
#!/usr/bin/env bash
CLIENT_ID="your_client_id"
CLIENT_SECRET="your_client_secret"
BASE_URL="https://noon-api-gateway.noon.partners/identity/oauth/v1"
# Step 1: Initiate OAuth flow
# Generate state and store it for CSRF protection
STATE=$(uuidgen)
# IMPORTANT: Store this state value (e.g., in Redis, file, or database)
# to verify later in callback. Example:
# echo "$STATE" > /tmp/oauth_state_${USER_ID}
AUTH_URL="https://oauth.noon.partners/?client_id=${CLIENT_ID}&state=${STATE}"
echo "Redirect user to: ${AUTH_URL}"
# Step 2: Handle OAuth callback (in your callback handler)
# IMPORTANT: Verify state parameter matches stored value
# RECEIVED_STATE="..." # Extract from callback URL
# STORED_STATE=$(cat /tmp/oauth_state_${USER_ID})
# if [ "$RECEIVED_STATE" != "$STORED_STATE" ]; then
# echo "Invalid state - possible CSRF attack"
# exit 1
# fi
AUTH_CODE="AUTHORIZATION_CODE_FROM_CALLBACK"
# Step 3: Exchange authorization code for access token
TOKEN_RESPONSE=$(curl -s -X POST "${BASE_URL}/token/create" \
-H "Content-Type: application/json" \
-H "User-Agent: YourApp/1.0" \
-d "{
\"grant_type\": \"authorization_code\",
\"code\": \"${AUTH_CODE}\",
\"client_id\": \"${CLIENT_ID}\",
\"client_secret\": \"${CLIENT_SECRET}\"
}")
# Extract access token
ACCESS_TOKEN=$(echo "$TOKEN_RESPONSE" | jq -r '.access_token')
PROJECT_CODE=$(echo "$TOKEN_RESPONSE" | jq -r '.project_code')
echo "Access token obtained for project: ${PROJECT_CODE}"
# Step 4: Exchange access token to create service account
SA_RESPONSE=$(curl -s -X POST "${BASE_URL}/token/exchange" \
-H "Content-Type: application/json" \
-H "User-Agent: YourApp/1.0" \
-d "{
\"access_token\": \"${ACCESS_TOKEN}\"
}")
echo "Service account created: ${SA_RESPONSE}"
Testing Your Integration
Verification Checklist
Before going live with sellers, verify:
- State parameter is validated to prevent CSRF
- Authorization codes are used only once
- Access tokens are stored securely and never logged
- Token expiry is handled gracefully
- Client secret is never exposed to clients
- Error responses are handled appropriately
Security Best Practices
For comprehensive security guidelines, see the Security Considerations in the Getting Credentials via OAuth guide.
1. Protect Your Client Secret
# BAD - Hardcoded secret
client_secret = "my-secret-123"
# GOOD - Use environment variables
import os
client_secret = os.environ.get('NOON_CLIENT_SECRET')
2. Validate State Parameter
# Always validate state
import secrets
# Generate random state
state = secrets.token_urlsafe(32)
session['oauth_state'] = state
# Later, in callback
if request.args.get('state') != session.get('oauth_state'):
raise ValueError("CSRF detected")
3. Use HTTPS Only
# Enforce HTTPS in production
if not request.is_secure and app.env == 'production':
return redirect(request.url.replace('http://', 'https://'))
4. Use Access Tokens Immediately
# Exchange access token immediately after receiving it
# Access tokens are single-use and should be consumed right away
token_response = get_access_token(auth_code)
access_token = token_response['access_token']
# Immediately exchange for service account creation
sa_response = create_service_account(access_token)
Next Steps
Now that you've completed the OAuth flow:
- Store the credentials from
resultsecurely — Save the private key in a secrets manager and associate it with the seller in your database - Start making API calls — Use the credentials as shown in the Authentication Guide
- Rotate or revoke credentials as needed — Use the API User Service to manage credentials over time
Additional Resources
- OAuth API Reference - Complete API documentation
- Getting Credentials via OAuth - Detailed OAuth concepts
Need Help?
If you encounter issues:
- Review the error messages in the API response
- Check the Error Handling section for common issues
- Contact Support with your client_id (never share client_secret)