CTCaliberTek
Course: Cybersecurity Fundamentals and Identity · Lesson 7 of 7Log in to track your progress and earn a certificate when you finish the course.
Cybersecurity

OAuth 2.0, OpenID Connect, and JWT Security Basics

Published July 14, 2026·Updated October 29, 2026·10 min read
Like SaveLog in to like, save, and track progress.

Introduction

OAuth 2.0 lets an application act on a user's behalf without ever seeing their password, and OpenID Connect adds a standard way to learn who the user is. They power most modern sign-in buttons and API access, and they are easy to get wrong. This lesson explains the flow you should use and the mistakes to avoid, following the current security best practices in RFC 9700.

Prerequisites

  • The IAM fundamentals lesson
  • Basic understanding of HTTP and JSON

Step-by-Step Guide

Step 1: Know the roles

The resource owner is the user. The client is the application that wants access. The authorization server authenticates the user and issues tokens. The resource server is the API that accepts those tokens. OAuth 2.0 is about authorization, delegating limited access to an API. OpenID Connect, OIDC, is a thin layer on top that adds authentication: it defines an ID token that tells the client who the user is and how they signed in.

Step 2: Use the authorization code flow with PKCE

The client creates a random code verifier and derives a code challenge from it using SHA-256. It sends the user to the authorization server with the challenge, the user signs in and approves, and the server redirects back with a short-lived authorization code. The client then exchanges the code and the original verifier for tokens, so a stolen code is useless without the verifier. RFC 9700 says public clients must use PKCE and recommends it for confidential clients too. Always send and verify a random state value to prevent request forgery.

import base64, hashlib, secrets

code_verifier = secrets.token_urlsafe(64)
digest = hashlib.sha256(code_verifier.encode("ascii")).digest()
code_challenge = base64.urlsafe_b64encode(digest).rstrip(b"=").decode("ascii")
state = secrets.token_urlsafe(16)

# Send the user to (parameters URL-encoded):
# https://auth.example.com/authorize
#   ?response_type=code
#   &client_id=YOUR_CLIENT_ID
#   &redirect_uri=https://app.example.com/callback
#   &scope=openid profile
#   &state=<state>
#   &code_challenge=<code_challenge>
#   &code_challenge_method=S256

Step 3: Avoid the discouraged flows

The implicit grant returns tokens in the browser's URL, where history, referrer headers, and scripts can expose them, so RFC 9700 says clients should not use it. The resource owner password credentials grant makes the client handle the user's password and blocks MFA and passkeys, and RFC 9700 says it must not be used. The authorization server must match redirect URIs exactly, never by pattern. OAuth 2.1 is still an in-progress draft that folds these lessons together: PKCE required, exact redirect matching, and no implicit or password grants.

Step 4: Do not confuse ID tokens and access tokens

An ID token is defined by OIDC, is a JWT, and is meant for the client to learn the user's identity, containing at least a subject identifier and details of how and when they authenticated. An access token is defined by OAuth and is meant for the API. The API must not accept an ID token as an access token, and an ID token should never be sent to an API. Access tokens may be JWTs or opaque strings, so clients should not try to read them.

Step 5: Validate JWTs correctly

A JWT has a header, payload, and signature, each Base64URL-encoded. The payload is readable by anyone, so never put secrets in it. When validating, follow RFC 8725: accept only the algorithms you expect, because attackers try the none algorithm and switching an RSA algorithm to a symmetric one to abuse the public key, and verify the signature, the issuer, the audience, and the expiry. Do not trust header values such as the key ID or key URL without checks. Use a maintained library and pass explicit expectations.

import jwt  # PyJWT

claims = jwt.decode(
    token,
    key=public_key,
    algorithms=["RS256"],            # only what you expect
    audience="https://api.example.com",
    issuer="https://auth.example.com",
)  # raises an exception if signature, aud, iss, or exp checks fail

FAQs

Is OAuth a login protocol?
Not by itself. OAuth delegates access to APIs. Use OpenID Connect when you need to log users in and learn who they are.
Where should a browser app store tokens?
Treat that as a risk decision. Tokens in storage that scripts can read are exposed to cross-site scripting, so many teams keep tokens on a backend and give the browser only a secure, HttpOnly session cookie, and apply strong XSS defenses regardless.

Summary

Use the authorization code flow with PKCE and exact redirect URIs, avoid implicit and password grants, keep ID tokens and access tokens separate, and validate JWTs with explicit algorithms, issuer, audience, and expiry checks.

Related tutorials

Comments

Log in or create a free account to join the discussion.

  • No comments yet. Be the first to comment.