UNPKG

@quarks/quarks-iam

Version:

A modern authorization server built to authenticate your users and protect your APIs

1,105 lines (950 loc) 34.8 kB
/* global process, Buffer */ /** * Module dependencies */ var client = require('../boot/redis').getClient() var Modinha = require('modinha') var Document = require('modinha-redis') var User = require('./User') var AuthorizationError = require('../errors/AuthorizationError') var base64url = require('base64url') var url = require('url') /** * Client model */ var Client = Modinha.define('clients', { /** * Client Metadata * * http://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata * * Clients have metadata associated with their unique Client Identifier at the * Authorization Server. These can range from human-facing display strings, such * as a Client name, to items that impact the security of the protocol, such as * the list of valid redirect URIs. * * The Client Metadata values are used in two ways: * * * as input values to registration requests, and * * as output values in registration responses and read responses. * * These Client Metadata values are used by OpenID Connect: * * redirect_uris * REQUIRED. Array of Redirection URI values used by the Client. One of these * registered Redirection URI values MUST exactly match the redirect_uri * parameter value used in each Authorization Request, with the matching * performed as described in Section 6.2.1 of [RFC3986] (Simple String * Comparison). */ redirect_uris: { type: 'array', required: true, format: 'url', messages: { message: 'invalid_redirect_uri', format: 'Must contain valid URIs', conform: 'Must follow guidelines in OpenID Connect Registration 1.0 ' + 'specification for client metadata' }, trim: true, conform: function (value, instance) { var valid = true var inDevelopment = process.env.NODE_ENV === 'development' || !process.env.NODE_ENV // Proceed with validation if there are redirect URIs defined if (Array.isArray(value)) { // Native clients if ( instance.application_type === 'native' ) { // Check each redirect URI value.forEach(function (uri) { try { var parsedURI = url.parse(uri) // Native clients must register with http://localhost[:PORT] // Here, we check if they are not if ( parsedURI.hostname !== 'localhost' || parsedURI.protocol !== 'http:' ) { // If they don't, then they must use a custom scheme // Here, we check if they are not if ( parsedURI.protocol === 'https:' || parsedURI.protocol === 'http:' ) { // Fail validation if the client has redirect URIs that are // neither http://localhost[:PORT] or using a custom scheme valid = false } } } catch (err) { // Fail validation in the unlikely event that URI cannot be parsed valid = false } }) // Web clients with implicit grant type (not enforced in development) } else if ( !inDevelopment && Array.isArray(instance.grant_types) && instance.grant_types.indexOf('implicit') !== -1 ) { // Check each redirect URI value.forEach(function (uri) { try { var parsedURI = url.parse(uri) // Web clients must register with https and NOT with localhost // Here, we check if they don't obey this rule if ( parsedURI.hostname === 'localhost' || parsedURI.protocol !== 'https:' ) { // Fail validation valid = false } } catch (err) { // Fail validation in the unlikely event that URI cannot be parsed valid = false } }) } } return valid } }, /** * response_types * OPTIONAL. JSON array containing a list of the OAuth 2.0 response_type * values that the Client is declaring that it will restrict itself to * using. If omitted, the default is that the Client will use only the code * Response Type. */ response_types: { type: 'array', required: true, default: ['code'], messages: { conform: 'Invalid response_type or insufficient grant_types defined ' + 'for desired response_types' }, conform: function (value, instance) { var valid = true // authorization_code grant type is default if grant_types are not defined var hasAuthorizationCodeGrant = true var hasImplicitGrant = false if (Array.isArray(instance.grant_types)) { hasAuthorizationCodeGrant = instance.grant_types.indexOf('authorization_code') !== -1 hasImplicitGrant = instance.grant_types.indexOf('implicit') !== -1 } // Proceed with validation if there are response types defined if (Array.isArray(value)) { // Check each response type value.forEach(function (responseTypeString) { var responseTypeSet = responseTypeString.split(' ') responseTypeSet.forEach(function (responseType) { if ( // invalid response_type value ['code', 'id_token', 'token', 'none'] .indexOf(responseType) === -1 || // none response_type with other response_type values in the same // set, e.g. "none code" (responseType === 'none' && responseTypeSet.length !== 1) || // code response_type but no authorization_code grant_type (responseType === 'code' && !hasAuthorizationCodeGrant) || // id_token response_type but no implicit grant_type (responseType === 'id_token' && !hasImplicitGrant) || // token response_type but no implicit grant_type (responseType === 'token' && !hasImplicitGrant) ) { // Fail validation valid = false } }) }) } return valid } }, /** * grant_types * OPTIONAL. JSON array containing a list of the OAuth 2.0 Grant Types that * the Client is declaring that it will restrict itself to using. The Grant * Type values used by OpenID Connect are: * * * authorization_code: The Authorization Code Grant Type described in * OAuth 2.0 Section 4.1. * * implicit: The Implicit Grant Type described in OAuth 2.0 Section 4.2. * * refresh_token: The Refresh Token Grant Type described in OAuth 2.0 * Section 6. * * The following table lists the correspondence between response_type values * that the Client will use and grant_type values that MUST be included in the * registered grant_types list: * * * code: authorization_code * * id_token: implicit * * token id_token: implicit * * code id_token: authorization_code, implicit * * code token: authorization_code, implicit * * code token id_token: authorization_code, implicit * * If omitted, the default is that the Client will use only the * authorization_code Grant Type. */ grant_types: { type: 'array', required: true, default: ['authorization_code'], enum: [ 'authorization_code', 'implicit', 'refresh_token', 'client_credentials' ] }, /** * application_type * OPTIONAL. Kind of the application. The default, if omitted, is web. The * defined values are native or web. Web Clients using the OAuth Implicit * Grant Type MUST only register URLs using the https scheme as redirect_uris * they MUST NOT use localhost as the hostname. Native Clients MUST only * register redirect_uris using custom URI schemes or URLs using the http: * scheme with localhost as the hostname. Authorization Servers MAY place * additional constraints on Native Clients. Authorization Servers MAY reject * Redirection URI values using the http scheme, other than the localhost case * for Native Clients. The Authorization Server MUST verify that all the * registered redirect_uris conform to these constraints. This prevents * sharing a Client ID across different types of Clients. */ application_type: { type: 'string', default: 'web', enum: [ 'web', 'native', 'service' ] }, /** * contacts * OPTIONAL. Array of e-mail addresses of people responsible for this * Client. This might be used by some providers to enable a Web user * interface to modify the Client information. */ contacts: { type: 'array', format: 'email' }, /** * client_name * OPTIONAL. Name of the Client to be presented to the End-User. If desired, * representation of this Claim in different languages and scripts is * represented as described in Section 2.1. */ client_name: { type: 'string' }, /** * logo_uri * OPTIONAL. URL that references a logo for the Client application. If * present, the server SHOULD display this image to the End-User during * approval. The value of this field MUST point to a valid image file. If * desired, representation of this Claim in different languages and scripts * is represented as described in Section 2.1. */ logo_uri: { type: 'string', format: 'url' }, /** * client_uri * OPTIONAL. URL of the home page of the Client. The value of this field * MUST point to a valid Web page. If present, the server SHOULD display * this URL to the End-User in a followable fashion. If desired, * representation of this Claim in different languages and scripts is * represented as described in Section 2.1. */ client_uri: { type: 'string', format: 'url' }, /** *policy_uri * OPTIONAL. URL that the Relying Party Client provides to the End-User to * read about the how the profile data will be used. The value of this field * MUST point to a valid web page. The OpenID Provider SHOULD display this * URL to the End-User if it is given. If desired, representation of this * Claim in different languages and scripts is represented as described in * Section 2.1. */ policy_uri: { type: 'string', format: 'url' }, /** * tos_uri * OPTIONAL. URL that the Relying Party Client provides to the End-User to * read about the Relying Party's terms of service. The value of this field * MUST point to a valid web page. The OpenID Provider SHOULD display this * URL to the End-User if it is given. If desired, representation of this * Claim in different languages and scripts is represented as described in * Section 2.1. */ tos_uri: { type: 'string', format: 'url' }, /** * jwks_uri * OPTIONAL. URL for the Client's JSON Web Key Set [JWK] document. If the * Client signs requests to the Server, it contains the signing key(s) the * Server uses to validate signatures from the Client. The JWK Set MAY also * contain the Client's encryption keys(s), which are used by the Server to * encrypt responses to the Client. When both signing and encryption keys * are made available, a use (Key Use) parameter value is REQUIRED for all * keys in the referenced JWK Set to indicate each key's intended usage. * Although some algorithms allow the same key to be used for both * signatures and encryption, doing so is NOT RECOMMENDED, as it is less * secure. The JWK x5c parameter MAY be used to provide X.509 * representations of keys provided. When used, the bare key values MUST * still be present and MUST match those in the certificate. */ jwks_uri: { type: 'string', format: 'url', conform: function (value, instance) { return !(value && instance.jwks) }, messages: { conform: 'Cannot use jwks_uri at the same time as jwks' } }, /** * jwks * OPTIONAL. Client's JSON Web Key Set [JWK] document, passed by value. The * semantics of the jwk parameter are the same as the jwk_uri parameter, * other than that The JWK Set is passed by value, rather than by reference. * This parameter is intended only to be used by Clients that, for some * reason, are unable to use the jwk_uri parameter, for instance, by native * applications that might not have a location to host the contents of the JWK * Set. If a Client can use jwk_uri, it MUST NOT use jwk. One significant * downside of jwk is that it does not enable key rotation (which jwk_uri * does, as described in Section 10 of OpenID Connect Core 1.0 * [OpenID.Core]). The jwk_uri and jwk parameters MAY NOT be used together. */ jwks: { type: 'string', conform: function (value, instance) { return !(value && instance.jwks_uri) }, messages: { conform: 'Cannot use jwks at the same time as jwks_uri' } }, /** * sector_identifier_uri * OPTIONAL. URL using the https scheme to be used in calculating * Pseudonymous Identifiers by the OP. The URL references a file with a * single JSON array of redirect_uri values. Please see Section 5. Providers * that use pairwise sub (subject) values SHOULD utilize the * sector_identifier_uri value provided in the Subject Identifier * calculation for pairwise identifiers. */ sector_identifier_uri: { type: 'string', format: 'url' }, /** * subject_type * OPTIONAL. subject_type requested for responses to this Client. The * subject_types_supported Discovery parameter contains a list of the * supported subject_type values for this server. Valid types include * pairwise and public. */ subject_type: { type: 'string', enum: ['pairwise', 'public'] }, /** * id_token_signed_response_alg * OPTIONAL. JWS alg algorithm [JWA] REQUIRED for signing the ID Token * issued to this Client. The value none MUST NOT be used as the ID Token * alg value unless the Client uses only Response Types that return no ID * Token from the Authorization Endpoint (such as when only using the * Authorization Code Flow). The default, if omitted, is RS256. The * public key for validating the signature is provided by retrieving the JWK * Set referenced by the jwks_uri element from OpenID Connect Discovery 1.0 * [OpenID.Discovery]. */ id_token_signed_response_alg: { type: 'string' }, /** * id_token_encrypted_response_alg * OPTIONAL. JWE alg algorithm [JWA] REQUIRED for encrypting the ID Token * issued to this Client. If this is requested, the response will be signed * then encrypted, with the result being a Nested JWT, as defined in [JWT]. * The default, if omitted, is that no encryption is performed. */ id_token_encrypted_response_alg: { type: 'string' }, /** * id_token_encrypted_response_enc * OPTIONAL. JWE enc algorithm [JWA] REQUIRED for encrypting the ID Token * issued to this Client. If id_token_encrypted_response_alg is specified, * the default for this value is A128CBC-HS256. When * id_token_encrypted_response_enc is included, * id_token_encrypted_response_alg MUST also be provided. */ id_token_encrypted_response_enc: { type: 'string' }, /** * userinfo_signed_response_alg * OPTIONAL. JWS alg algorithm [JWA] REQUIRED for signing UserInfo * Responses. If this is specified, the response will be JWT [JWT] * serialized, and signed using JWS. The default, if omitted, is for the * UserInfo Response to return the Claims as a UTF-8 encoded JSON object * using the application/json content-type. */ userinfo_signed_response_alg: { type: 'string' }, /** * userinfo_encrypted_response_alg * OPTIONAL. JWE [JWE] alg algorithm [JWA] REQUIRED for encrypting UserInfo * Responses. If both signing and encryption are requested, the response * will be signed then encrypted, with the result being a Nested JWT, as * defined in [JWT]. The default, if omitted, is that no encryption is * performed. */ userinfo_encrypted_response_alg: { type: 'string' }, /** * userinfo_encrypted_response_enc * OPTIONAL. JWE enc algorithm [JWA] REQUIRED for encrypting UserInfo * Responses. If userinfo_encrypted_response_alg is specified, the default * for this value is A128CBC-HS256. When userinfo_encrypted_response_enc is * included, userinfo_encrypted_response_alg MUST also be provided. */ userinfo_encrypted_response_enc: { type: 'string' }, /** * request_object_signing_alg * OPTIONAL. JWS [JWS] alg algorithm [JWA] that MUST be used for signing * Request Objects sent to the OP. All Request Objects from this Client MUST * be rejected, if not signed with this algorithm. Request Objects are * described in Section 6.1 of OpenID Connect Core 1.0 [OpenID.Core]. This * algorithm MUST be used both when the Request Object is passed by value * (using the request parameter) and when it is passed by reference (using * the request_uri parameter). Servers SHOULD support RS256. The value * none MAY be used. The default, if omitted, is that any algorithm * supported by the OP and the RP MAY be used. */ request_object_signing_alg: { type: 'string' }, /** * request_object_encryption_alg * OPTIONAL. JWE [JWE] alg algorithm [JWA] the RP is declaring that it may * use for encrypting Request Objects sent to the OP. This parameter SHOULD * be included when symmetric encryption will be used, since this signals to * the OP that a client_secret value needs to be returned from which the * symmetric key will be derived, that might not otherwise be returned. The * RP MAY still use other supported encryption algorithms or send * unencrypted Request Objects, even when this parameter is present. If both * signing and encryption are requested, the Request Object will be signed * then encrypted, with the result being a Nested JWT, as defined in [JWT]. * The default, if omitted, is that the RP is not declaring whether it might * encrypt any Request Objects. */ request_object_encryption_alg: { type: 'string' }, /** * request_object_encryption_enc * OPTIONAL. JWE enc algorithm [JWA] the RP is declaring that it may use for * encrypting Request Objects sent to the OP. If * request_object_encryption_alg is specified, the default for this value is * A128CBC-HS256. When request_object_encryption_enc is included, * request_object_encryption_alg MUST also be provided. */ request_object_encryption_enc: { type: 'string' }, /** * token_endpoint_auth_method * OPTIONAL. Requested Client Authentication method for the Token Endpoint. * The options are client_secret_post, client_secret_basic, * client_secret_jwt, private_key_jwt, and none, as described in Section 9 of * OpenID Connect Core 1.0 [OpenID.Core]. Other authentication methods MAY be * defined by extensions. If omitted, the default is client_secret_basic -- * the HTTP Basic Authentication Scheme specified in Section 2.3.1 of OAuth * 2.0 [RFC6749]. */ token_endpoint_auth_method: { type: 'string', enum: [ 'client_secret_basic', 'client_secret_post', 'client_secret_jwt', 'private_key_jwt' // 'none' ], default: 'client_secret_basic' }, /** * token_endpoint_auth_signing_alg * OPTIONAL. JWS [JWS] alg algorithm [JWA] that MUST be used for signing the * JWT [JWT] used to authenticate the Client at the Token Endpoint for the * private_key_jwt and client_secret_jwt authentication methods. All Token * Requests using these authentication methods from this Client MUST be * rejected, if the JWT is not signed with this algorithm. Servers SHOULD * support RS256. The value none MUST NOT be used. The default, if omitted, * is that any algorithm supported by the OP and the RP MAY be used. */ token_endpoint_auth_signing_alg: { type: 'string' }, /** * default_max_age * OPTIONAL. Default Maximum Authentication Age. Specifies that the End-User * MUST be actively authenticated if the End-User was authenticated longer * ago than the specified number of seconds. The max_age request parameter * overrides this default value. If omitted, no default Maximum * Authentication Age is specified. */ default_max_age: { type: 'number' }, /** * require_auth_time * OPTIONAL. Boolean value specifying whether the auth_time Claim in the ID * Token is REQUIRED. It is REQUIRED when the value is true. (If this is * false, the auth_time Claim can still be dynamically requested as an * individual Claim for the ID Token using the claims request parameter * described in Section 5.5.1 of OpenID Connect Core 1.0 [OpenID.Core].) * If omitted, the default value is false. */ require_auth_time: { type: 'boolean' }, /** * default_acr_values * OPTIONAL. Default requested Authentication Context Class Reference * values. Array of strings that specifies the default acr values that the * OP is being requested to use for processing requests from this Client, * with the values appearing in order of preference. The Authentication Context * Class satisfied by the authentication performed is returned as the acr * Claim Value in the issued ID Token. The acr Claim is requested as a * Voluntary Claim by this parameter. The acr_values_supported discovery * element contains a list of the supported acr values supported by this * server. Values specified in the acr_values request parameter or an * individual acr Claim request override these default values. */ default_acr_values: { type: 'array' }, /** * initiate_login_uri * OPTIONAL. URI using the https scheme that a third party can use to * initiate a login by the RP, as specified in Section 4 of OpenID Connect * Core 1.0 [OpenID.Core]. The URI MUST accept requests via both GET and * POST. The Client MUST understand the login_hint and iss parameters and * SHOULD support the target_link_uri parameter. */ initiate_login_uri: { type: 'string', format: 'url' }, /** * request_uris * OPTIONAL. Array of request_uri values that are pre-registered by the RP * for use at the OP. Servers MAY cache the contents of the files referenced * by these URIs and not retrieve them at the time they are used in a * request. OPs can require that request_uri values used be * pre-registered with the require_request_uri_registration discovery * parameter. * If the contents of the request file could ever change, these URI values * SHOULD include the base64url encoded SHA-256 hash value of the file * contents referenced by the URI as the value of the URI fragment. If the * fragment value used for a URI changes, that signals the server that its * cached value for that URI with the old fragment value is no longer valid. */ request_uris: { type: 'array', format: 'url' }, /** * Additional Client Metadata parameters MAY also be used. Some are defined by * other specifications, such as OpenID Connect Session Management 1.0 * [OpenID.Session]. */ /** * OpenID Connect Session Management 1.0 * Client Registration Metadata * * http://openid.net/specs/openid-connect-session-1_0.html#ClientMetadata * * This Client Metadata parameter MAY be included in the Client's Registration * information when Session Management and Dynamic Registration are supported: * * post_logout_redirect_uris * OPTIONAL. Array of URLs supplied by the RP to which it MAY request that * the End-User's User Agent be redirected using the post_logout_redirect_uri * parameter after a logout has been performed. */ post_logout_redirect_uris: { type: 'array', format: 'url', message: 'invalid_post_logout_redirect_uri', messages: { format: 'Must contain valid URIs' } }, /** * client_secret */ client_secret: { type: 'string', default: Modinha.defaults.random(10) }, /** * Anvil Connect specific properties */ /** * trusted_client * Flag that indicates whether or not the client is within the sercurity * realm of the authorization server. This can be used to suppress the user- * consent prompt when the Relying Party is owned by the owner of the * authorization server. */ trusted: { type: 'boolean', default: false, secondary: true }, /** * default_client_scope * Property used to specify default scope for client access tokens issued * by the /token endpoint during "client_credentials" grant. * (Two-legged OAuth) */ default_client_scope: { type: 'string' }, /** * user_id * The UUID of the user who registered the client. */ userId: { type: 'string', reference: User }, /** * origins * List of URIs for this client from which requests to the authorization * server can originate. */ origins: { type: 'array', format: 'url' }, /** * scopes * List of user-authorized scopes required to display this client at the * applications endpoint. * * In the future, this value may be used to restrict users from signing into * clients they have no permissions for. */ scopes: { type: 'array', default: [] } }) /** * Document persistence */ Client.extend(Document) Client.__client = client /** * Client intersections */ Client.intersects('roles') /** * Authorized scope */ Client.prototype.authorizedScope = function (callback) { var client = Client.__client client.zrange('clients:' + this._id + ':roles', 0, -1, function (err, roles) { if (err) { return callback(err) } if (!roles || roles.length === 0) { return callback(null, []) } var multi = client.multi() roles.forEach(function (role) { multi.zrange('roles:' + role + ':scopes', 0, -1) }) multi.exec(function (err, results) { if (err) { return callback(err) } callback(null, results.map(function (result) { return result[1][0] })) }) }) } /** * Authorized by user */ Client.listAuthorizedByUser = function (userId, options, callback) { if (!callback) { callback = options options = {} } options.index = 'users:' + userId + ':clients' options.select = [ '_id', 'client_name', 'client_uri', 'logo_uri', 'trusted' ] Client.list(options, function (err, clients) { if (err) { return callback(err) } callback(null, clients) }) } /** * Mappings */ Client.mappings.registration = { _id: 'client_id', client_secret: 'client_secret', client_name: 'client_name', logo_uri: 'logo_uri', contacts: 'contacts', token_endpoint_auth_method: 'token_endpoint_auth_method', application_type: 'application_type', redirect_uris: 'redirect_uris', request_uris: 'request_uris', created: 'client_id_issued_at' } /** * Client Configuration */ Client.prototype.configuration = function (settings, token) { var configuration = this.project('registration') var registrationClientUri = settings.issuer + '/register/' + this._id configuration.registration_client_uri = registrationClientUri if (token) { configuration.registration_access_token = token } return configuration } /** * Authenticate */ Client.authenticate = function (req, callback) { var method // Use HTTP Basic Authentication Method if (req.headers && req.headers.authorization) { method = 'client_secret_basic' } // Use HTTP Post Authentication Method if (req.body && req.body.client_secret) { // Fail if multiple authentication methods are attempted if (method) { return callback(new AuthorizationError({ error: 'unauthorized_client', error_description: 'Must use only one authentication method', statusCode: 400 })) } method = 'client_secret_post' } // Use Client JWT Authentication Method if (req.body && req.body.client_assertion_type) { var type = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer' // Fail if multiple authentication methods are attempted if (method) { return callback(new AuthorizationError({ error: 'unauthorized_client', error_description: 'Must use only one authentication method', statusCode: 400 })) } // Invalid client assertion type if (req.body.client_assertion_type !== type) { return callback(new AuthorizationError({ error: 'unauthorized_client', error_description: 'Invalid client assertion type', statusCode: 400 })) } // Missing client assertion if (!req.body.client_assertion) { return callback(new AuthorizationError({ error: 'unauthorized_client', error_description: 'Missing client assertion', statusCode: 400 })) } method = 'client_secret_jwt' } // Missing authentication parameters if (!method) { return callback(new AuthorizationError({ error: 'unauthorized_client', error_description: 'Missing client credentials', statusCode: 400 })) } // Apply the appropriate authentication method authenticators[method](req, callback) } /** * */ var authenticators = { /** * HTTP Basic Authentication w/client_id and client_secret */ 'client_secret_basic': function (req, callback) { var authorization = req.headers.authorization.split(' ') var scheme = authorization[0] var credentials = Buffer.from(authorization[1], 'base64') .toString('ascii') .split(':') var clientId = credentials[0] var clientSecret = credentials[1] // malformed credentials if (credentials.length !== 2) { return callback(new AuthorizationError({ error: 'unauthorized_client', error_description: 'Malformed HTTP Basic credentials', statusCode: 400 })) } // invalid authorization scheme if (!/^Basic$/i.test(scheme)) { return callback(new AuthorizationError({ error: 'unauthorized_client', error_description: 'Invalid authorization scheme', statusCode: 400 })) } // missing credentials if (!clientId || !clientSecret) { return callback(new AuthorizationError({ error: 'unauthorized_client', error_description: 'Missing client credentials', statusCode: 400 })) } Client.get(clientId, function (err, client) { if (err) { return callback(err) } // Unknown client if (!client) { return callback(new AuthorizationError({ error: 'unauthorized_client', error_description: 'Unknown client identifier', statusCode: 401 })) } // Mismatching secret if (client.client_secret !== clientSecret) { return callback(new AuthorizationError({ error: 'unauthorized_client', error_description: 'Mismatching client secret', statusCode: 401 })) } callback(null, client) }) }, /** * HTTP POST body authentication */ 'client_secret_post': function (req, callback) { var params = req.body var clientId = params.client_id var clientSecret = params.client_secret // missing credentials if (!clientId || !clientSecret) { return callback(new AuthorizationError({ error: 'unauthorized_client', error_description: 'Missing client credentials', statusCode: 400 })) } Client.get(clientId, function (err, client) { if (err) { return callback(err) } // Unknown client if (!client) { return callback(new AuthorizationError({ error: 'unauthorized_client', error_description: 'Unknown client identifier', statusCode: 401 })) } // Mismatching secret if (client.client_secret !== clientSecret) { return callback(new AuthorizationError({ error: 'unauthorized_client', error_description: 'Mismatching client secret', statusCode: 401 })) } callback(null, client) }) }, 'client_secret_jwt': function (req, callback) { // peek at the JWT body to get the sub var jwt = req.body.client_assertion var payloadB64u = jwt.split('.')[1] var payload = JSON.parse(base64url.decode(payloadB64u)) if (!payload || !payload.sub) { return callback(new AuthorizationError({ error: 'unauthorized_client', error_description: 'Cannot extract client id from JWT', statusCode: 400 })) } Client.get(payload.sub, function (err, client) { if (err) { return callback(err) } if (!client) { return callback(new AuthorizationError({ error: 'unauthorized_client', error_description: 'Unknown client identifier', statusCode: 400 })) } if (!client.client_secret) { return callback(new AuthorizationError({ error: 'unauthorized_client', error_description: 'Missing client secret', statusCode: 400 })) } var token // = ClientSecretToken.decode(jwt, client.client_secret) if (!token || token instanceof Error) { return callback(new AuthorizationError({ error: 'unauthorized_client', error_description: 'Invalid client JWT', statusCode: 400 })) } // TODO: validate the payload callback(null, client, token) }) } // 'private_key_jwt': function () {}, // 'none': function () {} } /** * Exports */ module.exports = Client