Skip to main content
View as Markdown

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
  • At least one redirect URI registered with noon for receiving authorization codes. A client can have several — see Choosing a Callback URL
  • Basic understanding of OAuth 2.0 authorization code flow
Use your integrator authentication for the OAuth API calls

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, generate a PKCE code_verifier, derive its code_challenge, store the verifier server-side, then redirect them to the noon authorization endpoint:

https://oauth.noon.partners/?client_id=abc123xyz&state=random-state-string-123&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256

Query Parameters:

ParameterDescriptionExample
client_idYour OAuth client IDabc123xyz
stateRandom string for CSRF protectionrandom-state-string-123
code_challengeBase64url, unpadded, of SHA-256(code_verifier)E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
code_challenge_methodMust be S256S256
redirect_uriOptional. Which registered callback URL to return to. Omitted, the first one registered is usedhttps://yourapp.com/callback
info

The seller will be presented with a consent screen showing what permissions your application is requesting. They must approve before proceeding.

PKCE may be mandatory for your client

code_challenge and code_challenge_method are required whenever your OAuth client is configured to require PKCE — the seller will see an error on the consent screen if they are missing. For other clients they are optional per flow, but recommended. Full details, including how to generate the verifier, are in Securing the Flow with PKCE.

Whichever applies, store the code_verifier alongside state in your server-side session record for this flow. You need it in Step 3, and it must never be sent to the browser.

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&iss=https%3A%2F%2Foauth.noon.partners

Your callback handler must:

  1. Verify the state parameter matches what you sent to prevent CSRF attacks
  2. Extract the authorization code from the code parameter
  3. Load the code_verifier you stored for this flow, if you started the flow with a code_challenge
  4. Ignore, or optionally check, iss — it names the authorization server that issued the code (https://oauth.noon.partners). Do not treat it as an unexpected parameter and fail

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

# Optional: confirm which authorization server issued this code (RFC 9207)
issuer = request.args.get('iss')
if issuer and issuer != 'https://oauth.noon.partners':
return "Unexpected issuer", 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",
"code_verifier": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
}

Include code_verifier when this flow's authorization URL carried a code_challenge, and send the verifier matching that exact challenge. Omit it entirely for flows that did not use PKCE — sending one for a non-PKCE flow is rejected. See Securing the Flow with PKCE.

Response:

{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "TOKEN_TYPE_BEARER",
"expires_in": "3600s",
"scopes": ["access:grant"],
"project_code": "PRJ12345"
}
Security Best Practice

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 access
  • expires_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: 0 means success)
  • project_code: The seller's project code
  • oauth_request_id: A unique identifier for tracking this OAuth exchange request — store this to monitor the service account creation progress
  • result: The service account credentials, including the private key needed to authenticate API calls
Store Your Credentials Securely

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.

Tracking Your Request

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:

Authentication required before calling the OAuth API endpoints

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.

These examples use PKCE

Each example generates a code_verifier per flow, sends the derived code_challenge on the authorization URL, and presents the verifier at token creation. PKCE is required for OAuth clients configured to require it and recommended for all others — see Securing the Flow with PKCE.

If your client does not use PKCE, drop the code_challenge parameters from the authorization URL and the code_verifier field from the token creation body. Sending one without the other is rejected.

Callback URL and issuer

Each example sends redirect_uri on the authorization URL to name which registered callback URL the seller returns to, and shows where to check the iss parameter that comes back with the code. Both are optional — drop redirect_uri to use the first URL registered on your client. See Choosing a Callback URL.

import base64
import hashlib
import json
import os
import uuid
import requests
from urllib.parse import quote
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'
# One of the callback URLs registered on your OAuth client. Omit it from the authorization URL
# and the seller is returned to the first one registered.
REDIRECT_URI = 'https://your-app.com/oauth/callback'
# The authorization server that issues your codes, echoed back as `iss` on the callback.
ISSUER = 'https://oauth.noon.partners'

# PKCE: 32 random bytes base64url-encode to a 43-character verifier,
# which satisfies the required 43-128 character length
def generate_pkce_pair():
verifier = base64.urlsafe_b64encode(os.urandom(32)).decode('ascii').rstrip('=')
digest = hashlib.sha256(verifier.encode('ascii')).digest()
challenge = base64.urlsafe_b64encode(digest).decode('ascii').rstrip('=')
return verifier, challenge

# 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

# Only the challenge is sent - the verifier stays server-side
code_verifier, code_challenge = generate_pkce_pair()
session['code_verifier'] = code_verifier

authorization_url = (
f"https://oauth.noon.partners/?client_id={CLIENT_ID}&state={state}"
f"&redirect_uri={quote(REDIRECT_URI, safe='')}"
f"&code_challenge={code_challenge}&code_challenge_method=S256"
)
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)

# Optional: confirm which authorization server issued this code (RFC 9207)
if request.args.get('iss') not in (None, ISSUER):
return "Unexpected issuer", 400

# Get authorization code
authorization_code = request.args.get('code')
if not authorization_code:
return "No authorization code received", 400

# Retrieve the PKCE verifier stored for this flow
code_verifier = session.pop('code_verifier', None)
if not code_verifier:
return "No code_verifier stored for this flow", 400

try:
# Step 3: Exchange authorization code for access token
token_response = get_access_token(authorization_code, code_verifier)
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, code_verifier):
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,
'code_verifier': code_verifier
}

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)

Testing Your Integration

Verification Checklist

Before going live with sellers, verify:

  • State parameter is validated to prevent CSRF
  • A fresh code_verifier is generated per flow and never reused
  • The code_verifier is stored server-side and never reaches the browser
  • The code_verifier sent at token creation belongs to the same flow as the authorization code
  • 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. Generate a Fresh PKCE Verifier Per Flow

import base64
import hashlib
import os

def generate_pkce_pair():
# 32 random bytes -> 43-character base64url verifier, no padding
verifier = base64.urlsafe_b64encode(os.urandom(32)).decode('ascii').rstrip('=')
digest = hashlib.sha256(verifier.encode('ascii')).digest()
challenge = base64.urlsafe_b64encode(digest).decode('ascii').rstrip('=')
return verifier, challenge

# Keep the verifier server-side, next to the state you already store
verifier, challenge = generate_pkce_pair()
session['code_verifier'] = verifier

# Only the challenge goes on the authorization URL

4. Use HTTPS Only

# Enforce HTTPS in production
if not request.is_secure and app.env == 'production':
return redirect(request.url.replace('http://', 'https://'))

5. 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:

  1. Store the credentials from result securely — Save the private key in a secrets manager and associate it with the seller in your database
  2. Start making API calls — Use the credentials as shown in the Authentication Guide
  3. Rotate or revoke credentials as needed — Use the API User Service to manage credentials over time

Additional Resources

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)