Token Expired
Handle expired access tokens by detecting errors and refreshing automatically
Handling Expired Access Tokens
Access tokens expire after 15 minutes. When you make a request with an expired token, the API returns an UNAUTHENTICATED error:
{
"errors": [
{
"message": "Authentication required",
"extensions": { "code": "UNAUTHENTICATED" }
}
]
}The solution is to detect the error, refresh the token, and retry the request.
#!/bin/bash
API_URL="https://data.nextschool.io/"
# Assume these are set from a previous login
# ACCESS_TOKEN="eyJhbGciOiJIUzI1NiIs..."
# REFRESH_TOKEN="a1b2c3d4-e5f6-7890-abcd-ef1234567890"
QUERY='{ students(limit: 5) { id fullName classroomName } }'
# Step 1: Make a request with the (possibly expired) access token
RESPONSE=$(curl -s -X POST "$API_URL" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-d "{\"query\": \"$QUERY\"}")
# Step 2: Check if we got UNAUTHENTICATED
CODE=$(echo "$RESPONSE" | jq -r '.errors[0].extensions.code // empty')
if [ "$CODE" = "UNAUTHENTICATED" ]; then
echo "Token expired, refreshing..."
# Step 3: Refresh the access token
REFRESH_RESPONSE=$(curl -s -X POST "$API_URL" \
-H "Content-Type: application/json" \
-d "{
\"query\": \"mutation { refreshToken(refreshToken: \\\"$REFRESH_TOKEN\\\") { accessToken expiresIn } }\"
}")
ACCESS_TOKEN=$(echo "$REFRESH_RESPONSE" | jq -r '.data.refreshToken.accessToken')
if [ "$ACCESS_TOKEN" = "null" ] || [ -z "$ACCESS_TOKEN" ]; then
echo "Refresh token also expired. Please login again."
exit 1
fi
echo "Token refreshed successfully."
# Step 4: Retry the original request with the new token
RESPONSE=$(curl -s -X POST "$API_URL" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-d "{\"query\": \"$QUERY\"}")
fi
echo "$RESPONSE" | jq '.data'const API_URL = 'https://data.nextschool.io/';
let accessToken = '...'; // from login
let refreshToken = '...'; // from login
async function fetchWithRefresh(query, variables = {}) {
// Step 1: Try the request
let res = await fetch(API_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${accessToken}`
},
body: JSON.stringify({ query, variables })
});
let result = await res.json();
// Step 2: Check for expired token
if (result.errors?.[0]?.extensions?.code === 'UNAUTHENTICATED') {
console.log('Token expired, refreshing...');
// Step 3: Refresh the token
const refreshRes = await fetch(API_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: `mutation { refreshToken(refreshToken: "${refreshToken}") {
accessToken expiresIn
}}`
})
});
const refreshResult = await refreshRes.json();
if (refreshResult.errors) {
throw new Error('Refresh token expired. Please login again.');
}
accessToken = refreshResult.data.refreshToken.accessToken;
console.log('Token refreshed successfully.');
// Step 4: Retry with the new token
res = await fetch(API_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${accessToken}`
},
body: JSON.stringify({ query, variables })
});
result = await res.json();
}
return result.data;
}
// Usage
const students = await fetchWithRefresh(
'{ students(limit: 5) { id fullName classroomName } }'
);
console.log(students);import requests
API_URL = 'https://data.nextschool.io/'
access_token = '...' # from login
refresh_token = '...' # from login
def fetch_with_refresh(query: str, variables: dict = None):
global access_token
# Step 1: Try the request
resp = requests.post(API_URL, headers={
'Content-Type': 'application/json',
'Authorization': f'Bearer {access_token}',
}, json={'query': query, 'variables': variables or {}})
result = resp.json()
# Step 2: Check for expired token
errors = result.get('errors', [])
if errors and errors[0].get('extensions', {}).get('code') == 'UNAUTHENTICATED':
print('Token expired, refreshing...')
# Step 3: Refresh the token
refresh_resp = requests.post(API_URL, json={
'query': f'''mutation {{
refreshToken(refreshToken: "{refresh_token}") {{
accessToken expiresIn
}}
}}'''
})
refresh_result = refresh_resp.json()
if 'errors' in refresh_result:
raise Exception('Refresh token expired. Please login again.')
access_token = refresh_result['data']['refreshToken']['accessToken']
print('Token refreshed successfully.')
# Step 4: Retry with the new token
resp = requests.post(API_URL, headers={
'Content-Type': 'application/json',
'Authorization': f'Bearer {access_token}',
}, json={'query': query, 'variables': variables or {}})
result = resp.json()
return result['data']
# Usage
students = fetch_with_refresh(
'{ students(limit: 5) { id fullName classroomName } }'
)
print(students)<?php
$apiUrl = 'https://data.nextschool.io/';
$accessToken = '...'; // from login
$refreshToken = '...'; // from login
function fetchWithRefresh(string $query, array $variables = []): array {
global $apiUrl, $accessToken, $refreshToken;
// Step 1: Try the request
$result = graphqlRequest($query, $accessToken);
// Step 2: Check for expired token
$code = $result['errors'][0]['extensions']['code'] ?? null;
if ($code === 'UNAUTHENTICATED') {
echo "Token expired, refreshing...\n";
// Step 3: Refresh the token
$refreshResult = graphqlRequest(
"mutation { refreshToken(refreshToken: \"$refreshToken\") { accessToken expiresIn } }"
);
if (isset($refreshResult['errors'])) {
throw new \RuntimeException('Refresh token expired. Please login again.');
}
$accessToken = $refreshResult['data']['refreshToken']['accessToken'];
echo "Token refreshed successfully.\n";
// Step 4: Retry with the new token
$result = graphqlRequest($query, $accessToken);
}
return $result['data'];
}
function graphqlRequest(string $query, ?string $token = null): array {
global $apiUrl;
$ch = curl_init($apiUrl);
$headers = ['Content-Type: application/json'];
if ($token) {
$headers[] = "Authorization: Bearer $token";
}
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_POSTFIELDS => json_encode(['query' => $query]),
CURLOPT_RETURNTRANSFER => true,
]);
$data = json_decode(curl_exec($ch), true);
curl_close($ch);
return $data;
}
// Usage
$students = fetchWithRefresh('{ students(limit: 5) { id fullName classroomName } }');
print_r($students);package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
const apiURL = "https://data.nextschool.io/"
var accessToken = "..." // from login
var refreshToken = "..." // from login
func fetchWithRefresh(query string) (map[string]any, error) {
// Step 1: Try the request
result, err := graphqlRequest(query, accessToken)
if err != nil {
return nil, err
}
// Step 2: Check for expired token
if errors, ok := result["errors"].([]any); ok && len(errors) > 0 {
errMap := errors[0].(map[string]any)
if ext, ok := errMap["extensions"].(map[string]any); ok && ext["code"] == "UNAUTHENTICATED" {
fmt.Println("Token expired, refreshing...")
// Step 3: Refresh the token
refreshQuery := fmt.Sprintf(`mutation {
refreshToken(refreshToken: "%s") { accessToken expiresIn }
}`, refreshToken)
refreshResult, err := graphqlRequest(refreshQuery, "")
if err != nil {
return nil, err
}
if _, hasErr := refreshResult["errors"]; hasErr {
return nil, fmt.Errorf("refresh token expired, please login again")
}
rt := refreshResult["data"].(map[string]any)["refreshToken"].(map[string]any)
accessToken = rt["accessToken"].(string)
fmt.Println("Token refreshed successfully.")
// Step 4: Retry with the new token
result, err = graphqlRequest(query, accessToken)
if err != nil {
return nil, err
}
}
}
return result["data"].(map[string]any), nil
}
func graphqlRequest(query, token string) (map[string]any, error) {
body, _ := json.Marshal(map[string]string{"query": query})
req, _ := http.NewRequest("POST", apiURL, bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
if token != "" {
req.Header.Set("Authorization", "Bearer "+token)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result map[string]any
json.NewDecoder(resp.Body).Decode(&result)
return result, nil
}
func main() {
students, err := fetchWithRefresh(`{ students(limit: 5) { id fullName classroomName } }`)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println(students)
}use reqwest::Client;
use serde_json::{json, Value};
const API_URL: &str = "https://data.nextschool.io/";
struct TokenStore {
access_token: String,
refresh_token: String,
}
async fn fetch_with_refresh(
client: &Client,
tokens: &mut TokenStore,
query: &str,
) -> Result<Value, Box<dyn std::error::Error>> {
// Step 1: Try the request
let mut result = graphql_request(client, query, &tokens.access_token).await?;
// Step 2: Check for expired token
if let Some(errors) = result["errors"].as_array() {
if let Some(first) = errors.first() {
let code = first["extensions"]["code"].as_str().unwrap_or("");
if code == "UNAUTHENTICATED" {
println!("Token expired, refreshing...");
// Step 3: Refresh the token
let refresh_query = format!(
r#"mutation {{ refreshToken(refreshToken: "{}") {{ accessToken expiresIn }} }}"#,
tokens.refresh_token
);
let refresh_result = graphql_request(client, &refresh_query, "").await?;
if refresh_result["errors"].is_array() {
return Err("Refresh token expired. Please login again.".into());
}
tokens.access_token = refresh_result["data"]["refreshToken"]["accessToken"]
.as_str()
.unwrap()
.to_string();
println!("Token refreshed successfully.");
// Step 4: Retry with the new token
result = graphql_request(client, query, &tokens.access_token).await?;
}
}
}
Ok(result["data"].clone())
}
async fn graphql_request(
client: &Client,
query: &str,
token: &str,
) -> Result<Value, Box<dyn std::error::Error>> {
let mut req = client.post(API_URL)
.header("Content-Type", "application/json");
if !token.is_empty() {
req = req.header("Authorization", format!("Bearer {}", token));
}
let resp: Value = req
.json(&json!({ "query": query }))
.send().await?
.json().await?;
Ok(resp)
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new();
let mut tokens = TokenStore {
access_token: "...".into(), // from login
refresh_token: "...".into(), // from login
};
let students = fetch_with_refresh(
&client,
&mut tokens,
r#"{ students(limit: 5) { id fullName classroomName } }"#,
).await?;
println!("{:#}", students);
Ok(())
}For a complete API client class that handles token refresh automatically, see the Full Client example.