{
  "info": {
    "name": "OIDC Login API - Asian Paints",
    "_postman_id": "oidc-asian-paints-collection",
    "description": "Complete OIDC Identity Provider API Collection for Asian Paints. Follow the requests in order: Step 1 → Step 2 → Step 3 → Step 4 → Step 5 → Step 6 → Step 7",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "variable": [
    {
      "key": "base_url",
      "value": "http://127.0.0.1:8000",
      "type": "string",
      "description": "Change this to your production URL when deploying"
    },
    {
      "key": "client_id",
      "value": "759b2c61f3db684d86f30e6de8b1965c",
      "type": "string"
    },
    {
      "key": "client_secret",
      "value": "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",
      "type": "string"
    },
    {
      "key": "redirect_uri",
      "value": "https://apg-uae.myshopify.com/authorization",
      "type": "string"
    },
    {
      "key": "post_logout_redirect_uri",
      "value": "https://apg-uae.myshopify.com",
      "type": "string"
    },
    {
      "key": "auth_code",
      "value": "",
      "type": "string",
      "description": "Auto-filled after Step 3 runs"
    },
    {
      "key": "id_token",
      "value": "",
      "type": "string",
      "description": "Auto-filled after Step 4 runs"
    },
    {
      "key": "access_token",
      "value": "",
      "type": "string",
      "description": "Auto-filled after Step 4 runs"
    },
    {
      "key": "refresh_token",
      "value": "",
      "type": "string",
      "description": "Auto-filled after Step 4 runs"
    }
  ],
  "item": [
    {
      "name": "Step 1 - Discovery Document",
      "request": {
        "method": "GET",
        "header": [],
        "url": {
          "raw": "{{base_url}}/api/oidc/.well-known/openid-configuration",
          "host": ["{{base_url}}"],
          "path": ["api", "oidc", ".well-known", "openid-configuration"]
        },
        "description": "Returns all OIDC endpoint URLs supported by this provider. No parameters needed. Run this first to discover all available endpoints."
      },
      "response": [
        {
          "name": "Success Response",
          "status": "OK",
          "code": 200,
          "body": "{\n  \"issuer\": \"http://127.0.0.1:8000\",\n  \"authorization_endpoint\": \"http://127.0.0.1:8000/api/oidc/authorize\",\n  \"token_endpoint\": \"http://127.0.0.1:8000/api/oidc/token\",\n  \"jwks_uri\": \"http://127.0.0.1:8000/api/oidc/jwks.json\",\n  \"userinfo_endpoint\": \"http://127.0.0.1:8000/api/oidc/userinfo\",\n  \"end_session_endpoint\": \"http://127.0.0.1:8000/api/oidc/logout\",\n  \"scopes_supported\": [\"openid\", \"email\", \"profile\"],\n  \"response_types_supported\": [\"code\"],\n  \"grant_types_supported\": [\"authorization_code\", \"refresh_token\"],\n  \"code_challenge_methods_supported\": [\"S256\"],\n  \"id_token_signing_alg_values_supported\": [\"RS256\"]\n}"
        }
      ]
    },
    {
      "name": "Step 2 - JWKS Public Keys",
      "request": {
        "method": "GET",
        "header": [],
        "url": {
          "raw": "{{base_url}}/api/oidc/jwks.json",
          "host": ["{{base_url}}"],
          "path": ["api", "oidc", "jwks.json"]
        },
        "description": "Returns the RSA public key used to verify ID tokens (JWT). External apps use this to verify the signature of id_token."
      },
      "response": [
        {
          "name": "Success Response",
          "status": "OK",
          "code": 200,
          "body": "{\n  \"keys\": [\n    {\n      \"kty\": \"RSA\",\n      \"use\": \"sig\",\n      \"alg\": \"RS256\",\n      \"kid\": \"key-1\",\n      \"n\": \"...\",\n      \"e\": \"AQAB\"\n    }\n  ]\n}"
        }
      ]
    },
    {
      "name": "Step 3 - Login (Get Auth Code)",
      "event": [
        {
          "listen": "test",
          "script": {
            "exec": [
              "// Auto extract code from Location header and save to variable",
              "var location = pm.response.headers.get('Location');",
              "if (location) {",
              "    var match = location.match(/[?&]code=([^&]+)/);",
              "    if (match) {",
              "        pm.collectionVariables.set('auth_code', match[1]);",
              "        console.log('Auth code saved: ' + match[1]);",
              "        pm.test('Auth code extracted successfully', function() {",
              "            pm.expect(match[1]).to.not.be.empty;",
              "        });",
              "    }",
              "}",
              "pm.test('Status is 302', function() {",
              "    pm.response.to.have.status(302);",
              "});"
            ],
            "type": "text/javascript"
          }
        }
      ],
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/x-www-form-urlencoded"
          }
        ],
        "body": {
          "mode": "urlencoded",
          "urlencoded": [
            {
              "key": "response_type",
              "value": "code",
              "description": "Must always be 'code'"
            },
            {
              "key": "client_id",
              "value": "{{client_id}}",
              "description": "Your registered client ID"
            },
            {
              "key": "redirect_uri",
              "value": "{{redirect_uri}}",
              "description": "Must match registered redirect URI in oidc_clients table"
            },
            {
              "key": "state",
              "value": "test123",
              "description": "Random string to prevent CSRF attacks"
            },
            {
              "key": "scope",
              "value": "openid email",
              "description": "Requested scopes"
            },
            {
              "key": "username",
              "value": "mishranimesh067@gmail.com",
              "description": "Customer email from customer_details table"
            },
            {
              "key": "password",
              "value": "anyvalue",
              "description": "Any value (no password check currently)"
            }
          ]
        },
        "url": {
          "raw": "{{base_url}}/api/oidc/authorize",
          "host": ["{{base_url}}"],
          "path": ["api", "oidc", "authorize"]
        },
        "description": "⚠️ IMPORTANT: Turn OFF 'Automatically follow redirects' in Postman Settings before sending.\n\nAuthenticates the customer by email from customer_details table.\nOn success returns 302 with Location header containing the auth code.\nThe test script auto-saves the code to {{auth_code}} variable."
      },
      "response": [
        {
          "name": "Success Response",
          "status": "Found",
          "code": 302,
          "header": [
            {
              "key": "Location",
              "value": "https://apg-uae.myshopify.com/authorization?code=CbyPYm1JkIVOurMyzOjLBggVMg4H6TOG&state=test123"
            }
          ],
          "body": ""
        },
        {
          "name": "Error - Invalid Client",
          "status": "Bad Request",
          "code": 400,
          "body": "invalid_client or redirect_uri"
        },
        {
          "name": "Error - Invalid Credentials",
          "status": "Unauthorized",
          "code": 401,
          "body": "Invalid credentials"
        }
      ]
    },
    {
      "name": "Step 4 - Token Exchange (Get ID Token)",
      "event": [
        {
          "listen": "test",
          "script": {
            "exec": [
              "// Auto save tokens to collection variables",
              "if (pm.response.code === 200) {",
              "    var json = pm.response.json();",
              "    pm.collectionVariables.set('id_token', json.id_token);",
              "    pm.collectionVariables.set('access_token', json.access_token);",
              "    pm.collectionVariables.set('refresh_token', json.refresh_token);",
              "    console.log('Tokens saved to collection variables');",
              "    pm.test('id_token received', function() {",
              "        pm.expect(json.id_token).to.not.be.empty;",
              "    });",
              "    pm.test('access_token received', function() {",
              "        pm.expect(json.access_token).to.not.be.empty;",
              "    });",
              "    pm.test('refresh_token received', function() {",
              "        pm.expect(json.refresh_token).to.not.be.empty;",
              "    });",
              "}",
              "pm.test('Status is 200', function() {",
              "    pm.response.to.have.status(200);",
              "});"
            ],
            "type": "text/javascript"
          }
        }
      ],
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/x-www-form-urlencoded"
          }
        ],
        "body": {
          "mode": "urlencoded",
          "urlencoded": [
            {
              "key": "grant_type",
              "value": "authorization_code",
              "description": "Must always be 'authorization_code'"
            },
            {
              "key": "code",
              "value": "{{auth_code}}",
              "description": "Auth code received from Step 3 (auto-filled)"
            },
            {
              "key": "redirect_uri",
              "value": "{{redirect_uri}}",
              "description": "Must be same redirect_uri used in Step 3"
            },
            {
              "key": "client_id",
              "value": "{{client_id}}",
              "description": "Your client ID"
            },
            {
              "key": "client_secret",
              "value": "{{client_secret}}",
              "description": "Your client secret"
            }
          ]
        },
        "url": {
          "raw": "{{base_url}}/api/oidc/token",
          "host": ["{{base_url}}"],
          "path": ["api", "oidc", "token"]
        },
        "description": "Exchanges the auth code from Step 3 for tokens.\nReturns id_token (JWT), access_token, and refresh_token.\nThe test script auto-saves all tokens to collection variables.\n\nid_token contains: sub, email, name, phone, shopify_customer_id\nid_token expires in 300 seconds (5 minutes)\nrefresh_token expires in 30 days"
      },
      "response": [
        {
          "name": "Success Response",
          "status": "OK",
          "code": 200,
          "body": "{\n  \"access_token\": \"33PvglScw4SUlAzBJaYmtTnuS4XnQRqNWk9FDmiT\",\n  \"token_type\": \"Bearer\",\n  \"expires_in\": 300,\n  \"id_token\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImtleS0xIn0...\",\n  \"refresh_token\": \"87MuEl4A7864lFx8rcBHnHYqTrKQTIMiXWkL46C5\"\n}"
        },
        {
          "name": "Error - Invalid Client",
          "status": "Unauthorized",
          "code": 401,
          "body": "{\"error\": \"invalid_client\"}"
        },
        {
          "name": "Error - Invalid Grant",
          "status": "Bad Request",
          "code": 400,
          "body": "{\"error\": \"invalid_grant\"}"
        }
      ]
    },
    {
      "name": "Step 5 - Get User Info",
      "event": [
        {
          "listen": "test",
          "script": {
            "exec": [
              "pm.test('Status is 200', function() {",
              "    pm.response.to.have.status(200);",
              "});",
              "if (pm.response.code === 200) {",
              "    var json = pm.response.json();",
              "    pm.test('Email returned', function() {",
              "        pm.expect(json.email).to.not.be.empty;",
              "    });",
              "    pm.test('Shopify customer ID returned', function() {",
              "        pm.expect(json.shopify_customer_id).to.not.be.empty;",
              "    });",
              "    console.log('User: ' + json.name + ' | Email: ' + json.email);",
              "}"
            ],
            "type": "text/javascript"
          }
        }
      ],
      "request": {
        "method": "GET",
        "header": [
          {
            "key": "Authorization",
            "value": "Bearer {{id_token}}",
            "description": "Pass the id_token from Step 4 (auto-filled)"
          }
        ],
        "url": {
          "raw": "{{base_url}}/api/oidc/userinfo",
          "host": ["{{base_url}}"],
          "path": ["api", "oidc", "userinfo"]
        },
        "description": "Returns the authenticated customer's details.\nPass the id_token from Step 4 as Bearer token.\nReturns: sub, email, email_verified, name, phone, shopify_customer_id"
      },
      "response": [
        {
          "name": "Success Response",
          "status": "OK",
          "code": 200,
          "body": "{\n  \"sub\": \"3\",\n  \"email\": \"mishranimesh067@gmail.com\",\n  \"email_verified\": true,\n  \"name\": \"Nimesh Mishra\",\n  \"phone\": \"919099868381\",\n  \"shopify_customer_id\": \"9312912310334\"\n}"
        },
        {
          "name": "Error - Invalid Token",
          "status": "Unauthorized",
          "code": 401,
          "body": "{\"error\": \"invalid_token\"}"
        }
      ]
    },
    {
      "name": "Step 6 - Logout",
      "request": {
        "method": "GET",
        "header": [],
        "url": {
          "raw": "{{base_url}}/api/oidc/logout?post_logout_redirect_uri={{post_logout_redirect_uri}}",
          "host": ["{{base_url}}"],
          "path": ["api", "oidc", "logout"],
          "query": [
            {
              "key": "post_logout_redirect_uri",
              "value": "{{post_logout_redirect_uri}}",
              "description": "Must be registered in oidc_clients table post_logout_redirect_uris column"
            }
          ]
        },
        "description": "⚠️ Turn OFF 'Automatically follow redirects' in Postman Settings.\n\nLogs out the user and redirects to the registered post_logout_redirect_uri.\nReturns 302 redirect to the provided URI."
      },
      "response": [
        {
          "name": "Success Response",
          "status": "Found",
          "code": 302,
          "header": [
            {
              "key": "Location",
              "value": "https://apg-uae.myshopify.com"
            }
          ],
          "body": ""
        },
        {
          "name": "Error - Invalid URI",
          "status": "Bad Request",
          "code": 400,
          "body": "{\n  \"success\": false,\n  \"status_code\": 400,\n  \"message\": \"invalid post_logout_redirect_uri\",\n  \"data\": []\n}"
        }
      ]
    },
    {
      "name": "Extra - Show Login Form (Browser)",
      "request": {
        "method": "GET",
        "header": [],
        "url": {
          "raw": "{{base_url}}/api/oidc/authorize?response_type=code&client_id={{client_id}}&redirect_uri={{redirect_uri}}&state=test123&scope=openid email",
          "host": ["{{base_url}}"],
          "path": ["api", "oidc", "authorize"],
          "query": [
            {
              "key": "response_type",
              "value": "code"
            },
            {
              "key": "client_id",
              "value": "{{client_id}}"
            },
            {
              "key": "redirect_uri",
              "value": "{{redirect_uri}}"
            },
            {
              "key": "state",
              "value": "test123"
            },
            {
              "key": "scope",
              "value": "openid email"
            }
          ]
        },
        "description": "Opens the HTML login form. Best viewed in a browser.\nCopy the URL and open in browser to see the login page UI."
      },
      "response": [
        {
          "name": "Success Response",
          "status": "OK",
          "code": 200,
          "body": "<html><body><h1>Login</h1><form>...</form></body></html>"
        }
      ]
    }
  ]
}
