Microsoft Authentication Library (MSAL) for Go

repository·main·Indexed 43 days ago

https://github.com/azuread/microsoft-authentication-library-for-go

A library for signing in users or applications with Microsoft identities (Azure AD and Microsoft Accounts) and obtaining tokens to call Microsoft APIs. It provides support for public, confidential, and managed identity clients, including token acquisition via AcquireTokenSilent, AcquireTokenInteractive, and AcquireTokenByCredential.

Tokens
10.5K
Snippets
30
Records
46
Agent score
55%

What's inside MSAL Go

  1. Requirements for structs using the JSON package

    main

    The internal JSON package is designed to handle JSON messages where fields not explicitly defined in a Go struct must be preserved during unmarshalling and re-marshalled during encoding.

    To use this package correctly, your structs must adhere to these rules:

    1. Preservation of unknown fields: If a struct does not implement the json.Marshaler or json.Unmarshaler interfaces, it must define an AdditionalFields field of type map[string]interface{}. Failure to include this field will result in errors.
    2. Root level objects: The package only supports root-level objects that are either a struct or a *struct.
    3. Unsupported types: To prevent data loss, the package does not support *map, *slice, or additional pointers on structs after the initial *struct level.
    4. Interface limitations: While []interface{} or map[string]interface{} can be marshalled/unmarshalled, they will not support the AdditionalFields preservation logic.
  2. State machine implementation pattern

    main

    The package implements state machines using the stateFn pattern. A state machine is a struct with methods that satisfy the stateFn type.

    Type Definition:

    type stateFn func() (stateFn, error)

    Execution Flow:

    1. run() is called to execute the machine.
    2. start() is the first stateFn called.
    3. Each stateFn returns the next stateFn or an error:
      • If a non-nil stateFn is returned, it is called next.
      • If a non-nil error is returned, run() returns that error.
      • If both are nil, run() returns nil (success).
  3. Understand the MSAL Go Public API structure

    main

    The MSAL Go public API is organized into three primary packages located under the apps/ directory. Depending on your application type, you will interact with one of the following:

    • public/: Used for Public Application Clients (service clients).
    • confidential/: Used for Confidential Application Clients (services).
    • cache/: Provides the interfaces required to implement persistent cache storage for any MSAL client.
  4. How the JSON package handles Marshalling and Unmarshalling

    main

    The package uses a state-machine-based design to process JSON via json.Encoder and json.Decoder for efficient streaming.

    Marshalling Logic

    When encoding a struct:

    1. If the struct has a custom marshaller, it is called first.
    2. If the struct has an AdditionalFields field (type map[string]interface{}), it is used to capture extra keys. If this field is missing, an error is returned.
    3. The package maps JSON names to Go field names using struct tags.
    4. For each public field:
      • If the value is a struct, the state machine is called recursively.
      • Otherwise, the standard json.Encoder is used to write the value.

    Unmarshalling Logic

    When decoding JSON into a struct:

    1. If the struct has a custom unmarshaller, it is called first.
    2. The package maps JSON keys to Go fields using struct tags.
    3. For each key found in the JSON:
      • If the key matches a struct field:
        • If the value is a basic type, it is extracted using the Decoder.
        • If the value is a struct, the state machine is called recursively.
      • If the key does not exist in the struct: It is added to the AdditionalFields map (if present) using the Decoder.
  5. Understand the MSAL error design philosophy

    main

    MSAL errors are designed for app developers to troubleshoot, not for displaying to end-users. The error design follows these goals:

    1. Diagnostics: Provide enough information (via CallErr and Verbose()) to track down bugs or client misconfigurations.
    2. Transience Detection: Identify errors that are temporary and can be retried (e.g., rate limiting).
    3. Actionable Feedback: Allow the application to respond to specific error states, such as prompting a user for enrollment.
  6. How Federated Managed Identity (FMI) works in MSAL Go

    main

    Federated Managed Identity (FMI) is a design for supporting high-cardinality identities (millions of entities) that do not reside in a traditional directory. It allows for directory-less identities in scenarios like Kubernetes Pods, Service Fabric, and Azure Arc.

    FMI works by extending the standard MSAL Go authentication flow to include:

    1. FMI Paths: Representing the identity hierarchy.
    2. External Attributes: Supporting federated claims from external authorities.
    3. FMI Credential/Token Flows: Implementing specific flows (Flow 1 through Flow 5) for acquiring credentials and tokens for leaf entities or sub-RMAs (Resource Management Authorities).

    The architecture integrates these into the Public API layer via confidential.New() using options like WithFMIPath() and WithAttributes().

  7. Understand FMI cache isolation behavior

    main

    MSAL provides automatic cache isolation for Federated Managed Identity. This ensures that:

    • Tokens acquired with different FMI paths are cached separately.
    • FMI tokens do not interfere with regular (non-FMI) tokens.
    • Silent token acquisition works seamlessly by automatically finding the correct cached token based on the provided FMI path.

    Example of cache behavior:

    // First call - acquires from identity provider
    result, err := app.AcquireTokenByCredential(ctx, scopes,
        confidential.WithFMIPath("YourFmiPath/CredentialPath"))
    
    // Second call - retrieves from cache
    cachedResult, err := app.AcquireTokenByCredential(ctx, scopes, 
        confidential.WithFMIPath("YourFmiPath/CredentialPath"))
  8. MSAL Go logging and PII policy

    main

    MSAL Go follows strict privacy guidelines regarding logging:

    • No PII (Personally Identifiable Information) is logged. The library does not log any data categorized as PII by Microsoft's trust center definitions.
    • Organizational metadata may be logged, including tenant id, authority, and client id.
    • Operational metadata may be logged, such as request correlation IDs and HTTP status codes.
  9. Acquire tokens with Confidential Clients

    main

    Confidential clients should first attempt to acquire a token silently using AcquireTokenSilent(). If that fails (e.g., due to a cache miss), use AcquireTokenByCredential() to authenticate using the client's stored credential.

    scopes := []string{"scope"}
    result, err := confidentialClient.AcquireTokenSilent(context.TODO(), scopes)
    if err != nil {
        // cache miss, authenticate with another AcquireToken... method
        result, err = confidentialClient.AcquireTokenByCredential(context.TODO(), scopes)
        if err != nil {
            // TODO: handle error
        }
    }
    accessToken := result.AccessToken
  10. Implement Federated Managed Identity (FMI) authentication

    main

    Federated Managed Identity (FMI) allows Azure services to use federated credentials for authentication via an assertion callback. To use FMI, you must use the special client ID urn:microsoft:identity:fmi when initializing your confidential client. The process involves creating a credential using confidential.NewCredFromAssertionCallback, which takes a function that dynamically retrieves the FMI token (typically from a Resource Management API service) whenever a new assertion is required.

    func main() {
        ctx := context.Background()
    
        // Create FMI credential using assertion callback
        fmiCred := confidential.NewCredFromAssertionCallback(
            func(ctx context.Context, aro confidential.AssertionRequestOptions) (string, error) {
                return GetFMICredentialFromRMA(ctx)
            },
        )
    
        // Create confidential client with FMI client ID
        app, err := confidential.New(
            "https://login.microsoftonline.com/tenant-id",
            "urn:microsoft:identity:fmi",  // Special FMI client ID
            fmiCred,
            confidential.WithCache(cacheAccessor),
        )
        if err != nil {
            panic(err)
        }
    
        // Acquire token using FMI
        result, err := app.AcquireTokenByCredential(
            ctx,
            []string{"your-resource/.default"},
            confidential.WithFMIPath("YourFmiPath/CredentialPath"),
        )
        if err != nil {
            panic(err)
        }
    
        // Use the access token
        accessToken := result.AccessToken
    }