Skip to content

Commit

Permalink
Implemented the DPoP token exchange (#411)
Browse files Browse the repository at this point in the history
- This implements the OAuth2 code exchange step: after an auth code has been returned by the IdP through redirection, the client can use the obtained auth code to get an access token. If the request has a DPoP header, the returned token is bound to the DPoP key.
- The mockJwk reference is unefined when setting up the mock, but the mockJwk() function is defined when *calling* the mock. Thanks @Vinnl !
- The oidc module is independant from solid, so it should not depend on the core module, which is meant to be solid-specific. this implies some redundancy in the types implemented in both places, but that means that these types may evolve independantly, while still getting errors in case of incompatibility, which is a good thing.
- The endpoint returns a token_type field, which can be used to verify that the token is of the requested type (Bearer or DPoP)

Co-authored-by: Vincent <Vinnl@users.noreply.github.com>
  • Loading branch information
NSeydoux and Vinnl committed Oct 13, 2020
1 parent caa8518 commit 66b8839
Show file tree
Hide file tree
Showing 12 changed files with 916 additions and 79 deletions.
10 changes: 10 additions & 0 deletions packages/oidc-dpop-client-browser/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions packages/oidc-dpop-client-browser/package.json
Expand Up @@ -29,15 +29,16 @@
"@rollup/plugin-node-resolve": "^9.0.0",
"cross-fetch": "^3.0.6",
"rollup": "^2.15.0",
"rollup-plugin-typescript2": "^0.27.1",
"rollup-plugin-node-polyfills": "^0.2.1"
"rollup-plugin-node-polyfills": "^0.2.1",
"rollup-plugin-typescript2": "^0.27.1"
},
"dependencies": {
"@inrupt/solid-client-authn-core": "^0.2.0",
"@types/form-urlencoded": "^2.0.1",
"@types/jsonwebtoken": "^8.5.0",
"@types/node-jose": "^1.1.5",
"@types/url-parse": "^1.4.3",
"@types/uuid": "^8.3.0",
"form-urlencoded": "^4.2.1",
"jose": "^2.0.2",
"jsonwebtoken": "^8.5.1",
"node-jose": "^2.0.0",
Expand Down
73 changes: 73 additions & 0 deletions packages/oidc-dpop-client-browser/src/common/types.ts
@@ -0,0 +1,73 @@
/*
* Copyright 2020 Inrupt Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

import URL from "url-parse";

export interface IIssuerConfig {
issuer: URL;
authorizationEndpoint: URL;
tokenEndpoint: URL;
userinfoEndpoint?: URL;
jwksUri: URL;
registrationEndpoint?: URL;
scopesSupported?: string[];
responseTypesSupported?: string[];
responseModesSupported?: string[];
grantTypesSupported?: string[];
acrValuesSupported?: string[];
subjectTypesSupported: string[];
idTokenSigningAlgValuesSupported?: string[];
idTokenEncryptionAlgValuesSupported?: string[];
idTokenEncryptionEncValuesSupported?: string[];
userinfoSigningAlgValuesSupported?: string[];
userinfoEncryptionAlgValuesSupported?: string[];
userinfoEncryptionEncValuesSupported?: string[];
requestObjectSigningAlgValuesSupported?: string[];
requestObjectEncryptionAlgValuesSupported?: string[];
requestObjectEncryptionEncValuesSupported?: string[];
tokenEndpointAuthMethodsSupported?: string[];
tokenEndpointAuthSigningAlgValuesSupported?: string[];
displayValuesSupported?: string[];
claimTypesSupported?: string[];
claimsSupported: string[];
serviceDocumentation?: string[];
claimsLocalesSupported?: boolean;
uiLocalesSupported?: boolean;
claimsParameterSupported?: boolean;
requestParameterSupported?: boolean;
requestUriParameterSupported?: boolean;
requireRequestUriRegistration?: boolean;
opPolicyUri?: URL;
opTosUri?: URL;
}

export interface IClient {
clientId: string;
clientSecret?: string;
clientName?: string;
}

export interface IClientRegistrarOptions {
sessionId: string;
clientName?: string;
redirectUrl?: URL;
registrationAccessToken?: string;
}
Expand Up @@ -20,10 +20,7 @@
*/

import { it, describe } from "@jest/globals";
import {
IIssuerConfig,
IClientRegistrarOptions,
} from "@inrupt/solid-client-authn-core";
import { IIssuerConfig, IClientRegistrarOptions } from "../common/types";
import URL from "url-parse";
import { registerClient } from "./clientRegistrar";
import { Response } from "cross-fetch";
Expand Down
Expand Up @@ -28,7 +28,7 @@ import {
IIssuerConfig,
IClient,
IClientRegistrarOptions,
} from "@inrupt/solid-client-authn-core";
} from "../common/types";

