OAuth徹底入門 セキュアな認可システムを適用するための原則と実践 電子書籍(Justin Richer Antonio Sanso 須田 智之 Authlete, Inc.)|翔泳社の本
  1. ホーム >
  2. 電子書籍 >
  3. OAuth徹底入門 セキュアな認可システムを適用するための原則と実践

OAuth徹底入門 セキュアな認可システムを適用するための原則と実践



翻訳
監修

形式:
電子書籍
発売日:
ISBN:
9784798159300
価格:
4,620(本体4,200円+税10%)
カテゴリ:
ネットワーク・サーバ
キーワード:
#ネットワーク・サーバ・セキュリティ,#データ・データベース,#システム運用,#Web・アプリ開発
シリーズ:
徹底入門
電子書籍

OAuthは近年、WEBアプリケーションで使われる主要な認可プロトコルです。本書ではOAuthをどのようなプラットフォームでも適用できるように解説をしています。
本書は全体で16章あり、4つのパートに分割しています。パート1にあたる第1章と第2章はOAuth 2.0のプロトコルの概要を説明しており、基盤となる知識を得るための読み物としています。パート2は第3章から第6章までとなっており、OAuth 2.0のエコシステム全体をどのように構築するのかについて示しています。パート3は第7章から第10章までとなっており、OAuth 2.0のエコシステムにおけるさまざまな構成要素が持つ脆弱性について説明しており、その脆弱性をどのように回避するのかについて述べています。最後のパートは第11章から第16章までで構成されており、OAuth 2.0を核とした次の世代のプロトコルについて語っており、標準や仕様に関してOAuthの周辺の技術も踏まえて見ていき、最後に本書のまとめを行っています。

※本電子書籍は同名出版物を底本として作成しました。記載内容は印刷出版当時のものです。
※印刷出版再現のため電子書籍としては不要な情報を含んでいる場合があります。
※印刷出版とは異なる表記・表現の場合があります。予めご了承ください。
※プレビューにてお手持ちの電子端末での表示状態をご確認の上、商品をお買い求めください。

(翔泳社)

OAuth2.0の仕組みを把握し、構築への実用的な指針を徹底解説
OAuthを実サービスに適用する技術を4部構成で徹底的に解説します。

OAuthを実サービスに適用する技術を4部構成で徹底的に解説します。

第1部はじめの一歩

OAuth2.0のプロトコルがどのように機能するのか、そして、なぜそのような仕様になっているのかについて、全体像を学びます。

第2部OAuth2.0環境の構築

OAuth2.0のエコシステム全体(クライアント、保護対象リソース、認可サーバ)をゼロから構築します。

第3部OAuth2.0の実装と脆弱性

OAuth 2.0のエコシステムにおける構成要素が持つ脆弱性について、脆弱性をどのように回避するかについて説明します。

第4部さらなるOAuthの活用

OAuthプロトコルの中核となる部分からすこし離れて、その堅牢な核の周りに用意された拡張機能、プロファイル、補助的な構成要素の世界について学びます。

目次の登録はありません。
本書は付属データの提供はございません。

お問い合わせ

内容についてのお問い合わせは、正誤表、追加情報をご確認後に、お送りいただくようお願いいたします。

正誤表、追加情報に掲載されていない書籍内容へのお問い合わせや
その他書籍に関するお問い合わせは、書籍のお問い合わせフォームからお送りください。

利用許諾に関するお問い合わせ

本書の書影(表紙画像)をご利用になりたい場合は書影許諾申請フォームから申請をお願いいたします。
書影(表紙画像)以外のご利用については、こちらからお問い合わせください。

追加情報はありません。
この商品の「よくある質問」はありません。

現在表示されている正誤表の対象書籍

書籍の種類:電子書籍

書籍の刷数:全刷


※重版をした際に、内容が修正されている場合があります。「刷数の確認方法(例)」の図を参考に、お手元の書籍の刷数をご確認ください。下の「書籍の刷数」の欄で刷数を選択すると、お持ちの書籍の刷数に合わせて、正誤情報を絞り込むことができます。

書籍によっては表記が異なる場合がございます


