/// <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)); }
static void AuthorizeGoogleAccess_Using_JWT() { Console.WriteLine("Loading certificate from " + JWT_CertificatePath); // Load certificate from file and get a crypto service provider for SHA256 signing X509Certificate2 certificate = new X509Certificate2(JWT_CertificatePath, "notasecret", X509KeyStorageFlags.Exportable); using (RSACryptoServiceProvider cp = (RSACryptoServiceProvider)certificate.PrivateKey) { // Create new crypto service provider that supports SHA256 (and don't ask me why the first one doesn't) CspParameters cspParam = new CspParameters { KeyContainerName = cp.CspKeyContainerInfo.KeyContainerName, KeyNumber = cp.CspKeyContainerInfo.KeyNumber == KeyNumber.Exchange ? 1 : 2 }; using (var aes_csp = new RSACryptoServiceProvider(cspParam) { PersistKeyInCsp = false }) { // Parameters for JWT creation AssertionArgs args = new AssertionArgs { Audience = TokenEndpointUrl, Issuer = JWT_Issuer, Scope = Scope }; Console.WriteLine("Authorizing with Google"); OAuth2AccessTokenResponse token = Session.OAuth2_GetAccessTokenFromJWT_RSASHA256(aes_csp, args); } } }
/// <summary> /// Get an access token using the flow "Client Credentials Grant" with RAS-SHA256 signed JWT client credentials. /// </summary> /// <param name="session">Ramone session.</param> /// <param name="cp">RSA Crypto provider used to do the RSA-SHA256 signing.</param> /// <param name="args">Assertion arguments.</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_RSASHA256( this ISession session, RSACryptoServiceProvider cp, AssertionArgs args, bool useAccessToken = true) { return(OAuth2_GetAccessTokenFromJWT(session, Jose.JwsAlgorithm.RS256, cp, args, useAccessToken)); }
/// <summary> /// Get an access token using the flow "Client Credentials Grant" with SHA256 signed JWT client credentials. /// </summary> /// <param name="session">Ramone session.</param> /// <param name="shaKey">Binary representation of the SHA256 key.</param> /// <param name="args">Assertion arguments</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_SHA256( this ISession session, byte[] shaKey, AssertionArgs args, bool useAccessToken = true) { return(OAuth2_GetAccessTokenFromJWT(session, Jose.JwsAlgorithm.HS256, shaKey, args, 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 the flow "Client Credentials Grant" defined in RFC 6749 Section 4.4 with JWT client credentials signed with a generic algorithm. /// </summary> /// <param name="session">Ramone session.</param> /// <param name="signingAlgorithm">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="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, bool useAccessToken = true) { return(OAuth2_GetAccessTokenFromJWT(session, alg, key, args, ClientAuthenticationFlowType.Rfc7523Section21, null, null, null, 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)); }
/// <summary> /// Get an access token using the flow "Client Credentials Grant" with JWT client credentials signed with a generic algorithm. /// </summary> /// <param name="session">Ramone session.</param> /// <param name="signingAlgorithm">An implementation of ISigningAlgorithm to do the actual signing.</param> /// <param name="args">Assertion arguments.</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, bool useAccessToken = true) { 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(); var claims = new Dictionary <string, object>() { { "iss", args.Issuer }, { "scope", args.Scope }, { "aud", args.Audience }, { "sub", args.Subject }, { "exp", expires }, { "iat", issuedAt } }; claims = claims.Where(kv => kv.Value != null).ToDictionary(kv => kv.Key, kv => kv.Value); string token = Jose.JWT.Encode(claims, key, alg); NameValueCollection tokenRequestArgs = new NameValueCollection(); tokenRequestArgs["grant_type"] = "urn:ietf:params:oauth:grant-type:jwt-bearer"; tokenRequestArgs["assertion"] = token; return(GetAndStoreAccessToken(session, tokenRequestArgs, useAccessToken)); }