/// <summary> /// Get an access token using either the flow "JWTs as Authorization Grants" defined in RFC 7523 Section 2.1 or /// "Client Acting on Behalf of Itself" defined in RFC 7521 Section 6.2 with JWT client credentials signed with /// an algorithm appropiate for the supplied certificate. /// </summary> /// <param name="session">Ramone session.</param> /// <param name="key">The key used by the signing algorithm.</param> /// <param name="args">Assertion arguments.</param> /// <param name="flowType">Specify which client authentication flow to use.</param> /// <param name="extraHeaders">Optionally specify extra headers in the assertion.</param> /// <param name="extraClaims">Optionally specify extra claims in the assertion.</param> /// <param name="extraRequestArgs">Optionally specify extra arguments in the POST data of the HTTP request.</param> /// <param name="useAccessToken">Store the returned access token in session and use that in future requests to the resource server.</param> /// <returns></returns> /// <remarks>For RSA the number of signature bits are independent of the keysize, this function will always use RS256 for RSA.</remarks> public static OAuth2AccessTokenResponse OAuth2_GetAccessTokenFromJWT_ByCertificate( this ISession session, X509Certificate2 cert, AssertionArgs args, ClientAuthenticationFlowType flowType, IDictionary <string, object> extraHeaders = null, IDictionary <string, object> extraClaims = null, IDictionary <string, string> extraRequestArgs = null, bool useAccessToken = true) { if (cert == null) { throw new ArgumentNullException(nameof(cert)); } if (!cert.HasPrivateKey) { throw new InvalidOperationException("The certificate does not contain a private key"); } AsymmetricAlgorithm key = cert.GetRSAPrivateKey(); if (key == null) { key = GetECDsaPublicKey.Value?.Invoke(cert); } if (key == null) { throw new NotSupportedException(string.Format("Unsupported private key: {0}", cert.PrivateKey)); } return(OAuth2_GetAccessTokenFromJWT_ByKey(session, key, args, flowType, extraHeaders, extraClaims, extraRequestArgs, useAccessToken)); }
/// <summary> /// Get an access token using either the flow "JWTs as Authorization Grants" defined in RFC 7523 Section 2.1 or /// "Client Acting on Behalf of Itself" defined in RFC 7521 Section 6.2 with JWT client credentials signed with /// an algorithm appropiate for the supplied key. /// </summary> /// <param name="session">Ramone session.</param> /// <param name="key">The key used by the signing algorithm.</param> /// <param name="args">Assertion arguments.</param> /// <param name="flowType">Specify which client authentication flow to use.</param> /// <param name="extraHeaders">Optionally specify extra headers in the assertion.</param> /// <param name="extraClaims">Optionally specify extra claims in the assertion.</param> /// <param name="extraRequestArgs">Optionally specify extra arguments in the POST data of the HTTP request.</param> /// <param name="useAccessToken">Store the returned access token in session and use that in future requests to the resource server.</param> /// <returns></returns> /// <remarks>For RSA the number of signature bits are independent of the keysize, this function will always use RS256 for RSA.</remarks> public static OAuth2AccessTokenResponse OAuth2_GetAccessTokenFromJWT_ByKey( this ISession session, object key, AssertionArgs args, ClientAuthenticationFlowType flowType, IDictionary <string, object> extraHeaders = null, IDictionary <string, object> extraClaims = null, IDictionary <string, string> extraRequestArgs = null, bool useAccessToken = true) { object testKey = key ?? throw new ArgumentNullException(nameof(key)); if (testKey is CngKey cngKey) { if (cngKey.AlgorithmGroup.Equals(CngAlgorithmGroup.ECDiffieHellman) || cngKey.AlgorithmGroup.Equals(CngAlgorithmGroup.ECDsa)) { testKey = new ECDsaCng(cngKey); } else if (cngKey.AlgorithmGroup.Equals(CngAlgorithmGroup.Rsa)) { testKey = new RSACng(cngKey); } else { throw new NotSupportedException(string.Format("Unsupported algorithm: {0}", cngKey.Algorithm)); } } Jose.JwsAlgorithm alg; if (testKey is RSA) { alg = Jose.JwsAlgorithm.RS256; } else if (testKey is ECDsa ecdsaKey) { switch (ecdsaKey.KeySize) { case 256: alg = Jose.JwsAlgorithm.ES256; break; case 384: alg = Jose.JwsAlgorithm.ES384; break; case 521: // NB: ES512 uses the P-521/secp521r1 curve which has a key size of 521 bits, this is not a typo... alg = Jose.JwsAlgorithm.ES512; break; default: throw new NotSupportedException(string.Format("Unsupported ECDSA key size: {0}", ecdsaKey.KeySize)); } } else { throw new NotSupportedException(string.Format("Unsupported private key type: {0}", key.GetType())); } return(OAuth2_GetAccessTokenFromJWT(session, alg, key, args, flowType, extraHeaders, extraClaims, extraRequestArgs, useAccessToken)); }
/// <summary> /// Get an access token using either the flow "JWTs as Authorization Grants" defined in RFC 7523 Section 2.1 or /// "Client Acting on Behalf of Itself" defined in RFC 7521 Section 6.2 with JWT client credentials signed with a generic algorithm. /// </summary> /// <param name="session">Ramone session.</param> /// <param name="alg">An implementation of ISigningAlgorithm to do the actual signing.<</param> /// <param name="key">The key used by the signing algorithm.</param> /// <param name="args">Assertion arguments.</param> /// <param name="flowType">Specify which client authentication flow to use.</param> /// <param name="extraHeaders">Optionally specify extra headers in the assertion.</param> /// <param name="extraClaims">Optionally specify extra claims in the assertion.</param> /// <param name="extraRequestArgs">Optionally specify extra arguments in the POST data of the HTTP request.</param> /// <param name="useAccessToken">Store the returned access token in session and use that in future requests to the resource server.</param> /// <returns></returns> public static OAuth2AccessTokenResponse OAuth2_GetAccessTokenFromJWT( this ISession session, Jose.JwsAlgorithm alg, object key, AssertionArgs args, ClientAuthenticationFlowType flowType, IDictionary <string, object> extraHeaders = null, IDictionary <string, object> extraClaims = null, IDictionary <string, string> extraRequestArgs = null, bool useAccessToken = true) { if (args == null) { throw new ArgumentNullException(nameof(args)); } if (flowType != ClientAuthenticationFlowType.Rfc7523Section21 && flowType != ClientAuthenticationFlowType.Rfc7521Section62) { throw new ArgumentOutOfRangeException(nameof(flowType)); } OAuth2Settings settings = GetSettings(session); DateTime now = DateTime.UtcNow; DateTime issuedAtDate = now.Add(args.IssueTimeOffset); DateTime expiresDate = issuedAtDate.Add(args.ExpireTime); long issuedAt = issuedAtDate.ToUnixTime(); long expires = expiresDate.ToUnixTime(); IEnumerable <KeyValuePair <string, object> > baseClaims = new Dictionary <string, object>() { { "iss", args.Issuer }, { "scope", args.Scope }, { "aud", args.Audience }, { "sub", args.Subject }, { "exp", expires }, { "iat", issuedAt } }; if (extraClaims != null) { baseClaims = baseClaims.Concat(extraClaims); } var claims = baseClaims.Where(kv => kv.Value != null).ToDictionary(kv => kv.Key, kv => kv.Value); string token = Jose.JWT.Encode(claims, key, alg, extraHeaders: extraHeaders); NameValueCollection tokenRequestArgs = new NameValueCollection(); if (flowType == ClientAuthenticationFlowType.Rfc7523Section21) { tokenRequestArgs["grant_type"] = "urn:ietf:params:oauth:grant-type:jwt-bearer"; tokenRequestArgs["assertion"] = token; } else { tokenRequestArgs["scope"] = args.Scope; // This is nominally optional and redundant, but Microsoft wants it... tokenRequestArgs["grant_type"] = "client_credentials"; tokenRequestArgs["client_assertion_type"] = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"; tokenRequestArgs["client_assertion"] = token; } if (extraRequestArgs != null) { foreach (var kv in extraRequestArgs) { tokenRequestArgs[kv.Key] = kv.Value; } } return(GetAndStoreAccessToken(session, tokenRequestArgs, useAccessToken)); }