OpenID Connect is a thin layer on top of OAuth 2.0, enabling clients to verify the identity of the end-user based on the authentication performed by an authorization server, as well as to obtain basic profile information about the end-user.

Let’s see how this plays out with API Gateway. Imagine an API Gateway acting as a client to an Identity Provider (IdP) like Auth0, Okta, or even a custom OIDC server. The API Gateway’s job is to protect your backend APIs. When a client application (like a mobile app or a web frontend) wants to access your API, it first gets an ID token from the IdP. This ID token is a JSON Web Token (JWT) that contains claims about the authenticated user. The client then sends this ID token to the API Gateway, usually in an Authorization: Bearer <id_token> header.

Here’s a simplified flow:

  1. Client authenticates with IdP: The user logs in to the IdP. The IdP issues an ID token (JWT) and potentially an access token.
  2. Client calls API Gateway: The client includes the ID token in the Authorization header.
  3. API Gateway validates ID token: The API Gateway acts as a resource server. It needs to verify that the ID token is valid. This involves:
    • Checking the signature of the JWT.
    • Verifying the issuer (iss) claim matches the expected IdP.
    • Verifying the audience (aud) claim matches the API Gateway’s identifier.
    • Checking the expiration time (exp) and not-before time (nbf) claims.
    • Ensuring the token type (typ) is JWT.
  4. API Gateway authorizes request: If the token is valid, the API Gateway can then decide whether to allow the request to proceed to the backend based on other authorization rules (e.g., user roles, scopes).
  5. API Gateway forwards request: If authorized, the API Gateway forwards the request to the backend API, potentially passing along user information extracted from the ID token.

Let’s consider a concrete example using AWS API Gateway. Suppose you have an API that requires users to be authenticated. You can configure API Gateway to use a JWT authorizer.

Here’s a snippet of what that configuration might look like in AWS CloudFormation:

MyApi:
  Type: AWS::ApiGateway::RestApi
  Properties:
    Name: MySecureApi

MyApiAuthorizer:
  Type: AWS::ApiGateway::Authorizer
  Properties:
    Name: MyOidcAuthorizer
    RestApiId: !Ref MyApi
    Type: TOKEN
    IdentitySource: method.request.header.Authorization
    AuthorizerUri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyLambdaAuthorizerFunction.Arn}/invocations"
    # For JWT Authorizer, you'd typically use a Cognito User Pool or a custom authorizer
    # If using a custom authorizer that validates JWTs:
    # IdentitySource: method.request.header.Authorization
    # AuthorizerCredentials: !Ref MyLambdaExecutionRole
    # AuthorizerUri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyLambdaAuthorizerFunction.Arn}/invocations"

MyApiResource:
  Type: AWS::ApiGateway::Resource
  Properties:
    RestApiId: !Ref MyApi
    ParentId: !GetAtt MyApi.RootResourceId
    PathPart: users

MyApiMethod:
  Type: AWS::ApiGateway::Method
  Properties:
    RestApiId: !Ref MyApi
    ResourceId: !Ref MyApiResource
    HttpMethod: GET
    AuthorizationType: CUSTOM # Or AWS_IAM, COGNITO_USER_POOLS
    AuthorizerId: !Ref MyApiAuthorizer
    # If using Cognito User Pool Authorizer directly:
    # AuthorizationType: COGNITO_USER_POOLS
    # AuthCase:
    #   - 'CognitoUserPoolAuthorizer':
    #       UserPoolArn: arn:aws:cognito-idp:us-east-1:123456789012:userpool/us-east-1_xxxxxxxxx
    #       ClientIds:
    #         - 'xxxxxxxxxxxxxxxxxxxxxx'
    Integration:
      Type: AWS_PROXY
      IntegrationHttpMethod: POST
      Uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyBackendLambdaFunction.Arn}/invocations"
      Credentials: !Ref MyApiLambdaExecutionRole

In this example, if we were to use a custom Lambda authorizer, that Lambda function would receive the ID token from the Authorization header. It would then perform the JWT validation steps mentioned earlier. The crucial part is that this Lambda authorizer needs to fetch the public keys from the IdP’s JWKS (JSON Web Key Set) endpoint to verify the token’s signature.

The IdP exposes a JWKS endpoint (e.g., https://your-idp.com/.well-known/jwks.json). This endpoint provides the public keys used to sign the ID tokens. Your API Gateway (or its authorizer function) must fetch these keys and use them to verify the signature of the incoming JWT.

A common misconception is that API Gateway itself performs all the JWT validation checks directly when using a TOKEN type authorizer. While API Gateway can do some basic checks (like checking if the token is present and matches a pattern), the heavy lifting of signature verification, issuer, audience, and expiry checks is typically delegated to a custom Lambda authorizer or handled by integrated services like AWS Cognito User Pools.

If you use AWS Cognito User Pool authorizers, API Gateway handles much of this complexity for you. You configure the User Pool, and API Gateway automatically fetches the necessary keys from Cognito’s JWKS endpoint and performs the validation. This is a more managed approach compared to a custom Lambda authorizer.

The real power here is that your backend services don’t need to know how the user was authenticated; they only need to trust that the API Gateway has already validated the request based on a trusted IdP. The API Gateway can then pass down user identity information (like sub for subject, email, name, or custom claims) to the backend, often by transforming the JWT claims into HTTP headers or by embedding them within the event payload sent to a Lambda function.

The next step after successfully integrating OIDC with API Gateway is often managing the lifecycle of these tokens, especially for refresh tokens and ensuring that expired ID tokens are correctly rejected.

Want structured learning?

Take the full Apigateway course →