function processErrorResponse(
// The type is any here because the object is parsed from a JSON response
Expand Down
30 changes: 1 addition & 29 deletions packages/oidc-dpop-client-browser/src/dpop/dpop.spec.ts
Expand Up @@ -25,38 +25,10 @@ import { describe, it } from "@jest/globals";
import {
createDpopHeader,
decodeJwt,
generateJwk,
generateJwkForDpop,
generateJwkRsa,
normalizeHttpUriClaim,
signJwt,
} from "./dpop";

describe("generateJwk", () => {
it("can generate a RSA-based JWK", async () => {
const key = await generateJwk("RSA");
expect(key.kty).toEqual("RSA");
});

it("can generate an elliptic curve-based JWK", async () => {
const key = await generateJwk("EC", "P-256");
expect(key.kty).toEqual("EC");
});
});

describe("generateJwkForDpop", () => {
it("generates an elliptic curve-base key, which is a sensible default for DPoP", async () => {
const key = await generateJwkForDpop();
expect(key.kty).toEqual("EC");
});
});

describe("generateJwkRsa", () => {
it("generates an RSA key", async () => {
const key = await generateJwkRsa();
expect(key.kty).toEqual("RSA");
});
});
import { generateJwk, generateJwkForDpop } from "./keyGeneration";

describe("signJwt/decodeJwt", () => {
it("generates a JWT that can be decoded without signature verification", async () => {
Expand Down
41 changes: 1 addition & 40 deletions packages/oidc-dpop-client-browser/src/dpop/dpop.ts
Expand Up @@ -21,35 +21,10 @@

import URL from "url-parse";
import { JWK } from "node-jose";
import {
BasicParameters,
ECCurve,
JSONWebKey,
JWKECKey,
JWKOctKey,
JWKOKPKey,
JWKRSAKey,
OKPCurve,
} from "jose";
import { JSONWebKey, JWKECKey, JWKOctKey, JWKOKPKey, JWKRSAKey } from "jose";
import JWT, { VerifyOptions } from "jsonwebtoken";
import { v4 } from "uuid";

/**
* Generates a Json Web Key
* @param kty Key type
* @param crvBitlength Curve length (only relevant for elliptic curve algorithms)
* @param parameters
* @hidden
*/
export async function generateJwk(
kty: "EC" | "RSA",
crvBitlength?: ECCurve | OKPCurve | number,
parameters?: BasicParameters
): Promise<JSONWebKey> {
const key = await JWK.createKey(kty, crvBitlength, parameters);
return key.toJSON(true) as JSONWebKey;
}

/**
* Generates a Json Web Token (https://tools.ietf.org/html/rfc7519) containing
* the provided payload and using the signature algorithm specified in the options.
Expand Down Expand Up @@ -148,17 +123,3 @@ export async function createDpopHeader(
}
);
}

/**
* Generates a JSON Web Key suitable to be used to sign HTTP request headers.
*/
export async function generateJwkForDpop(): Promise<JSONWebKey> {
return generateJwk("EC", "P-256", { alg: "ES256" });
}

/**
* Generates a JSON Web Key based on the RSA algorithm
*/
export async function generateJwkRsa(): Promise<JSONWebKey> {
return generateJwk("RSA");
}
54 changes: 54 additions & 0 deletions packages/oidc-dpop-client-browser/src/dpop/keyGeneration.spec.ts
@@ -0,0 +1,54 @@
/*
* Copyright 2020 Inrupt Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

import { describe, it } from "@jest/globals";

import {
generateJwk,
generateJwkForDpop,
generateJwkRsa,
} from "./keyGeneration";

describe("generateJwk", () => {
it("can generate a RSA-based JWK", async () => {
const key = await generateJwk("RSA");
expect(key.kty).toEqual("RSA");
});

it("can generate an elliptic curve-based JWK", async () => {
const key = await generateJwk("EC", "P-256");
expect(key.kty).toEqual("EC");
});
});

describe("generateJwkForDpop", () => {
it("generates an elliptic curve-base key, which is a sensible default for DPoP", async () => {
const key = await generateJwkForDpop();
expect(key.kty).toEqual("EC");
});
});

describe("generateJwkRsa", () => {
it("generates an RSA key", async () => {
const key = await generateJwkRsa();
expect(key.kty).toEqual("RSA");
});
});
53 changes: 53 additions & 0 deletions packages/oidc-dpop-client-browser/src/dpop/keyGeneration.ts
@@ -0,0 +1,53 @@
/*
* Copyright 2020 Inrupt Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

import { JWK } from "node-jose";
import { BasicParameters, ECCurve, JSONWebKey, OKPCurve } from "jose";

/**
* Generates a Json Web Key
* @param kty Key type
* @param crvBitlength Curve length (only relevant for elliptic curve algorithms)
* @param parameters
* @hidden
*/
export async function generateJwk(
kty: "EC" | "RSA",
crvBitlength?: ECCurve | OKPCurve | number,
parameters?: BasicParameters
): Promise<JSONWebKey> {
const key = await JWK.createKey(kty, crvBitlength, parameters);
return key.toJSON(true) as JSONWebKey;
}

/**
* Generates a JSON Web Key suitable to be used to sign HTTP request headers.
*/
export async function generateJwkForDpop(): Promise<JSONWebKey> {
return generateJwk("EC", "P-256", { alg: "ES256" });
}

/**
* Generates a JSON Web Key based on the RSA algorithm
*/
export async function generateJwkRsa(): Promise<JSONWebKey> {
return generateJwk("RSA");
}

0 comments on commit 66b8839

Please sign in to comment.