本書に誤りまたは不十分な記述がありました。下記のとおり訂正し、お詫び申し上げます。

対象の書籍は正誤表がありません。

 書籍の刷数で正誤情報を絞り込みたい場合は選択してください。

 書籍の種類:

最終更新日:2024年09月04日
ページ数 内容 書籍修正刷 電子書籍訂正 発生刷 登録日
059
真ん中のコード内下から2行目
redirect_uri: client.redirect_urls[0]
redirect_uri: client.redirect_uris[0]

小文字の「l(エル)」を「i(アイ)に修正しました。
※リフローEPUBの場合は「3.2.1 認可リクエストの送信」の3番目のコードが該当します。
3刷 1刷 2020.01.07
061
一番下のコード内下から2行目
redirect_uri: client.redirect_urls[0]
redirect_uri: client.redirect_uris[0]

小文字の「l(エル)」を「i(アイ)に修正しました。
※リフローEPUBの場合は「3.2.2 認可サーバーからのレスポンスの処理」の3番目のコードが該当します。
3刷 1刷 2020.01.07
064
3つ目のコードの1行目
if (req.query.state == state) {
if (req.query.state != state) {

※リフローEPUBの場合は、「3.2.3 stateパラメータを使ったサイトをまたいだ攻撃に対する保護の追加」の3番目のコードが該当します。
3刷 1刷 2020.01.24
067
2つ目のコード 下から4行目
res.ender('error', { error: 'Server returned response code: ' +
res.render('error', {error: 'Server returned response code: ' +

※リフローEPUBの場合、図3.5の後にある2つ目のコードが該当箇所になります。
5刷 1刷 2021.07.28
080
4.2節 1番目のコードブロック
nosql.one(function(token) {
  if (token.access_token == inToken) {
    return token;
  }
}, function(err, token) {
  if (token) {
    console.log("We found a matching token: %s", inToken);
  } else {
    console.log(’No matching token was found.’);
  }
  req.access_token = token;
  next();
  return;
});
nosql.one().make(function(builder) {
  builder.where('access_token', inToken);
  builder.callback(function(err, token) {
    if (token) {
      console.log("We found a matching token: %s", inToken);
    } else {
      console.log('No matching token was found.');
    };
    req.access_token = token;
    next();
    return;
  });
});
6刷 1刷 2024.09.04
100
2番目のコードブロック(2行目の「`_.find`」が「`__.find`」になります)
var getClient = function(clientId) {
  return _.find(clients, function(client) {
    return client.client_id == clientId;
  });
};
var getClient = function(clientId) {
  return __.find(clients, function(client) {
    return client.client_id == clientId;
  });
};

リフローEPUBの場合、5.1節の4つ目のコード枠が該当箇所になります。
6刷 1刷 2024.09.04
102
最初のコードブロック内のres.renderの第一引数
if (!client) { res.render('error, {error: 'Unknown client'}); ……
if (!client) { res.render('error', {error: 'Unknown client'}); ……

「'error,」を「'error', 」に修正します。

※リフローEPUBの場合、「5.2.1 認可エンドポイント」の3番目のコードが該当箇所になります。
5刷 1刷 2022.10.20
105
1つ目のコード内、下から1行目
reqid=tKVUYQSM}&approve=Approve
reqid=tKVUYQSM&approve=Approve

不要な } を削除しました。
※リフローEPUBの場合は、「5.2.2 クライアントの認可」の2番目のコードが該当します。
3刷 1刷 2019.05.07
112
囲み内の見出し、下から4行目
見出し トークンの中に何をたせるのか? 下から4行目 OAuth のトークンは JWT(JASON Web Token)や……
見出し トークンの中に何をたせるのか? 下から4行目 OAuth のトークンは JWT(JSON Web Token)や……

内容を更新しました(2023/02/01).

※リフローEPUBの場合、「5.3.2 認可付与のリクエストに関する処理」のコラムの見出しと本文の2番目の段落が該当箇所になります。
5刷 1刷 2022.10.20
115
2番目のコードブロック
nosql.one(function(token) {
  if (token.refresh_token == req.body.refresh_token) {
    return token;
  }
}, function(err, token) {
  if (token) {
    対象のリフレッシュ・トークンが存在する場合の処理を行う
  } else {
    res.status(400).json({error: 'invalid_grant'});
    return;
  }
});
nosql.one().make(function(builder) {
  builder.where('refresh_token', req.body.refresh_token);
  builder.callback(function(err, token) {
    if (token) {
      対象のリフレッシュ・トークンが存在する場合の処理を行う
    } else {
      res.status(400).json({error: 'invalid_grant'});
      return;
    }
  });
});

リフローEPUBの場合、5.4節の4つ目のコード枠が該当箇所になります。
6刷 1刷 2024.09.04
115
3番目のコードブロック
if (token.client_id != clientId) {
  nosql.remove(function(found) {
    return (found == token);
  }, function () {} );
  res.status(400).json({error: 'invalid_grant'});
  return;
}
if (token.client_id != clientId) {
  nosql.remove().make(function(builder) {
    builder.where('refresh_token', req.body.refresh_token);
  });
  res.status(400).json({error: 'invalid_grant'});
  return;
}

リフローEPUBの場合、5.4節の5つ目のコード枠が該当箇所になります。
6刷 1刷 2024.09.04
130
ページ下部のコード
nosql.insert({ access_token: access_token, client_id: client.clientId, ……
nosql.insert({ access_token: access_token, client_id: client.client_id, ……

※リフローEPUBの場合、「6.1.1 インプリシット付与方式」の6番目のコードが該当箇所になります。
5刷 1刷 2022.10.26
135
上のコード
nosql.insert({
   access_token: access_token,
   client_id: client.clientId,
   scope: rscope
});
nosql.insert({
   access_token: access_token,
   client_id: clientId,
   scope: rscope
});

※リフローEPUBの場合「6.1.2 クライアント・クレデンシャルによる付与方式」の2番目のコラム「スコープと付与方式(Grant Type)のすぐ上にあるコードが該当箇所になります。
5刷 1刷 2022.11.01
139
1つ目のコード内、最下行
grant_type=password}&scope=foo%20bar&username=alice&password=secret
grant_type=password&scope=foo%20bar&username=alice&password=secret

不要な } を削除しました。

※リフローEPUBの場合は、「6.1.3 リソース所有者のクレデンシャルによる付与方式」の1番目のコードが該当します。
2刷 1刷 2019.02.19
140
2つ目のコード、上から3行目
if (user)
if (!user) {

※リフローEPUBの場合は、「6.1.3 リソース所有者のクレデンシャルによる付与方式」の5番目のコードが該当します。
3刷 1刷 2019.05.07
140
3番目のコード、上から2行目
if (user.password = password)
if (user.password != password) {

!=の間違い。
※リフローEPUBの場合は、「6.1.3 リソース所有者のクレデンシャルによる付与方式」の6番目のコードが該当します。
3刷 1刷 2019.05.07
141
上から2つ目のコード
nosql.insert({
   access_token: access_token,
   client_id: client.clientId,
   scope: rscope
});
nosql.insert({
   refresh_token: refresh_token,
   client_id: client.clientId,
   scope: rscope
});
nosql.insert({
   access_token: access_token,
   client_id: clientId,
   scope: rscope
});
nosql.insert({
   refresh_token: refresh_token,
   client_id: clientId,
   scope: rscope
});

※リフローEPUBでは、「6.1.3 リソース所有者のクレデンシャルによる付与方式」の図6.4の上にあるコードが該当箇所になります。
5刷 1刷 2022.11.01
142
本文上から1行目
クトを生成しています。
成しています。

※リフローEPUBの場合、「6.1.3 リソース所有者のクレデンシャルによる付与方式」の8番目のコードの直後の段落が該当します。
3刷 1刷 2019.05.07
151
コード内、下から6行目
if (callbackData.state == localStorage.getItem('oauth-state'))
if (callbackData.state !== localStorage.getItem('oauth-state')) {

※リフローEPUBの場合、「6.2.2 ブラウザ内アプリケーション」の4番目のコードが該当します。
3刷 1刷 2019.05.07
172
3番目のコードブロック
function handleAuthorizationRequestClick(ev) {
  if (!client.client_id) {
    $.ajax({
      url: authServer.registrationEndpoint,
      type: 'POST',
      data: client,
      crossDomain: true,
      dataType: ’json’
    }).done(function(data) {
      client.client_id = data.client_id;
      client.client_secret = data.client_secret;
    }).fail(function() {
      $('.oauth-protected-resource')
      .text('Error while fetching registration endpoint');
    });

    } else { …略
var protectedResource = 'http://localhost:9002/resource';

window.onload = function() {
   if (!client.client_id) {
    $.ajax({
      url: authServer.registrationEndpoint,
      type: 'POST',
      data: client,
      crossDomain: true,
      dataType: 'json'
    }).done(function(data) {
      client.client_id = data.client_id;
      client.client_secret = data.client_secret;
    }).fail(function() {
      $('.oauth-protected-resource')
          .text('Error while fetching registration endpoint');
    });
  }
}

リフローEPUBの場合、7.3節の4つ目のコード枠が該当箇所になります。
6刷 1刷 2024.09.04
238
(前ページから続く)1番目のコードブロック、1行目
var form_data = qs.stringify({
  grant_type: 'authorization_code',
  code: code,
  redirect_uri: client.redirect_uris[0],
  code_veryfier: code_veryfier
});
var form_data = qs.stringify({
  grant_type: 'authorization_code',
  code: code,
  redirect_uri: client.redirect_uri,
  code_verifier: code_verifier
});

リフローEPUBの場合、10.4.1項の2つ目のコード枠が該当箇所になります。
6刷 1刷 2024.09.04
257
コードブロック
// 先ほどまでのすべての整合性チェックはこのif文内で行われる
if (jose.jws.JWS.verify(
  inToken,
  new Buffer(sharedTokenSecret).toString(’hex’),
  [header.alg])) {
…略
}
// 先ほどまでのすべての整合性チェックはこのif文内で行われる
if (jose.jws.JWS.verify(
    inToken,
    Buffer.from(sharedTokenSecret).toString('hex'),
    [header.alg])) {
  …略
}

リフローEPUBの場合、11.3.1項の最後のコードブロックが該当箇所になります。
6刷 1刷 2024.09.04
268
コードブロック
var inToken = req.body.token;
nosql.one(function(token) {
  if (token.access_token == inToken) {
    return token;
  }
}, function(err, token) {
  if (token) {
    var introspectionResponse = {
      active: true,
      iss: 'http://localhost:9001/',
      aud: 'http://localhost:9002/',
      sub: token.user ? token.user.sub : undefined,
      username: token.user ? token.user.preferred_username : undefined,
      scope: token.scope ? token.scope.join(’ ’) : undefined,
      client_id: token.client_id
    };
    res.status(200).json(introspectionResponse);
    return;
  } else {
    var introspectionResponse = {
      active: false
    };
    res.status(200).json(introspectionResponse);
    return;
  }
});
var inToken = req.body.token;
console.log('Introspecting token %s', inToken);
nosql.one().make(function(builder) {
  builder.where('access_token', inToken);
  builder.callback(function(err, token) {
    if (token) {
      var introspectionResponse = {
        active: true,
        iss: 'http://localhost:9001/',
        aud: 'http://localhost:9002/',
        sub: token.user ? token.user.sub : undefined,
        username: token.user ? token.user.preferred_username : undefined,
        scope: token.scope ? token.scope.join(' ') : undefined,
        client_id: token.client_id
      };
      res.status(200).json(introspectionResponse);
      return;
    } else {
      var introspectionResponse = {
        active: false
      };
      res.status(200).json(introspectionResponse);
      return;
    }
  });
});

リフローEPUBの場合、11.4.2の最後のコード枠が該当箇所になります。
6刷 1刷 2024.09.04
274,275
ページにまたがっているコードブロック
var inToken = req.body.token;
nosql.remove(function(token) {
  if (token.access_token == inToken &&
      token.client_id == clientId) {
    return true;
  }
}, function(err, count) {
  res.status(204).end();
  return;
});
var inToken = req.body.token;
nosql.remove().make(function(builder) {
  builder.and();
  builder.where('access_token', inToken);
  builder.where('client_id', clientId);
  builder.callback(function(err, count) {
    console.log("Removed %s tokens", count);
    res.status(204).end();
    return;
  });
});

リフローEPUBの場合、11.5.2の2つ目のコード枠が該当箇所になります。
6刷 1刷 2024.09.04
313
3番目のコードブロック
nosql.remove(function(token) {
  if (token.client_id == req.client.client_id) {
    return true;
  }
}, function(err, count) {
   console.log("Removed %s clients", count);
});

res.status(  204).end();
return;
nosql.remove().make(function(builder) {
  builder.where('client_id', clientId);
  builder.callback(function(err, count) {
    console.log("Removed %s tokens", count);
  });
});

res.status(204).end();
return;

リフローEPUBの場合、図12.4の上にあるコード枠が該当箇所になります。
6刷 1刷 2024.09.04
324
上から4行目
ここでは認可サーバーとリソース所有者をまとめて、それをひとつの構成要素であるアイデンティティ・プロバイダとして見ていきます。
ここでは認可サーバーと保護対象リソースをまとめて、それをひとつの構成要素であるアイデンティティ・プロバイダとして見ていきます。

※リフローEPUBでは、図13.2のすぐ上の段落が該当箇所になります。
2刷 1刷 2019.02.19
410,411
ページにまたがっているコードブロック
> node -v v4.4.1 > npm -v 2.15.1
> node -v v14.13.1 > npm -v 6.14.8

リフローEPUBの場合、「付録A」の2つ目のコード枠が該当箇所になります。
6刷 1刷 2024.09.04
420,421
リストB.6
var getAccessToken = function(req, res, next) {
  var inToken = null;
  var auth = req.headers[’authorization’];
  if (auth && auth.toLowerCase().indexOf(’bearer’) == 0) {
    inToken = auth.slice(’bearer ’.length);
  } else if (req.body && req.body.access_token) {
    inToken = req.body.access_token;
  } else if (req.query && req.query.access_token) {
    inToken = req.query.access_token
  }

  console.log(’Incoming token: %s’, inToken);

  nosql.one(function(token) {
    if (token.access_token == inToken) {
      return token;
    }
  }, function(err, token) {
    if (token) {
      console.log("We found a matching token: %s", inToken);
    } else {
      console.log(’No matching token was found.’);
    }

    req.access_token = token;
    next();
    return;
  });
};
var getAccessToken = function(req, res, next) {
  var inToken = null;
  var auth = req.headers['authorization'];
  if (auth && auth.toLowerCase().indexOf('bearer') == 0) {
    inToken = auth.slice('bearer '.length);
  } else if (req.body && req.body.access_token) {
    inToken = req.body.access_token;
  } else if (req.query && req.query.access_token) {
    inToken = req.query.access_token
  }

  console.log('Incoming token: %s', inToken);

  nosql.one().make(function(builder) {
    builder.where('access_token', inToken);
    builder.callback(function(err, token) {
      if (token) {
        console.log("We found a matching token: %s", inToken);
      } else {
        console.log('No matching token was found.');
      };
      req.access_token = token;
      next();
      return;
    });
  });
};

リフローEPUBの場合、「付録B」のリストB.6が該当箇所になります。
6刷 1刷 2024.09.04
424,425
リストB.10
} else if (req.body.grant_type == ’refresh_token’) {
  nosql.one(function(token) {
    if (token.refresh_token == req.body.refresh_token) {
      return token;
    }
  }, function(err, token) {
    if (token) {
      console.log("We found a matching refresh token: %s",
          req.body.refresh_token);

      if (token.client_id != clientId) {
        nosql.remove(function(found) {
          return (found == token);
        }, function () {} );

        res.status(400).json({error: ’invalid_grant’});
        return;
      }

      var access_token = randomstring.generate();

      nosql.insert({
        access_token: access_token,
        client_id: clientId
      });

      var token_response = {
        access_token: access_token,
        token_type: ’Bearer’,
        refresh_token: token.refresh_token
      };

      res.status(200).json(token_response);
      return;
    } else {
      console.log(’No matching token was found.’);
      res.status(400).json({error: ’invalid_grant’});
      return;
    }
  });
}
} else if (req.body.grant_type == 'refresh_token') {
  nosql.one().make(function(builder) {
    builder.where('refresh_token', req.body.refresh_token);
    builder.callback(function(err, token) {
      if (token) {
        console.log("We found a matching refresh token: %s",
            req.body.refresh_token);

        if (token.client_id != clientId) {
          nosql.remove().make(function(builder) {
            builder.where('refresh_token', req.body.refresh_token);
          });
          res.status(400).json({error: 'invalid_grant'});
          return;
        }

        var access_token = randomstring.generate();

        nosql.insert({
          access_token: access_token,
          client_id: clientId
        });

        var token_response = {
          access_token: access_token,
          token_type: 'Bearer',
          refresh_token: token.refresh_token
        };

        res.status(200).json(token_response);
        return;
      } else {
        console.log('No matching token was found.');
        res.status(400).json({error: 'invalid_grant'});
        return;
      }
    });
  });
} else if …略

リフローEPUBの場合、「付録B」のリストB.10が該当箇所になります。
6刷 1刷 2024.09.04
425
リストB.11
app.post(’/introspect’, function(req, res) {
  var auth = req.headers[’authorization’];
  var resourceCredentials = decodeClientCredentials(auth);
  var resourceId = resourceCredentials.id;
  var resourceSecret = resourceCredentials.secret;
  var resource = getProtectedResource(resourceId);

  if (!resource) {
    console.log(’Unknown resource %s’, resourceId);
    res.status(401).end();
    return;
  }

  if (resource.resource_secret != resourceSecret) {
    console.log(’Mismatched secret, expected %s got %s’,
        resource.resource_secret, resourceSecret);
    res.status(401).end();
    return;
  }

  var inToken = req.body.token;.

  console.log(’Introspecting token %s’, inToken);

  nosql.one(function(token) {
    if (token.access_token == inToken) {
      return token;
    }
  }, function(err, token) {
    if (token) {
      console.log("We found a matching token: %s", inToken);

      var introspectionResponse = {
        active: true,
        iss: ’http://localhost:9001/’,
        aud: ’http://localhost:9002/’,
        sub: token.user ? token.user.sub : undefined,
        username: token.user ? token.user.preferred_username :
        undefined,
        scope: token.scope ? token.scope.join(’ ’) : undefined,
        client_id: token.client_id
      };

      res.status(200).json(introspectionResponse);
      return;
    } else {
      console.log(’No matching token was found.’);

      var introspectionResponse = {
        active: false
      };

      res.status(200).json(introspectionResponse);
      return;
    }
  });
});
app.post('/introspect', function(req, res) {
  var auth = req.headers['authorization'];
  var resourceCredentials = decodeClientCredentials(auth);
  var resourceId = resourceCredentials.id;
  var resourceSecret = resourceCredentials.secret;
  var resource = getProtectedResource(resourceId);

  if (!resource) {
    console.log('Unknown resource %s', resourceId);
    res.status(401).end();
    return;
  }

  if (resource.resource_secret != resourceSecret) {
    console.log('Mismatched secret, expected %s got %s',
        resource.resource_secret, resourceSecret);
    res.status(401).end();
    return;
  }

  var inToken = req.body.token;

  console.log('Introspecting token %s', inToken);

  nosql.one().make(function(builder) {
    builder.where('access_token', inToken);
    builder.callback(function(err, token) {
      if (token) {
        console.log("We found a matching token: %s", inToken);

        var introspectionResponse = {
          active: true,
          iss: 'http://localhost:9001/',
          aud: 'http://localhost:9002/',
          sub: token.user ? token.user.sub : undefined,
          username: token.user ? token.user.preferred_username : undefined,
          scope: token.scope ? token.scope.join(' ') : undefined,
          client_id: token.client_id
        };

        res.status(200).json(introspectionResponse);
        return;
      } else {
        console.log('No matching token was found.');

        var introspectionResponse = {
          active: false
        };

        res.status(200).json(introspectionResponse);
        return;
      }
    });
  });
});

リフローEPUBの場合、「付録B」のリストB.11が該当箇所になります。
6刷 1刷 2024.09.04
426,427
リストB.12
app.post(’/revoke’, function(req, res) {
  var auth = req.headers[’authorization’];

  if (auth) {
    // Authorizationヘッダーをチェック
    var clientCredentials = decodeClientCredentials(auth);
    var clientId = clientCredentials.id;
    var clientSecret = clientCredentials.secret;
  }

  // ない場合はPOST送信されたボディーをチェック
  if (req.body.client_id) {
    if (clientId) {
      // すでにAuthorizationヘッダーにクライアントのクレデンシャルがある場合はエラー
      console.log(’Client attempted to authenticate with multiple methods’);
      res.status(401).json({error: ’invalid_client’});
      return;
    }

    var clientId = req.body.client_id;
    var clientSecret = req.body.client_secret;
  }

  var client = getClient(clientId);
  if (!client) {
    console.log(’Unknown client %s’, clientId);
    res.status(401).json({error: ’invalid_client’});
    return;
  }

  if (client.client_secret != clientSecret) {
    console.log(’Mismatched client secret, expected %s got %s’,
        client.client_secret, clientSecret);
    res.status(401).json({error: ’invalid_client’});
    return;
  }

  var inToken = req.body.token;

  nosql.remove(function(token) {
    if (token.access_token == inToken && token.client_id == clientId) {
      return true;
    }
  }, function(err, count) {
    console.log("Removed %s tokens", count);
    res.status(204).end();
    return;
  });
});
app.post('/revoke', function(req, res) {
  var auth = req.headers['authorization'];
  if (auth) {
    // Authorizationヘッダーをチェック
    var clientCredentials = decodeClientCredentials(auth);
    var clientId = clientCredentials.id;
    var clientSecret = clientCredentials.secret;
  }

  // ない場合はPOST送信されたボディーをチェック
  if (req.body.client_id) {
    if (clientId) {
      // すでにAuthorizationヘッダーにクライアントのクレデンシャルがある場合はエラー
      console.log('Client attempted to authenticate with multiplemethods');
      res.status(401).json({error: 'invalid_client'});
      return;
    }

    var clientId = req.body.client_id;
    var clientSecret = req.body.client_secret;
  }

  var client = getClient(clientId);
  if (!client) {
    console.log('Unknown client %s', clientId);
    res.status(401).json({error: 'invalid_client'});
    return;
  }

  if (client.client_secret != clientSecret) {
    console.log('Mismatched client secret, expected %s got %s',
        client.client_secret, clientSecret);
    res.status(401).json({error: 'invalid_client'});
    return;
  }

  var inToken = req.body.token;
  nosql.remove().make(function(builder) {
    builder.and();
    builder.where('access_token', inToken);
    builder.where('client_id', clientId);
    builder.callback(function(err, count) {
      console.log("Removed %s tokens", count);
      res.status(204).end();
      return;
    });
  });
});

リフローEPUBの場合、「付録B」のリストB.12が該当箇所になります。
6刷 1刷 2024.09.04

感想・レビュー

fakiyer さん

2019-03-30

OAuth2.0に関してサンプルコードとともにとても詳しく書かれている。 OpenID Connectについての説明もとても参考になった。 セキュリティに関しても詳しく書かれているので、OAuth構築する際は読んでおきたい本。

james さん

2020-05-23

OAuthを使うに当たってweb上の記事を読んでも用語の意味が分からず苦労したので体系的に理解したくて読んだ。OAuth2.0をきちんと使うなら読んでおくべき一冊だと思う。著者はOAuthに仕様検討段階から関わっているとのことで、なぜOAuth2.0が必要となったのかといった背景も含めて説明してくれる。中盤からはサンプルコードにプログラムを書き込んでいきながら進めるハンズオンでこれも理解を助けてくれた。node.jsは12ではエラーがでたので、8にバージョンを落としたら動作した。

kaseken さん

2020-05-10

技術書1000冊読破計画/17冊目