AI Computer Institute
Expert-curated CS & AI curriculum aligned to CBSE standards. A bharath.ai initiative. About Us

API Authentication and Security: Keys, Tokens, and OAuth

📚 APIs & Data Engineering⏱️ 22 min read🎓 Grade 9
✍️ AI Computer Institute Editorial Team Updated: August 2026 CBSE-aligned · Peer-reviewed · 22 min read
Content curated by subject matter experts with IIT/NIT backgrounds. All chapters are fact-checked against official CBSE/NCERT syllabi.

Open a Python shell and try this: call a public astronomy API that shows how many people are in space right now. No sign-up, no password, no key — it just answers. Now try calling the crop-price API on data.gov.in, India's open government data portal, the same simple way. You get back {"message": "Invalid API key"} even though you typed the URL exactly as shown in the documentation. Two APIs, two completely different reactions to a stranger showing up at the door. Why does one API answer anyone, while the other demands proof of who's asking before it says a word? That question is what this chapter answers — and the answer turns out to matter far beyond just "getting the request to work." Get authentication wrong and you can leak secrets that let strangers spend your money, read your private files, or lock you out of your own account.

Why an API would ever ask "who are you?"

An API that gives everyone the same answer for free, forever, has no reason to check identity — the astronomy-in-space API doesn't care who's asking because the answer costs it almost nothing and reveals nothing sensitive. But most useful APIs exist for one of these reasons, and each one forces the API to identify its caller:

  • Metering and billing. Many APIs are free only up to a limit, then charge per call. To bill correctly, the server must know which customer made each request.
  • Abuse and overload prevention. If anyone could fire unlimited requests anonymously, one careless script (or a deliberate attacker) could overwhelm the server for everyone else.
  • Personalised or private data. An API that returns your Gmail messages or your bank balance obviously cannot hand that data to whoever asks — it must first establish who "you" are.

The mechanism an API uses to answer "who is calling?" is called authentication. Note carefully that authentication is not the same question as "what is this caller allowed to do?" — that second question is called authorization, and it comes after authentication. A security guard checking your college ID card at the gate is authenticating you (confirming you really are a registered student); deciding whether that ID lets you into the physics lab versus only the library is authorization. Keep these two words separate — CBSE exam answers and real-world bug reports both frequently confuse them, and the rest of this chapter depends on the distinction.

The simplest fix: an API key

The most common way an API identifies a caller is the API key — a long, unique string issued to you when you register for the API, rather like a library membership card. The card doesn't prove which specific book you're allowed to borrow today; it just proves "this person is a registered member of this library," and every time you check out a book, the librarian records it against your card number. An API key works the same way: it's tied to your registered account (or "app"), and the server logs every request against that key so it can meter usage, apply rate limits, and — if the key is ever misused — block just that one key without affecting anyone else.

Here is a working example, using Python's requests library, of calling an API that requires a key:

import requests

API_KEY = "8f3a9c2e1b7d4f60"          # issued when you register on data.gov.in

url = "https://api.data.gov.in/resource/RESOURCE_ID"
params = {
    "api-key": API_KEY,
    "format": "json",
    "limit": 5
}

response = requests.get(url, params=params)
print(response.status_code)          # 200 means the key was accepted
data = response.json()
print(data["records"][0])            # first record in the result

Trace through what happens: requests.get builds a URL like .../resource/RESOURCE_ID?api-key=8f3a9c2e1b7d4f60&format=json&limit=5 and sends it to the server. The server looks up 8f3a9c2e1b7d4f60 in its database of registered keys. If it exists and hasn't exceeded its quota, the server replies with status code 200 OK and a JSON body — most data.gov.in datasets wrap their results in a list under the key "records", so data["records"][0] is the first row. If the key were wrong or missing, the server would instead reply with status code 401 Unauthorized and no data.

Notice something important about where the key travels in that example: it's inside the URL's query string, as ?api-key=.... This is simple and common for read-only, low-stakes APIs, but it has a real weakness — URLs get logged everywhere: in your browser history, in the server's access logs, in any proxy sitting in between. A key that lives in the URL is a key that ends up copy-pasted into places you didn't intend. The safer alternative, which you'll see below, is to send the key inside an HTTP header instead of the URL, since headers are not stored in browser history and are logged far less often by intermediate systems.

