Skip to content

Latest commit

 

History

History

OpenIdDict.Server

Transparent Auth Gateway providing an OIDC authentication from a linked Azure AD tenant and implementing OAuth 2 authorisation for client apps

1. Purpose

A custom build Identity Server that implements OAuth 2 Authorization Code Flow with PKCE to serve other client apps as a trusted authority and perform authentication from a linked Identity Provider (a specified tenant of Azure AD).

As a bonus for the API integration with third-parties, it also implements the Client Credentials Flow.

2. Run the sample app

Prerequisite: an Azure AD tenant supporting authentication.

  1. To support Azure Entra ID authentication, configure the appsettings.json (by setting parameters directly in the file or via the user secrets):
    • AzureAd:Tenant – the name of your tenant (e.g. contoso.onmicrosoft.com) or its tenant ID (a GUID). Sometime it's referred as the issuer
      The parameter is used in forming a set of HTTP endpoints for the Identity Provider (Azure AD in our case). E.g. https://login.microsoftonline.com/{TENANT}/oauth2/v2.0/authorize.
    • AzureAd:ClientId – the Application (client) ID (a GUID) of the App Registration.
    • AzureAd:Scope – The requested scope (also called delegated permission), for the client apps to obtain an access token. Note that all APIs must publish a minimum of one scope and this app is using just one for simplicity.
  2. Launch the Server.
  3. Try the two available end-points. /anonymous must return a HTTP 200 code, while /protected gives a 401.
  4. Pass the Swagger authentication. Either of the 2 provided options:
    1. The Authorization Code Flow (for users) to authenticate via the linked Azure Entra ID.
      It will pop up a new tab with a bunch of redirects that brings the user to the Azure Entra ID login page and will close it on successful authentication.
    2. The Client Credentials Flow (for API integration) to authenticate via the client_id and client_secret only.
  5. Try /protected end-point and it must return a HTTP 200 code.

3. Implementation

The API has just two end-points:

  • /anonymous that always returns HTTP code 200 on any request.
  • /protected that requires user to authenticate and provide a self-issued Bearer token. Otherwise, it returns HTTP code 401 Unauthorized.

Handling the OAuth 2 is implemented with using OpenIdDict NuGet package with the key implementation is in HandleAuthorizationRequestContext handler of its 'degraded mode' (see this article from the author of the library on detailed implementation).

The authentication client is implemented by NSwag.

From the consumer's perspective, the Authorization Code Flow looks like:

  1. The user gets redirected to /authorize route.
    E.g. /connect/authorize?response_type=code&client_id=TestApp&redirect_uri=https%3A%2F%2Flocalhost%3A5003%2Fswagger%2Foauth2-redirect.html&scope=openid&state={STATE}&realm=realm&code_challenge={CODE_CHALLENGE}&code_challenge_method=S256
  2. If a relevant user identity cookie not found,
    1. the user gets redirected further to the login page of the linked Identity Provider (for Azure AD it's https://login.microsoftonline.com/{TENANT}/oauth2/v2.0/authorize).
    2. on successful authentication withing the tenant, the user gets redirected back to the Auth Gateway to continue the authentication/authorisation process.
  3. On successful authentication/authorisation, the user gets redirected back to Swagger
    /swagger/oauth2-redirect.html?code={CODE}&session_state={RANDOM_STATE},
    where CODE is a reference token to the auth code stored in memory cache on the server.
  4. Then NSwag's JavaScript exchanges the received code to an access token by running a POST request to /token.

The Client Credentials Flow is simpler and involves only a single call to the /token route. E.g.:

curl --location '/connect/token' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data 'grant_type=client_credentials' \
  --data 'scope=profile' \
  --data 'client_id=TestApp' \
  --data 'client_secret=test'