Short-lived tokens
An option for securing communication using short-lived tokens.
How it works
- Mapy.com API – our server providing map services (
api.mapy.com), - Your server – the backend you operate,
- Your application – the client application running on the user’s device (web, mobile, or desktop application).
Instead of a permanent API key, your application receives a token valid for 15 minutes. Your server passes the token to the application after requesting it from the Mapy.com API using a secret. The secret never leaves your server. A copied token can therefore be misused for no more than 15 minutes, and an attacker cannot issue a new one themselves.
| Credential | Location | Secret? |
|---|---|---|
| Project secret | only on your server | yes |
| API key | only on your server | yes |
| Token (15 min) | in your application | no, anyone can see it |
The token inherits the restrictions of its API key (services, domain, IP, User-Agent), and usage is counted against that key.
Your application ──POST /api/mapy-token─► Your server ──POST /v1/auth/token (secret)──► Mapy.com API
Your application ◄──{token, expiresAt}─── Your server ◄──{token, expiresAt}──────────
Your application ──tiles, suggest… ?token=… ─────────────────────────────────────────► Mapy.com API
Setup in the API portal
1. Create a project secret. In the API project administration, under Secrets:
- The full secret value is displayed only once. Save it to your server configuration immediately.
- Optionally, restrict the secret to your server’s IP addresses (recommended).
- One secret is used for all API keys in the project.
- If the secret is leaked, revoke it. Tokens already issued remain valid until they expire.
2. Configure the API key for which you want to issue tokens:
| Key setting | Behavior |
|---|---|
| token access disabled (default) | only the key works; tokens cannot be issued for it |
| “Allow token access” | both the key and tokens work; suitable for a transition period |
| + “Require token” | only tokens work; the Mapy.com API rejects the key on its own (403) |
A copied API key only becomes useless once “Require token” is enabled. As long as only “Allow token access” is enabled, the Mapy.com API can still be called using the key alone.
Your server: one shared cached token
The Mapy.com token endpoint /v1/auth/token has a rate limit of 1 request/s (status code 429). Your server therefore distributes the same cached token to all users of your application and requests a new one only 2 minutes before expiration, approximately once every 13 minutes.
Where this runs: on your server (the example uses Node.js with Express; the approach is the same in other languages).
How it works:
- The
POST /api/mapy-tokenendpoint is the address your application calls. It only acceptsContent-Type: application/json(see the CORS section for the reason). - The
getToken()function returns the token from memory if it has more than 2 minutes left before expiration. - Otherwise, it requests a new token from the Mapy.com API. It sends the secret in a header and the API key in the request body.
- If multiple users request a token at the same time, they all wait for a single shared issuance request (the
issuingvariable). This sends just one request to the Mapy.com API.
// Load SECRET and API_KEY from your server configuration (e.g. environment variables)
let cached; // latest token { token, expiresAt }
let issuing; // request for a new token currently in progress
async function getToken() {
// The token in memory is still valid for at least 2 minutes → return it
if (cached && Date.now() < Date.parse(cached.expiresAt) - 120_000) return cached;
// Otherwise, request a new one; concurrent calls share the same request
issuing ??= fetch('https://api.mapy.com/v1/auth/token', {
method: 'POST',
headers: { 'X-MAPY-API-SECRET': SECRET, 'Content-Type': 'application/json' },
body: JSON.stringify({ apiKey: API_KEY }),
})
.then(r => (r.ok ? r.json() : Promise.reject(new Error(`Mapy.com API returned ${r.status}`))))
.finally(() => (issuing = null));
return (cached = await issuing);
}
// overPristup = your check for who may receive a token (authentication, rate limiting…)
app.post('/api/mapy-token', overPristup, async (req, res) => {
if (!req.is('application/json')) return res.sendStatus(415); // JSON only → forces a CORS preflight
res.set('Cache-Control', 'no-store'); // the response containing the token must not be cached
res.json(await getToken()); // { token, expiresAt }
});
Your application: using and refreshing the token
Where this runs: in your application on the user’s device. The example is a web map using Leaflet; the approach is the same in a mobile or desktop application.
How it works:
- Your application requests the token from your server, never directly from the Mapy.com API. It does not know the secret.
- It then uses the token to call the Mapy.com API directly: for map tiles and other features.
- Using
expiresAt, it schedules the next refresh for 2 minutes before expiration. This creates a loop that refreshes the token for as long as the application is running. - The map is not redrawn when the token is replaced. New tiles simply start loading with the new token.
let token; // current token
let layer; // map tile layer
async function refresh() {
// 1. Get a new token from your server
const res = await fetch('/api/mapy-token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: '{}',
});
const data = await res.json(); // { token, expiresAt }
token = data.token;
// 2. Add the token to the map tile URL (true = do not redraw the map)
const url = `https://api.mapy.com/v1/maptiles/basic/256/{z}/{x}/{y}?token=${token}`;
layer ? layer.setUrl(url, true) : (layer = L.tileLayer(url).addTo(map));
// 3. Schedule the next refresh for 2 minutes before expiration
setTimeout(refresh, Date.parse(data.expiresAt) - Date.now() - 120_000);
}
await refresh();
// Other Mapy.com API features: token in the `token` parameter or the X-MAPY-API-TOKEN header
const suggest = await fetch('https://api.mapy.com/v1/suggest?query=Praha', {
headers: { 'X-MAPY-API-TOKEN': token },
});
What the example omits for brevity, but your application should include:
- On a 401/403 response, request a new token and retry the request once.
- If the network is unavailable, retry the refresh after a short delay (e.g. 30 seconds), rather than immediately in a loop.
- When returning from the background, check whether the token is still valid. Timers slow down or stop in the background.
What this prevents and what it does not
Prevents:
- indefinite misuse of copied credentials, because the token expires after 15 minutes,
- exposure of the secret and API key through your application, because they are never sent to it,
- further token issuance after a leak: simply revoke the secret,
- use of the token on another website if the API key has a domain restriction (web only).
Does not prevent:
- misuse while the token is valid. Your application has access to the token, and so do its users. The token is not a secret; it only limits how long it can be used.
- requests using the token from consuming your quotas.
Your server decides who may obtain a token. If you want to restrict access to maps, protect the /api/mapy-token endpoint just like the rest of your application, for example by requiring user authentication or limiting the number of requests.
CORS applies only to the web: it prevents JavaScript on another website from reading your server’s response. It does not provide protection for mobile or desktop applications, or for calls made outside a browser.
Why POST with application/json: a browser will not send this type of request from another website without first making a preliminary request (CORS preflight). Your server does not approve the other domain, so the actual request is never sent. This prevents another website from using its visitors’ browsers to call your endpoint in bulk. That is why the server rejects other Content-Type values: a POST with text/plain or from an HTML form would go through without a preflight. Do not enable CORS for arbitrary origins (Access-Control-Allow-Origin: *) on the endpoint, otherwise any website could read the token.