Skip to content

Latest commit

 

History

History
225 lines (182 loc) · 8.12 KB

AuthorizationService.md

File metadata and controls

225 lines (182 loc) · 8.12 KB

AuthorizationService Developer Guide

Open Platform Communications Unified Architecture (OPC UA) Part 12 §9 defines the AuthorizationService, which a Global Discovery Server (GDS) provides to issue access tokens for OPC UA applications. Part 12 v1.05 uses the two-phase StartRequestToken / FinishRequestToken flow. RequestAccessToken remains available for legacy compatibility.

For client-side JSON Web Token (JWT) use during ActivateSession, see Identity Providers.

Contents

Architecture

flowchart LR
    client["OPC UA Client"]
    subgraph gds["GDS Server"]
        manager["ApplicationsNodeManager"]
        provider["IAccessTokenProvider"]
        issuer["ITokenIssuer<br/>JWT / HSM / cloud signer"]
        manager --> provider --> issuer
    end
    client -->|GetServiceDescription| manager
    client -->|"RequestAccessToken (legacy, Obsolete)"| manager
    client -->|StartRequestToken / FinishRequestToken| manager
    manager -->|access token| client
Loading

Opc.Ua.Gds.Server.IAccessTokenProvider routes requests for the AuthorizationService method handlers. Before returning a token, the provider validates resource IDs, policies, pending request IDs, and user-token inputs.

Modern Part 12 v1.05 clients call StartRequestToken followed by FinishRequestToken. Legacy clients can still call RequestAccessToken; the provider dispatches that call to IAccessTokenProvider.RequestAccessTokenAsync. The API marks this method [Obsolete].

Opc.Ua.Identity.ITokenIssuer signs a token after the provider accepts a request. The default issuer uses local certificate material. Custom issuers can delegate signing to a cloud key-management service, a hardware security module, or a corporate authorization service without changing the OPC UA method wiring.

Modern Part 12 v1.05 flow

using Opc.Ua.Gds.Client;

var authClient = new AuthorizationServiceClient(session, serviceNodeId);
var (serviceUri, serviceCertificate, tokenPolicies) =
    await authClient.GetServiceDescriptionAsync();

var (serviceData, requestId) = await authClient.StartRequestTokenAsync(
    resourceId: "urn:target-server",
    policyId: "jwt",
    requestorData: ByteString.From(Encoding.UTF8.GetBytes("read write")));

var (jwt, expiresAt, refreshToken, refreshExpiresAt) =
    await authClient.FinishRequestTokenAsync(
        requestId,
        Array.Empty<string>().ToArrayOf(),
        new AnonymousIdentityToken(),
        new SignatureData());

StartRequestToken validates the requested audience and scopes. It allocates a continuation request ID and stores the pending request in memory. FinishRequestToken exchanges that ID for a compact JWT (tokenType = "JWT") signed by the configured Opc.Ua.Identity.ITokenIssuer.

Refreshing an access token

if (refreshToken != null && DateTime.UtcNow >= expiresAt.AddMinutes(-5))
{
    var refreshed = await authClient.RefreshTokenAsync(resourceId, refreshToken);
    jwt = refreshed.accessToken;
    expiresAt = refreshed.accessTokenExpiryTime;
    refreshToken = refreshed.newRefreshToken; // store the rotated token
}

Server hosting

Use AddAuthorizationService to register the default in-box issuer and in-memory request store:

services.AddOpcUa()
    .AddGdsServer(o =>
    {
        o.ApplicationName = "GlobalDiscoveryServer";
        o.ApplicationUri = "urn:example:gds";
        o.EndpointUrls.Add("opc.tcp://localhost:58810/GDS");
    })
    .AddAuthorizationService(o =>
    {
        o.IssuerUri = "urn:example:gds";
        o.SigningCertificate = new CertificateIdentifier
        {
            StoreType = CertificateStoreType.Directory,
            StorePath = "%LocalApplicationData%/OPC Foundation/GDS/pki/own",
            SubjectName = "CN=GlobalDiscoveryServer"
        };
        o.AllowedAudiences.Add("urn:target-server");
        o.DefaultScopes.Add("read");
    });

