Back to Blog
Part 5 of 8
AzureServerlessAzure FunctionAuthenticationOAuth2

Azure Functions Tutorial: API Authentication

October 1, 20222 min read
Share:

API Authentication with Azure AD#

Now that we have Flask APIs running, let's add authentication using Azure AD.

Note

Azure Functions has built-in Identity feature, but for granular control over specific APIs and custom roles, we implement authentication in code.

OAuth2 Authentication Flow#

sequenceDiagram
    participant Client
    participant AzureAD
    participant FunctionApp
    
    Client->>AzureAD: Request Token (client_credentials)
    AzureAD-->>Client: Access Token (JWT)
    Client->>FunctionApp: API Request + Bearer Token
    FunctionApp->>FunctionApp: Validate JWT
    FunctionApp-->>Client: API Response

Create Azure AD App#

  1. Navigate to Azure PortalAzure Active DirectoryApp registrationsNew Registration

  2. Configure the app:

    • Name: functiondemo-ad-server-app
    • Supported Account Types: Single tenant
  3. Set Application ID URI:

    • Copy Application (client) ID
    • Go to Expose an API tab
    • Set URI: api://<<Application (client) ID>>
  4. Create Custom Role:

    • Go to App Roles tab
    • Create role: GetSecret

Implementation#

Error Codes (src/configs/error_codes.py)#

from box import Box

def auth_error():
    return {
        "authorization_header_missing": {
            "message": {"code": "AuthorizationHeaderMissing", "description": "Authorization header is missing"},
            "status_code": 401
        },
        "token_expired": {
            "message": {"code": "AuthorizationTokenExpired", "description": "Token is expired"},
            "status_code": 401
        },
        "invalid_claims": {
            "message": {"code": "InvalidAuthorizationClaims", "description": "Invalid claims"},
            "status_code": 401
        }
    }

errors = Box(auth_error())

JWT Verification (src/authz/verify.py)#

from functools import wraps
import jwt
from flask import request
from jwt import PyJWKClient
from src.configs import adapp

def protected(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        token = get_token_auth_header()
        jwk_client = PyJWKClient(adapp.JWT_URL)
        signing_key = jwk_client.get_signing_key_from_jwt(token)
        
        payload = jwt.decode(
            token,
            signing_key.key,
            algorithms=["RS256"],
            audience=adapp.API_AUDIENCE,
            issuer=adapp.ISSUER
        )
        
        if 'roles' not in payload:
            raise AppError(errors.invalid_claims)
            
        return f(*args, **kwargs)
    return decorated

Protect Your Route#

from src.authz.verify import protected

@app.route("/vault", methods=['GET'])
@protected
def get_secret():
    secret = request.args.get("secret")
    return jsonify(message=f"Secret: {secret}"), 200

Test Authentication#

Without token:

{
  "code": "AuthorizationHeaderMissing",
  "description": "Authorization header is missing from the request"
}
YS

Yogesh Sharma

Principal Software Engineer @ Intuit