Add application security to the swiyu generic management verifier APIs using OAuth

The article looks at implementing security using OAuth for the swiyu Public Beta Trust Infrastructure generic containers. The container provides endpoint for OpenID verification and the management APIs. The OpenID endpoints are publicly accessible using a reverse proxy, the management APIs can only be accessed in the network and using an access token for app security. The OAuth client credentials flow is used to acquire the access token.

Code: https://github.com/swiss-ssi-group/swiyu-passkeys-idp-loi-loa

Blogs in this series:

Setup

The generic container from the swiyu Public Beta Trust Infrastructure exposes APIs which are accessed from both the solution identity provider and also the YARP reverse proxy. The management APIs are only exposed in the network and the APIs require application security. An access token is required to use the APIs. Network boundaries are not enough. Application must be implemented as well. The management APIs MUST ensure that only access tokens intended for the APIs can be used.

Setup of the swiyu container

At present, the containers provide OAuth or direct access tokens as a way of implementing application security for the generic container. Only RSA is supported at present. Not all the required validation of the access token is forced, only the signature of the token is validated. See the documentation here:

https://github.com/swiyu-admin-ch/swiyu-verifier?tab=readme-ov-file#security

In this setup, Aspire is used to create the container and set the security definitions.

swiyuVerifier = builder.AddContainer("swiyu-verifier", "ghcr.io/swiyu-admin-ch/swiyu-verifier", "latest")
    //.WaitFor(identityProvider)
    .WithEnvironment("EXTERNAL_URL", verifierExternalUrl)
    .WithEnvironment("OPENID_CLIENT_METADATA_FILE", verifierOpenIdClientMetaDataFile)
    .WithEnvironment("VERIFIER_DID", verifierDid)
    .WithEnvironment("DID_VERIFICATION_METHOD", didVerifierMethod)
    .WithEnvironment("SIGNING_KEY", verifierSigningKey)
    .WithEnvironment("POSTGRES_USER", postGresUser)
    .WithEnvironment("POSTGRES_PASSWORD", postGresPassword)
    .WithEnvironment("POSTGRES_DB", postGresDbVerifier)
    .WithEnvironment("POSTGRES_JDBC", postGresJdbcVerifier)
    .WithEnvironment("SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUERURI", verifierJwtIssuer)
    .WithHttpEndpoint(port: VERIFIER_PORT, targetPort: 8080, name: HTTP);

The SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUERURI configuration property is set with the Issuer URL were the well known endpoints is defined. The container uses JAVA Springboot and default OAuth to find the public key for the token validation. Only RSA is supported. It only validates the signature and so any access token from the OAuth server will work. This is not good.

Using the OAuth client credentials flow

The access token is required for the application to use the API and no user is involved. This is an application flow and not a delegated flow. The OAuth client credentials flow is used to acquire the access token. This is just a simple clientId and client secret using an scope. This can be improved with client assertions. Any OAuth server can be used. I used Microsoft.Identity.Client in one example with an Entra ID specification and default OAuth client credentials in a second example. I would prefer to use OAuth DPoP, but this is not supported in the generic containers.

A standard OAuth servers can be implemented using the following code:

Example OAuth (Client credentials)

public static async Task<TokenResponse> RequestTokenOAuthAsync(IConfiguration configuration)
{
    var client = new HttpClient();

    var disco = await client.GetDiscoveryDocumentAsync(configuration["OAuthIssuerUrl"]);

    if (disco.IsError) throw new Exception(disco.Error);

    var response = await client.RequestClientCredentialsTokenAsync(
      new ClientCredentialsTokenRequest
     {
         Address = disco.TokenEndpoint,
         ClientId = "swiyu-client",
         // Client assertions are better
         ClientSecret = "--from secrets vault--",
         Scope = "swiyu",
     });

    if (response.IsError) throw new Exception(response.Error);

    return response;
}

This code be improved using OAuth DPoP.

Example using MSAL (Microsoft.Identity.Client)

 public static async Task<string> RequestTokenAsync(IConfiguration configuration)
 {
     // 1. Client client credentials client
     var app = ConfidentialClientApplicationBuilder
         .Create(configuration["SwiyuManagementClientId"])
         .WithClientSecret(configuration["SwiyuManagementClientSecret"])
         .WithAuthority(configuration["SwiyuManagementAuthority"])
         .Build();

     var scopes = new[] { configuration["SwiyuManagementScope"] };

     // 2. Get access token
     var authResult = await app.AcquireTokenForClient(scopes)
         .ExecuteAsync();

     return authResult.AccessToken;

 }

Note:

The management API of the container only validates the signature. This is not really good enough as any token issued from the same IDP will be accepted.

Further improvements

  • Using client assertions to acquire the access token
  • Support OAuth DPoP access tokens
  • Support more than just RSA
  • Use delegated access tokens
  • Add authorization, at present any access token from the identity provider will work.

Links

https://github.com/swiyu-admin-ch/swiyu-verifier/issues/223

https://github.com/swiyu-admin-ch/swiyu-verifier/issues/170

https://learn.microsoft.com/en-us/aspnet/core/fundamentals/servers/yarp/getting-started

https://github.com/dotnet/aspnetcore/issues/64881

https://openid.net/specs/openid-connect-eap-acr-values-1_0-final.html

https://datatracker.ietf.org/doc/html/rfc8176

https://learn.microsoft.com/en-us/aspnet/core/security/authentication/claims

SSI

https://www.eid.admin.ch/en/public-beta-e

https://learn.microsoft.com/en-us/dotnet/aspire/get-started/aspire-overview

https://www.npmjs.com/package/ngrok

https://swiyu-admin-ch.github.io/specifications/interoperability-profile/

https://andrewlock.net/converting-a-docker-compose-file-to-aspire/

https://swiyu-admin-ch.github.io/cookbooks/onboarding-generic-verifier/

https://github.com/orgs/swiyu-admin-ch/projects/2/views/2

SSI Standards

https://identity.foundation/trustdidweb/

https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html

https://openid.net/specs/openid-4-verifiable-presentations-1_0.html

https://datatracker.ietf.org/doc/draft-ietf-oauth-selective-disclosure-jwt/

https://datatracker.ietf.org/doc/draft-ietf-oauth-sd-jwt-vc/

https://datatracker.ietf.org/doc/draft-ietf-oauth-status-list/

https://www.w3.org/TR/vc-data-model-2.0/

One comment

  1. Unknown's avatar

    […] Add Application security to the swiyu generic management verifier APIs using OAuth […]

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.