Validating JWTs in Your Backend
When your frontend application authenticates users with the Agglestone Authentication and User Management Service, it receives JWT access tokens that can be used to make authenticated API calls. These JWT access tokens can also be used with your own backend services and APIs. If you have your own backend services or APIs, you can validate these Agglestone Authentication and User Management Service issued JWTs to verify that users have been authenticated and check their permissions.
Why Validate JWTs in Your Backend?
Validating JWTs in your backend provides several important benefits:
- Security: Ensure that only authenticated users can access your APIs
- User Identification: Identify which user is making the request
- Access Control: Use group memberships from the JWT to control what users can access
- Performance: All the information you need is in the token itself
Implementation Examples
Here are examples of how to validate JWTs in common backend frameworks. All examples use standard, well-maintained libraries that handle the complexity of JWT validation for you.
Recommended libraries and frameworks:
- C# / ASP.NET Core:
Microsoft.AspNetCore.Authentication.JwtBearer - Node.js / Express:
express-jwtandjwks-rsa - Python / FastAPI:
python-jose[cryptography]andhttpx
Don’t have your Tenant ID yet? Log into your account at https://portal.agglestone.com to find your Tenant ID. Then replace {tenantId} or your-tenant-id in the examples below with your own Tenant ID.
// Install: Microsoft.AspNetCore.Authentication.JwtBearer
// Add the configuration code below to your Program.cs file (or Startup.cs in older ASP.NET Core versions)
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
var builder = WebApplication.CreateBuilder(args);
var tenantId = "your-tenant-id"; // Get from https://portal.agglestone.com
var issuer = $"https://auth.agglestone.com/tenant/{tenantId}/v2.0/Auth";
// The JWT Bearer middleware automatically fetches, caches, and refreshes public keys from the JWKS endpoint
// It will refresh keys when it encounters a token with a key ID (kid) that isn't in the cache
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = issuer;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = issuer,
ValidateAudience = false, // Set to true and specify ValidAudience if needed
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
RequireSignedTokens = true,
ClockSkew = TimeSpan.Zero
};
});
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
// Add the controller code below to your API controllers
// In your controllers, use [Authorize] attribute
[Authorize]
[HttpGet("api/protected")]
public IActionResult GetProtectedData()
{
var userId = User.FindFirst("sub")?.Value; // User ID
var groups = User.FindAll("groups").Select(c => c.Value).ToList(); // Groups
return Ok(new { userId, groups });
}
// Install dependencies:
// npm install openid-client
// Add this code to your main server file (e.g., app.js, server.js, index.js)
import { Issuer } from 'openid-client';
const tenantId = 'your-tenant-id';
const issuerUrl = `https://auth.agglestone.com/tenant/${tenantId}/v2.0/Auth`;
const audience = 'your-api-audience';
// Discover OIDC configuration and create a client
const issuer = await Issuer.discover(issuerUrl);
const client = new issuer.Client({ client_id: 'api' });
async function validateToken(token: string) {
// This validates:
// 1. Signature (using keys from issuer discovery)
// 2. Expiry (exp), not-before (nbf), issued-at (iat)
// 3. Audience (aud) matches the expected API audience
// 4. Issuer (iss) matches the discovered issuer
const result = await client.jwtVerify(token, { audience });
return result.payload; // Contains claims like sub, groups, etc.
}
// Example usage:
const token = 'ey...'; // JWT from Authorization header
try {
const user = await validateToken(token);
console.log(user.sub, user.groups);
} catch {
console.error('Token is invalid or expired');
}
# Install: fastapi, authlib, httpx
# Add this code to your main FastAPI app file (e.g., main.py, app.py)
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from authlib.oidc.discovery import get_well_known
from authlib.jose import JsonWebToken
import httpx
app = FastAPI()
security = HTTPBearer()
TENANT_ID = "your-tenant-id"
ISSUER_URL = f"https://auth.agglestone.com/tenant/{TENANT_ID}/v2.0/Auth"
AUDIENCE = "your-api-audience"
# OIDC discovery to get endpoints (including jwks_uri)
oidc_config = get_well_known(f"{ISSUER_URL}/.well-known/openid-configuration")
jwt = JsonWebToken(['RS256']) # support only RS256
async def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)):
token = credentials.credentials
# Fetch the server's JWKS (public keys) dynamically
jwks_uri = oidc_config["jwks_uri"]
async with httpx.AsyncClient() as client:
jwks = (await client.get(jwks_uri)).json()
try:
# This validates signature, expiry, issuer, and audience
claims = jwt.decode(token, jwks)
claims.validate({
"iss": {"essential": True, "values": [ISSUER_URL]},
"aud": {"essential": True, "values": [AUDIENCE]}
})
return claims
except Exception:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid or expired token")
@app.get("/api/protected")
async def protected_route(token_data=Depends(verify_token)):
return {"userId": token_data["sub"], "groups": token_data.get("groups", [])}
How JWT Validation Works
The Agglestone Authentication and User Management Service signs all JWTs using RS256 (RSA with SHA-256), an asymmetric signing algorithm. This means the service uses a private key to sign tokens, and you use the corresponding public key to verify them.
Standard JWT validation libraries automatically:
- Verify the Signature: Check that the token was signed by the correct private key using the public key from the JWKS endpoint
- Validate the Issuer: Ensure the
iss(issuer) claim matches your tenant’s issuer URL - Check Expiration: Verify that the token hasn’t expired by checking the
expclaim - Validate Audience: Optionally verify that the token was issued for your application
The public keys needed for signature verification are available through the JWKS (JSON Web Key Set) endpoint, which is automatically discovered via the OpenID Connect discovery endpoint. Standard libraries handle fetching and caching these keys for you.
Accessing User Information from JWTs
Once you’ve validated a JWT, you can extract user information from the token claims. Here’s what you need to know:
User ID
The user ID is stored in the sub (subject) claim. This is a standard JWT claim that uniquely identifies the user.
var userId = User.FindFirst("sub")?.Value;
const userId = req.user.sub;
user_id = token_data.get("sub")
Groups
Group memberships are stored in the groups claim. This claim can appear multiple times in the JWT (one for each group), or it may be an array depending on how your validation library handles it.
When checking group membership, you’ll want to:
- Check if the user belongs to a specific group ID
- Grant or restrict access based on group membership
- Use groups for role-based access control in your application
var groups = User.FindAll("groups").Select(c => c.Value).ToList();
var isAdmin = groups.Contains("501b38ea-16af-4cd2-9f20-35675d2c001e");
const groups = Array.isArray(req.user.groups)
? req.user.groups
: [req.user.groups].filter(Boolean);
const isAdmin = groups.includes("501b38ea-16af-4cd2-9f20-35675d2c001e");
groups = token_data.get("groups", [])
if not isinstance(groups, list):
groups = [groups] if groups else []
is_admin = "501b38ea-16af-4cd2-9f20-35675d2c001e" in groups
Other Claims
JWTs may also include other useful claims such as:
name– User’s display nametid– Tenant IDexp– Token expiration timeiat– Token issued at time
Important Security Considerations
When validating JWTs in your backend, these security best practices are essential to protect your application and users:
⚠️ Verify the Issuer: Ensure the iss claim matches your tenant’s issuer: https://auth.agglestone.com/tenant/{tenantId}/v2.0/Auth
⚠️ Check Expiration: Reject expired tokens by validating the exp claim
⚠️ Never trust JWT contents without validating the signature. Always verify the token signature using the public key from the JWKS endpoint (accessible from the https://auth.agglestone.com/tenant/{tenantId}/v2.0/Auth/.well-known/openid-configuration metadata) to ensure the token hasn’t been tampered with.
⚠️ Always use HTTPS in production to protect tokens in transit and prevent man-in-the-middle attacks.
⚠️ Keep libraries updated – Use well-maintained, standard JWT validation libraries and keep them updated to protect against known vulnerabilities.
The standard JWT validation libraries used in the examples above handle most of these checks automatically when configured correctly.
> 📚 API Documentation: For detailed API documentation, request/response schemas, and to try out the endpoints interactively, visit the Swagger UI.
—
Ready to learn more? Check out the Quick Start Guide to see how to get authentication working in your frontend.