Skip to main content

Overview

The Short-URL API uses JWT (JSON Web Token) based authentication to secure password-protected short URLs. Only URLs created with a password require authentication for management operations.
URLs without passwords do not require authentication and cannot be managed after creation. They are public and permanent.

Authentication Flow

1

Create a Password-Protected URL

When creating a short URL, provide a password (3-20 characters). The password is hashed using PBKDF2-SHA256 before storage.
main.py:85-86
entry.url_pass=hash_password(entry.url_pass)
2

Login to Get Access Token

Use the /login endpoint with your URL code and password to receive a JWT access token.
POST /login
{
  "url_code": "mylink",
  "url_pass": "mypassword"
}
3

Use Token for Protected Operations

Include the token in the Authorization header as a Bearer token for all management operations.
Authorization: Bearer <your_access_token>

Token Generation

Tokens are generated using the create_access_token function from auth_utils.py:
auth_utils.py:20-25
def create_access_token(data: dict,expire_delta: timedelta = None):
    to_encode=data.copy()
    expire = datetime.utcnow()+(expire_delta if expire_delta else timedelta(minutes=TOKEN_EXPIRE))
    to_encode.update({"exp":expire})
    encoded_jwt=jwt.encode(to_encode, SECRET_KEY,algorithm=ALGORITHM)
    return encoded_jwt

Token Structure

Tokens contain the following payload:
  • url_id: MongoDB ObjectId of the URL entry (used to identify the URL)
  • exp: Expiration timestamp (Unix timestamp)
The token stores the _id from the database, not the url_code. This provides an additional security layer.

Token Expiration

By default, tokens expire after 5 minutes, configured via the TOKEN_EXPIRE environment variable:
auth_utils.py:8
TOKEN_EXPIRE=int(os.getenv("TOKEN_EXPIRE", "5"))
Expired tokens will be rejected with a 403 Forbidden error. Use the /refresh_token endpoint to obtain a new token before expiration.

Login Endpoint

The /login endpoint authenticates users and returns an access token:

Request Example

POST /login
Content-Type: application/json

{
  "url_code": "mylink",
  "url_pass": "mypassword"
}

Response Example

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "bearer"
}

Implementation Details

main.py:97-106
@app.post("/login")
async def login(login_entry:LoginEntry):
    entry = await app.URLMap.find_one({"url_code": login_entry.url_code})
    if entry and len(entry['url_pass'])==0:
        raise HTTPException(status_code=400, detail="Invalid request")
    elif entry and verify_password(login_entry.url_pass,entry.get("url_pass")):
        access_token = create_access_token(data={"url_id": str(entry["_id"])})
        return {"access_token":access_token, "token_type":"bearer"}
    else:
        raise HTTPException(status_code=401, detail="Invalid credentials")
You cannot login to URLs without passwords - attempting to do so returns a 400 error.

Using Bearer Tokens

All protected endpoints require a valid Bearer token in the Authorization header.

Example Request

GET /details
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Authentication Process

The API uses FastAPI’s HTTPBearer security scheme:
main.py:63-70
async def authenticate(credentials:HTTPAuthorizationCredentials):
    if not credentials:
        raise HTTPException(status_code=status.HTTP_403_UNAUTHORIZED,details="Missing Credentials")
    token=credentials.credentials
    payload=decode_access_token(token)
    if not payload:
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN,detail="Invalid or expired token")
    return await get_url_code_by_id(payload['url_id'])
1

Extract Token

The token is extracted from the Authorization: Bearer <token> header.
2

Decode and Validate

The token is decoded using the SECRET_KEY and validated for expiration.
3

Retrieve URL Code

The url_id from the token payload is used to fetch the corresponding url_code from the database.

Token Validation

Use the /validate_token endpoint to check if a token is valid:
GET /validate_token
Authorization: Bearer <your_access_token>
Response (Valid Token):
{
  "message": "Access token is Valid"
}
Response (Invalid Token):
{
  "detail": "Invalid or expired token"
}

Token Refresh

Before your token expires, use the /refresh_token endpoint to obtain a new token:
GET /refresh_token
Authorization: Bearer <your_current_token>

Implementation

main.py:204-212
@app.post("/refresh_token")
async def refresh_token(credentials:HTTPAuthorizationCredentials=Depends(security)):
    url_code=await authenticate(credentials)
    entry = await app.URLMap.find_one({"url_code": url_code})
    if entry and len(entry.get("url_pass"))>0:
        access_token = create_access_token(data={"url_id": str(entry["_id"])})
        return {"access_token":access_token, "token_type":"bearer"}
    else:
        raise HTTPException(status_code=404, detail="Invalid or expired token")
Refresh your token periodically in long-running applications to maintain continuous access without re-authenticating.

Password Security

Password Hashing

Passwords are hashed using PBKDF2-SHA256 via the passlib library:
auth_utils.py:10-15
pwd_context = CryptContext(schemes=["pbkdf2_sha256"], deprecated="auto")

def hash_password(password: str):
    if len(password)==0:
        return ""
    return pwd_context.hash(password)

Password Verification

auth_utils.py:17-18
def verify_password(plain_password: str, hashed_password: str):
    return pwd_context.verify(plain_password,hashed_password)
Passwords are never stored in plain text. They are immediately hashed before database insertion.

Security Best Practices

While the API allows passwords as short as 3 characters, use longer, complex passwords for better security.
  • Never expose tokens in URLs or logs
  • Store tokens in secure storage (e.g., environment variables, secure cookie storage)
  • Clear tokens from memory when no longer needed
  • Implement automatic token refresh before expiration
  • Handle 403 errors gracefully by redirecting to login
  • Don’t retry failed requests indefinitely
The SECRET_KEY environment variable is critical for JWT security:
  • Use a cryptographically secure random string
  • Never commit SECRET_KEY to version control
  • Rotate SECRET_KEY periodically (invalidates all tokens)
Always use HTTPS in production to prevent token interception during transmission.

Error Responses

Status CodeErrorDescription
400Invalid requestAttempting to login to a URL without a password
401Invalid credentialsWrong password or URL code
403UnauthorizedMissing credentials header
403ForbiddenInvalid or expired token
404Invalid or expired tokenToken validation failed or URL deleted

Protected Endpoints

The following endpoints require authentication:
  • POST /change_password - Change URL password
  • DELETE /delete - Delete short URL
  • PATCH /pause - Pause URL redirection
  • PATCH /resume - Resume URL redirection
  • PATCH /reset_hits - Reset hit counter
  • PATCH /change_url - Change destination URL
  • GET /details - Get URL details and statistics
  • GET /validate_token - Validate current token
  • GET /refresh_token - Get new access token
All protected endpoints use the Depends(security) dependency to enforce authentication.

Build docs developers (and LLMs) love