Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Web Sign In and PKCE #257

Merged
merged 3 commits into from
Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion includes/class-indieauth-authorization-endpoint.php
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ public function authorization_code( $params ) {
}

$code = $params['code'];
$code_verifier = isset( $params['code_verifier'] ) ? $params['code_verifier'] : null;
$params = wp_array_slice_assoc( $params, array( 'client_id', 'redirect_uri' ) );
$token = $this->get_code( $code );
$scopes = isset( $token['scope'] ) ? array_filter( explode( ' ', $token['scope'] ) ) : array();
Expand All @@ -335,7 +336,6 @@ public function authorization_code( $params ) {
unset( $token['exp'] );
// If there is a code challenge
if ( isset( $token['code_challenge'] ) ) {
$code_verifier = $request->get_param( 'code_verifier' );
if ( ! $code_verifier ) {
$this->delete_code( $code, $token['user'] );
return new WP_OAuth_Response( 'invalid_grant', __( 'Failed PKCE Validation', 'indieauth' ), 400 );
Expand Down
75 changes: 62 additions & 13 deletions includes/class-web-signin.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,40 +37,79 @@ public function settings() {
* @param string $redirect_uri where to redirect
*/
public function websignin_redirect( $me, $redirect_uri ) {
$authorization_endpoint = find_rels( $me, 'authorization_endpoint' );
if ( ! $authorization_endpoint ) {
$endpoints = find_rels( $me, array( 'indieauth-metadata', 'authorization_endpoint' ) );

if ( array_key_exists( 'indieauth-metadata', $endpoints ) ) {
$state = $this->get_indieauth_metadata( $endpoints['indieauth-metadata'] );
} elseif ( ! array_key_exists( 'authorization_endpoint', $endpoints ) ) {
return new WP_Error(
'authentication_failed',
__( '<strong>ERROR</strong>: Could not discover endpoints', 'indieauth' ),
array(
'status' => 401,
)
);
} else {
$state = array(
'me' => $me,
'authorization_endpoint' => $endpoints['authorization_endpoint'],
);
}
$state = compact( 'me', 'authorization_endpoint' );
$state['me'] = $me;
$state['code_verifier'] = wp_generate_password( 128, false );

$token = new Token_Transient( 'indieauth_state' );
$query = add_query_arg(
array(
'me' => rawurlencode( $me ),
'redirect_uri' => rawurlencode( $redirect_uri ),
'client_id' => rawurlencode( home_url() ),
'state' => $token->set_with_cookie( $state, 120 ),
'response_type' => 'id',
'response_type' => 'code', // In earlier versions of the specification this was ID.
'client_id' => rawurlencode( home_url() ),
'redirect_uri' => rawurlencode( $redirect_uri ),
'state' => $token->set_with_cookie( $state, 120 ),
'code_challenge' => base64_urlencode( indieauth_hash( $state['code_verifier'] ) ),
'code_challenge_method' => 'S256',
'me' => rawurlencode( $me ),
),
$authorization_endpoint
$endpoints['authorization_endpoint']
);
// redirect to authentication endpoint
wp_redirect( $query );
}

// Retrieves the Metadata from an IndieAuth Metadata Endpoint.
public function get_indieauth_metadata( $url ) {
$resp = wp_remote_get(
$url,
array(
'headers' => array(
'Accept' => 'application/json',
),
)
);
if ( is_wp_error( $resp ) ) {
return $resp;
}

$code = (int) wp_remote_retrieve_response_code( $resp );

if ( ( $code / 100 ) !== 2 ) {
return new WP_Error( 'no_metadata_endpoint', __( 'No Metadata Endpoint Found', 'indieauth' ) );
}

$body = wp_remote_retrieve_body( $resp );
return json_decode( $body, true );
}



// $args must consist of redirect_uri, client_id, and code
public function verify_authorization_code( $post_args, $endpoint ) {
if ( ! wp_http_validate_url( $endpoint ) ) {
return new WP_OAuth_Response( 'server_error', __( 'Did Not Receive a Valid Authorization Endpoint', 'indieauth' ), 500 );
}

$defaults = array(
'client_id' => home_url(),
'client_id' => home_url(),
'grant_type' => 'authorization_code',
);

$post_args = wp_parse_args( $post_args, $defaults );
Expand Down Expand Up @@ -134,10 +173,20 @@ public function authenticate( $user, $url ) {
if ( is_wp_error( $state ) ) {
return $state;
}
if ( array_key_exists( 'iss', $_REQUEST ) ) {
$iss = rawurldecode( $_REQUEST['iss'] );
if ( $iss !== $state['issuer'] ) {
return new WP_Error( 'indieauth_iss_error', __( 'Issuer Parameter does not Match Server Metadata', 'indieauth' ) );
}
} elseif ( array_key_exists( 'issuer', $state ) ) {
return new WP_Error( 'indieauth_iss_error', __( 'Issuer Parameter Present in Metadata Endpoint But Not Returned by Authorization Endpoint', 'indieauth' ) );
}

$response = $this->verify_authorization_code(
array(
'code' => $_REQUEST['code'],
'redirect_uri' => wp_login_url( $redirect_to ),
'code' => $_REQUEST['code'],
'redirect_uri' => wp_login_url( $redirect_to ),
'code_verifier' => $state['code_verifier'],
),
$state['authorization_endpoint']
);
Expand Down Expand Up @@ -253,7 +302,7 @@ public function login_form_websignin() {
include plugin_dir_path( __DIR__ ) . 'templates/websignin-form.php';
}
if ( 'POST' === $_SERVER['REQUEST_METHOD'] ) {
$redirect_to = array_key_exists( 'redirect_to', $_REQUEST ) ? $_REQUEST['redirect_to'] : null;
$redirect_to = array_key_exists( 'redirect_to', $_REQUEST ) ? $_REQUEST['redirect_to'] : '';
$redirect_to = rawurldecode( $redirect_to );

if ( array_key_exists( 'websignin_identifier', $_POST ) ) { // phpcs:ignore
Expand Down
3 changes: 3 additions & 0 deletions templates/indieauth-authenticate-form.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@
<input type="hidden" name="me" value="<?php echo esc_url( $me ); ?>" />
<input type="hidden" name="response_type" value="<?php echo esc_attr( $response_type ); ?>" />
<input type="hidden" name="state" value="<?php echo esc_attr( $state ); ?>" />
<input type="hidden" name="code_challenge" value="<?php echo esc_attr( $code_challenge ); ?>" />
<input type="hidden" name="code_challenge_method" value="<?php echo esc_attr( $code_challenge_method ); ?>" />

<button name="wp-submit" value="authorize" class="button button-primary button-large"><?php esc_html_e( 'Allow', 'indieauth' ); ?></button>
<a name="wp-submit" value="cancel" class="button button-large" href="<?php echo esc_url( home_url() ); ?>"><?php esc_html_e( 'Cancel', 'indieauth' ); ?></a>
</p>
Expand Down
2 changes: 2 additions & 0 deletions templates/indieauth-authorize-form.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@
<input type="hidden" name="state" value="<?php echo esc_attr( $state ); ?>" />
<input type="hidden" name="me" value="<?php echo esc_url( $me ); ?>" />
<input type="hidden" name="response_type" value="<?php echo esc_attr( $response_type ); ?>" />
<input type="hidden" name="code_challenge" value="<?php echo esc_attr( $code_challenge ); ?>" />
<input type="hidden" name="code_challenge_method" value="<?php echo esc_attr( $code_challenge_method ); ?>" />

<?php if ( ! is_null( $code_challenge ) ) { ?>
<input type="hidden" name="code_challenge" value="<?php echo esc_attr( $code_challenge ); ?>" />
Expand Down
8 changes: 4 additions & 4 deletions templates/indieauth-notices.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@
</p>
<?php
}
if ( ! is_null( $code_challenge ) && 'S256' === $code_challenge_method ) {
if ( is_null( $code_challenge ) && 'S256' !== $code_challenge_method ) {
?>
<p class="pkce">
<strong> 🔒
<p class="pkce notice notice-error">
<strong> 🛡️
<?php
echo wp_kses(
/* translators: PKCE specification link */
sprintf( __( 'This app is using %s for security.', 'indieauth' ), '<a href="https://indieweb.org/PKCE">PKCE</a>' ),
sprintf( __( 'This app is not using %s for security which is now required for IndieAuth', 'indieauth' ), '<a href="https://indieweb.org/PKCE">PKCE</a>' ),
array(
'a' => array(
'href' => array(),
Expand Down