Worked example: doing the arithmetic behind a rate limit

Suppose an API's documentation states a quota of 1,000 requests per key per day, and your monitoring dashboard shows that by 3:00 pm (15 hours into the day) your app has already made 734 requests. Two useful numbers follow directly from this:

Requests remaining today = 1000 − 734 = 266.

Average rate so far = 734 requests ÷ 15 hours ≈ 48.9 requests/hour.

If that same average rate continues for the remaining 9 hours of the day, projected additional usage ≈ 48.9 × 9 ≈ 440 requests, giving a projected total of 734 + 440 ≈ 1174 — which is over the 1,000 limit. This is exactly the kind of simple arithmetic a real engineer runs before shipping a feature: if the projected total exceeds the quota, the app needs to either cache results, reduce polling frequency, or request a higher quota — before users start seeing failed requests, not after.

The trouble with plain API keys — and the fix: tokens

An API key has two weaknesses that become serious once real user accounts are involved. First, it's usually a static, long-lived secret — the same string works forever until someone manually revokes it, so a leaked key stays dangerous indefinitely. Second, a single API key typically identifies an application, not an individual end user — if your quiz app has one API key shared by all its users, the server can't tell your requests apart from another user's.

This is where a token — specifically an access token — improves on the plain key. A token is also just a string sent with each request, but it is normally issued after a specific user logs in, it is tied to that one user's permissions, and critically, it expires after a short time (commonly an hour). The standard way to send a token is inside the Authorization HTTP header, using the word Bearer (meaning: "whoever bears/holds this token is authorized"):

import requests

ACCESS_TOKEN = "ya29.a0AfH6SMBx7pQ3zK9r"   # short-lived, issued after login

headers = {"Authorization": "Bearer " + ACCESS_TOKEN}

response = requests.get(
    "https://www.googleapis.com/drive/v3/files",
    headers=headers,
    params={"pageSize": 5}
)

print(response.status_code)      # 200 = token accepted
for f in response.json()["files"]:
    print(f["name"])

Trace this one too: the header sent is literally the text Authorization: Bearer ya29.a0AfH6SMBx7pQ3zK9r. The server checks that this exact token was issued, that it hasn't expired, and that it's allowed to list files. If all three checks pass, it replies 200 with a JSON body containing a list under "files", and the loop prints each file's "name". Because the token is tied to one logged-in user and expires quickly, a leaked token is far less dangerous than a leaked long-lived API key — the damage window is measured in minutes to hours, not "until someone remembers to rotate it."

Worked example: is this token still valid?

Tokens carry an expiry, usually expressed as expires_in — a number of seconds valid from the moment of issue. Define a simple function of time: a token is valid at a given clock time t if t < expiry_time, where expiry_time = issued_at + expires_in.

Say a token is issued at 10:00:00 with expires_in = 3600 (seconds). Since 3600 seconds = 60 minutes, expiry_time = 10:00:00 + 60 min = 11:00:00.

  • A request at 10:47: elapsed = 47 minutes = 2820 seconds; remaining = 3600 − 2820 = 780 seconds = 13 minutes. Since 10:47 < 11:00, the token is still valid — the server answers 200.
  • A request at 11:15: this is 15 minutes past expiry_time. Since 11:15 is not < 11:00, the token has expired — the server replies 401 Unauthorized, even though nothing else about the request changed.

A well-built app doesn't make the user log in again at 11:15. Alongside the short-lived access token, the login process also issues a long-lived refresh token, which the app can silently exchange for a brand-new access token whenever the old one expires — the user never notices the handoff.

Common misconception: "HTTPS keeps my hardcoded key safe"