When SigningCertificate is omitted in the hosted GDS, the default issuer falls back to the GDS application instance certificate. Custom deployments can replace the signer:

builder.AddAuthorizationService<MyTokenIssuer>(o =>
{
    o.IssuerUri = "https://issuer.example";
});

MyTokenIssuer implements Opc.Ua.Identity.ITokenIssuer. The default CertificateJwtIssuer signs JSON Web Signature (JWS) tokens defined by RFC 7515. It uses the Elliptic Curve Digital Signature Algorithm (ECDSA) (ES256, ES384, or ES512) or RSA (RS256 by default). Custom issuers can also use RSA-PSS. The default issuer does not add a JWT package dependency.

Custom ITokenIssuer for cloud or HSM signing

Use AddAuthorizationService<TIssuer>() when the signing key is not resident in the process. External signing options include:

  • Azure Key Vault
  • AWS KMS
  • An HSM
  • A corporate token service

The issuer builds the JWS header and payload, then delegates the signing operation to the external service.

public sealed class CloudKmsTokenIssuer : ITokenIssuer
{
    public string IssuerUri => "https://issuer.example/gds";
    public string ProfileUri => Profiles.JwtUserToken;

    public async ValueTask<AccessToken> IssueAsync(
        TokenIssuanceRequest request,
        CancellationToken ct = default)
    {
        byte[] signingInput = BuildJwtSigningInput(request);
        byte[] signature = await SignWithKeyVaultOrKmsAsync(signingInput, ct)
            .ConfigureAwait(false);
        byte[] compactJws = CombineCompactJws(signingInput, signature);

        return new AccessToken(
            Profiles.JwtUserToken,
            compactJws,
            DateTime.UtcNow + request.RequestedLifetime,
            request.Subject);
    }
}

services.AddOpcUa()
    .AddGdsServer(o => o.ApplicationUri = "urn:example:gds")
    .AddAuthorizationService<CloudKmsTokenIssuer>(o =>
    {
        o.SigningCertificate = new CertificateIdentifier
        {
            StoreType = CertificateStoreType.Directory,
            StorePath = "%LocalApplicationData%/OPC Foundation/GDS/pki/own",
            SubjectName = "CN=GlobalDiscoveryServer"
        };
        o.IssuerUri = "https://issuer.example/gds";
        o.AllowedAudiences.Add("urn:target-server");
    });

The SigningCertificate option still advertises the issuer identity and key material that local verifiers expect. The custom issuer controls how it produces the signature.

Server-side abstraction

Opc.Ua.Gds.Server.IAccessTokenProvider backs the GDS method handlers:

ValueTask<(ByteString serviceData, Guid requestId)> StartRequestTokenAsync(...);
ValueTask<AccessTokenResult> FinishRequestTokenAsync(...);

[Obsolete("Use StartRequestTokenAsync + FinishRequestTokenAsync for Part 12 v1.05 compliance.")]
ValueTask<string> RequestAccessTokenAsync(...);

The default AuthorizationServiceManager delegates to InMemoryAccessTokenProvider, which keeps pending request ids in memory and calls the configured ITokenIssuer for final JWT issuance.

Client identity-provider bridge

GdsAccessTokenProvider adapts AuthorizationServiceClient to the client-side Opc.Ua.Identity.IAccessTokenProvider used by IssuedTokenIdentityProvider and Session.UpdateIdentityAsync(IClientIdentityProvider).

Legacy compatibility

RequestAccessToken and AuthorizationServiceClient.RequestAccessTokenAsync are obsolete because Part 12 v1.05 replaced the one-shot exchange with StartRequestToken and FinishRequestToken. When a provider is configured, the service still dispatches the wire method to IAccessTokenProvider.RequestAccessTokenAsync so v1.04 clients continue to work.

Audit events

Successful and failed token operations raise AccessTokenIssuedAuditEventType. Tokens and private credentials are not included in audit payloads.