-
Notifications
You must be signed in to change notification settings - Fork 89
Signed Assertions
In order to prove their identity, confidential client applications exchange a secret with Azure AD. This can be a:
- a client secret (application password),
- a certificate, which is really used to build a signed assertion containing standard claims. This can also be a signed assertion directly.
MSAL Go has 3 methods to provide either credentials or assertions to the confidential client app. The credential can be initialized as a secret, a certificate or a signed assertion using NewCredFromSecret()
, NewCredFromCert()
and NewCredFromAssertion()
NOTE: While it is possible to use the NewCredFromAssertion()
as a credential to acquire tokens for the confidential client, we do not recommend using it by default as it is more advanced and is designed to handle very specific scenarios which are not common. Using NewCredFromCert()
as a credential will allow MSAL to handle this for you. This api offers you the ability to customize your authentication request if needed but the default assertion created by using certificate credentia; will suffice for most authentication scenarios. This api can also be used as a workaround in some scenarios where MSAL fails to perform the signing operation internally.
A signed client assertion takes the form of a signed JWT with the payload containing the required authentication claims mandated by Azure AD, Base64 encoded. To use it:
var signedAssertion string
signedAssertion = ComputeAssertion();
cred, err := confidential.NewCredFromAssertion(signedAssertion)
if err != nil {
// handle error
}
app, err := confidential.New("client_id", cred, confidential.WithAuthority("authority_url"))
if err != nil {
// handle error
}
The claims expected by Azure AD are:
Claim type | Value | Description |
---|---|---|
aud | https://login.microsoftonline.com/{tenantId}/v2.0 | The "aud" (audience) claim identifies the recipients that the JWT is intended for (here Azure AD) See [RFC 7519, Section 4.1.3] |
exp | Thu Jun 27 2019 15:04:17 GMT+0200 (Romance Daylight Time) | The "exp" (expiration time) claim identifies the expiration time on or after which the JWT MUST NOT be accepted for processing. See [RFC 7519, Section 4.1.4] |
iss | {ClientID} | The "iss" (issuer) claim identifies the principal that issued the JWT. The processing of this claim is generally application specific. The "iss" value is a case-sensitive string containing a StringOrURI value. [RFC 7519, Section 4.1.1] |
jti | (a Guid) | The "jti" (JWT ID) claim provides a unique identifier for the JWT. The identifier value MUST be assigned in a manner that ensures that there is a negligible probability that the same value will be accidentally assigned to a different data object; if the application uses multiple issuers, collisions MUST be prevented among values produced by different issuers as well. The "jti" claim can be used to prevent the JWT from being replayed. The "jti" value is a case-sensitive string. [RFC 7519, Section 4.1.7] |
nbf | Thu Jun 27 2019 14:54:17 GMT+0200 (Romance Daylight Time) | The "nbf" (not before) claim identifies the time before which the JWT MUST NOT be accepted for processing. [RFC 7519, Section 4.1.5] |
sub | {ClientID} | The "sub" (subject) claim identifies the subject of the JWT. The claims in a JWT are normally statements about the subject. The subject value MUST either be scoped to be locally unique in the context of the issuer or be globally unique. The See [RFC 7519, Section 4.1.2] |
kid | {Certificate Thumbprint} | The X.509 certificate hash's (also known as the cert's SHA-1 thumbprint) Hex representation encoded as a Base64url string value. For example, given an X.509 certificate hash of 84E05C1D98BCE3A5421D225B140B36E86A3D5534 (Hex), the kid claim would be hOBcHZi846VCHSJbFAs26Go9VTQ= (Base64url). |
x5c | {Certificate Public Key Value} (Optional - only when wanting to use SNI described below) | The "x5c" (X.509 certificate chain) Header Parameter contains the X.509 public key certificate or certificate chain [RFC5280] corresponding to the key used to digitally sign the JWS. The certificate or certificate chain is represented as a JSON array of certificate value strings. Each string in the array is a base64-encoded (not base64url-encoded) DER [ITU.X690.2008] PKIX certificate value. |
Here is am example code snippet of how ComputeAssertion()
mentioned above with all the claims in the table would look like:
issuer
is the clientId of your application.
aud
is the audience - ${authority}/oauth2/v2.0
for AAD.
cert
is your certfile and key
is the private key for signing the assertion.
func ComputeAssertion(issuer string, aud string, cert *x509.Certificate, key crypto.PrivateKey) string {
expires := time.Now().Add(10 * time.Minute)
token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims{
"aud": aud, // Format - ${authority}/oauth2/v2.0/token
"exp": strconv.FormatInt(expires.Unix(), 10), // 10 minutes
"iss": issuer,
"jti": uuid.New().String(),
"nbf": strconv.FormatInt(time.Now().Unix(), 10),
"sub": issuer,
})
//alg represents the desired signing algorithm, which is SHA-256 in this case
//kid represents the certificate thumbprint
certHash:= sha1.Sum(cert.Raw)
token.Header = map[string]interface{}{
"alg": "RS256",
"typ": "JWT",
"kid": base64.StdEncoding.EncodeToString(certHarsh[:]),
}
assertion, err := token.SignedString(key)
if err != nil {
// handle error
}
return assertion
In order to reduce the amount of overhead needed to perform this authentication, it is recommended to cache the assertion for the duration of the expiration time.
Sending the x5c enables application developers to achieve easy certificate roll-over in Azure AD via subject name issuer authentication. Sending the public certificate to Azure AD along with the token request, will enable Azure AD to use it to validate the subject name based on a trusted issuer policy. This saves the application admin from the need to explicitly manage the certificate rollover (either via portal or PowerShell/CLI operation).
When using certificate credentials, MSAL handles this automatically when you use .WithX5c(true). However, when you use client assertions, you provide the signed payload, so you need to provide the right claims for sub name issuer certificate rotation to happen.