A mistake worth naming directly: many students assume that because their app talks to the API over HTTPS, it's fine to write the API key directly into their website's JavaScript or their Android app's source code. This confuses two completely different things. HTTPS (TLS) encrypts the data travelling between your device and the server, so someone eavesdropping on the same Wi-Fi network cannot read your key while it's in transit. It does nothing whatsoever to protect a key that is sitting in plain text inside code the user's browser downloads and can view instantly with "View Page Source," or inside an Android APK that can be decompiled with free tools in minutes. HTTPS protects the pipe; it says nothing about what you put inside the package that travels through the pipe and then sits, fully readable, on someone else's device.

The practical consequence: any secret placed in front-end (browser or mobile app) code should be treated as public. This is also why secrets should never be committed to a public GitHub repository — automated bots continuously scan public commits for text patterns that look like API keys, and a leaked key can be found and abused within minutes of being pushed. The standard fix is to keep secret keys only on your own server, in an environment variable that never gets written into the code you share, and have your server make the authenticated call on the client's behalf.

The bigger problem OAuth was built to solve

Tokens fix the "long-lived static secret" problem, but they raise a new question: how does an app get a token for your Google account in the first place, without you handing over your actual Google password? Before a better answer existed, this was genuinely how it worked on many sites — a third-party app would simply ask for your email password, log in as you, and do whatever it needed. This was a disaster for three reasons: the app now had unlimited access to your entire account, not just the one feature it needed; you had no way to revoke that access without changing your password everywhere; and you had just trained yourself to type your real password into random third-party apps, making phishing far easier.

OAuth 2.0 is the industry-standard protocol that solves this by never letting the third-party app see your password at all. Instead, you type your password only on Google's own login page, and Google hands the app a limited, revocable, scoped token instead. This is the mechanism behind every "Sign in with Google" or "Continue with Google" button you've seen on Indian apps and websites.

OAuth defines four roles, which are easiest to fix in memory with a concrete example — a quiz app that wants to save your results as a file in your Google Drive:

  • Resource Owner — you, the user who owns the Google Drive data.
  • Client — the quiz app, which wants access but does not own the data.
  • Authorization Server — Google's login-and-consent system, which checks your identity and issues tokens.
  • Resource Server — the actual Google Drive API that holds your files and will accept a valid token.

The diagram below traces the full exchange, numbered in the order the messages actually happen:

You (Resource Owner) Quiz App (Client) Google (Auth + Resource Server) 1. Tap "Sign in with Google" 2. Redirect: request login, scope=drive.file 3. Show Google login + consent screen 4. Enter Google password + Approve 5. Redirect back with authorization code 6. Server-to-server: code + client secret 7. Access token + refresh token issued 8. GET /files Authorization: Bearer token 9. Returns file list (read/write this app's files only) Key point: steps 3 and 4 happen directly between you and Google. The Quiz App never sees your Google password — it only ever receives a limited, revocable token.

Walk through why each step matters, not just what it does. Step 2's scope=drive.file tells Google exactly what the app is asking for — access only to files the app itself creates, not your entire Drive. Steps 3 and 4 happen on Google's own page, inside Google's own login form, which is precisely why the quiz app never learns your password. Step 5 hands the app a short-lived authorization code rather than a token directly — this code is single-use and nearly worthless on its own. Step 6 is the crucial security step: exchanging that code for an actual access token requires the app's client secret, a credential known only to the app's own server, so this exchange happens server-to-server, never inside the user's browser where the code could be intercepted. Only after that exchange does the app finally receive a real access token in step 7, which it then uses exactly like the Bearer-token example earlier, in step 8, and Google enforces the granted scope when it answers in step 9 — it will refuse any request for files the app didn't create, even with a perfectly valid token, because that request falls outside the approved scope.

Common misconception: "OAuth logs you in"

It's tempting to think the "Sign in with Google" button uses OAuth to prove your identity to the app. Strictly, OAuth 2.0's actual job is authorization — granting an app limited access to specific resources — not authentication. Notice that nothing in the numbered flow above technically tells the quiz app who you are; it only receives permission to read certain Drive files. In practice, most "Sign in with Google" buttons layer a separate, closely related protocol called OpenID Connect on top of OAuth, which adds a small identity token (containing your verified email and name) alongside the access token. So the precise statement is: OAuth grants access; OpenID Connect (built on top of OAuth) is what actually logs you in. Conflating the two is extremely common — even in professional job postings — but knowing the distinction is exactly the kind of precision that separates a surface-level understanding from a correct one.

401 versus 403: two different security failures

Once scopes are involved, two HTTP status codes that beginners often treat as interchangeable become clearly distinct. 401 Unauthorized means the server doesn't know who you are — no token was sent, or the token is invalid or expired (exactly what you calculated in the 11:15 example earlier). 403 Forbidden means the opposite: the server knows exactly who you are, your token is perfectly valid, but you are not permitted to do the specific thing you asked for. If the quiz app's token has only the drive.file scope (create/read its own files) and it attempts to DELETE some other file in your Drive that it didn't create, Google will not reply 401 — the token is genuine — it will reply 403, because a valid identity is still not the same as valid permission. This is the authentication-versus-authorization distinction from the start of the chapter, now visible as two concrete, different numbers on the wire.

Check your understanding

  • Q1. A weather app stores its API key directly in the JavaScript file it sends to every visitor's browser, but the app only ever talks to the weather server over HTTPS. Is the key safe? Answer: No. HTTPS only protects data while it travels between browser and server; it does nothing to hide a secret that is sitting inside code the browser downloads and can display via "View Page Source." The key should live on the app's own server instead.
  • Q2. A token is issued at 14:20:00 with expires_in = 1800 seconds. Is a request made at 14:52 still valid? Answer: 1800 seconds = 30 minutes, so expiry is 14:50:00. A request at 14:52 is 2 minutes past expiry, so the token has expired — the server will return 401, and the app should use its refresh token to get a new one.
  • Q3. Explain, in one sentence each, the difference between an API key and an OAuth access token. Answer: An API key is typically a long-lived secret identifying an application (or developer account) and rarely expires on its own; an OAuth access token is short-lived, tied to one specific user's consented permissions, and expires automatically within a set time.
  • Q4. An app has a valid, unexpired access token with scope read-only, and it sends a POST request to create a new file. What status code should it expect, and why not the other one? Answer: 403 Forbidden, not 401 — the token itself is genuine and identifies the user correctly (authentication succeeded), but the scope granted doesn't permit writing (authorization failed).
  • Q5. Why does the OAuth flow exchange the authorization code for a token using a "client secret," on the server, instead of just returning the access token straight to the browser in step 5? Answer: The redirect in step 5 passes through the user's browser, where a code sitting in a URL could potentially be intercepted or logged; requiring the client secret — known only to the app's own backend — for the final exchange means a bare authorization code is useless to anyone who doesn't also control the app's server, adding a second layer of proof beyond just "I have the code."

Summary

APIs that meter usage, guard against abuse, or hold private data must first answer "who is calling?" — that is authentication, and it is a distinct question from "what is this caller allowed to do?", which is authorization. An API key is the simplest answer: a long-lived string identifying an application, best sent in a header rather than a URL, and useful mainly for metering and coarse rate-limiting. An access token improves on this for real user accounts: it's short-lived, tied to one user's actual permissions, sent as Authorization: Bearer <token>, and paired with a longer-lived refresh token so users aren't forced to log in repeatedly. OAuth 2.0 exists to issue those tokens safely — letting a third-party app request limited, scoped, revocable access without ever seeing the user's actual password, by routing the login and consent steps through the real account provider and exchanging a one-time authorization code for a token using a secret only the app's own server holds. Two misconceptions are worth carrying forward deliberately: HTTPS protects data in transit, not secrets embedded in code you distribute; and OAuth by itself grants access, it doesn't prove identity — that additional layer is OpenID Connect. Finally, 401 and 403 encode this exact distinction as two different, precise signals on the wire: "I don't know who you are" versus "I know exactly who you are, and the answer is still no."

← JSON and Data Formats: The Language of APIsWeb Scraping and Data Extraction: Ethics and Practice →

Found this useful? Share it!

📱 WhatsApp 🐦 Twitter 💼 LinkedIn