omniauth-line-oidc 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +54 -0
- data/LICENSE.txt +22 -0
- data/README.md +221 -0
- data/lib/omniauth/line_oidc/version.rb +7 -0
- data/lib/omniauth/strategies/line.rb +227 -0
- data/lib/omniauth-line-oidc.rb +4 -0
- metadata +84 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 8f26151039d5a4335bc74e131925ce65064ab85e8d82597d1944bad71c22b283
|
|
4
|
+
data.tar.gz: bb091bb1368f5efbfceb229914c646b89a2945cb7441f7be00c7815c1efa1490
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 3e63eee133d0243a9369dc39883a7ea09e8aa6eccaac857c03a4c15215bb9863c1ab62f08e0e72f3de60098969ae79f329409c7996d33771a953ed84fa2b24be
|
|
7
|
+
data.tar.gz: bdb9da23b3657c86127991e2e81acd67a301bd006aa2bbda12e2639937a216ede80919351afe3ef3e1f0c5dd305fafc0b8af339b8c47770e2efd76267d06a5a2
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this gem are recorded here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the gem follows
|
|
5
|
+
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [1.0.0] - 2026-11-12
|
|
10
|
+
|
|
11
|
+
First release under the name `omniauth-line-oidc`. The gem continues the
|
|
12
|
+
`omniauth-line` strategy (last released as 0.1.0 in 2021) and keeps its
|
|
13
|
+
provider name, `:line`, so an app can switch by changing its Gemfile.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- A `nonce` is generated for every authorization request that asks for an
|
|
18
|
+
ID token, kept in the session under the request's `state`, and sent to
|
|
19
|
+
LINE's verify endpoint with the ID token on the callback.
|
|
20
|
+
- The sign-in fails with `invalid_credentials` when LINE rejects the ID
|
|
21
|
+
token, for its nonce or anything else.
|
|
22
|
+
- PKCE (`S256`) is on by default. Turn it off with `pkce: false`.
|
|
23
|
+
- `extra[:id_token_claims]`: the ID token's claims as LINE's verify
|
|
24
|
+
endpoint returned them, including `amr`. Present only for a verified
|
|
25
|
+
token.
|
|
26
|
+
- `extra[:raw_info]`: the profile API response.
|
|
27
|
+
- `extra[:friendship_status_changed]`, when LINE reports it on the callback
|
|
28
|
+
(`bot_prompt`).
|
|
29
|
+
- The sign-in fails with `invalid_credentials` when the verified ID token
|
|
30
|
+
names a different user than the profile.
|
|
31
|
+
|
|
32
|
+
### Changed since `omniauth-line` 0.1.0
|
|
33
|
+
|
|
34
|
+
- Token exchange sends the channel secret in the request body, one of the
|
|
35
|
+
two client authentication methods LINE documents.
|
|
36
|
+
- The authorization URL and the API calls use their own hosts
|
|
37
|
+
(`access.line.me` and `api.line.me`) from fixed settings; nothing is
|
|
38
|
+
rewritten per request.
|
|
39
|
+
- `info[:email]` is read from the ID token through LINE's verify endpoint.
|
|
40
|
+
When the endpoint cannot be reached, times out or answers with a server
|
|
41
|
+
error, the email is `nil` and the sign-in carries on.
|
|
42
|
+
- The verify call has timeouts (5 s to open, 10 s to read), configurable
|
|
43
|
+
through `verify_options`.
|
|
44
|
+
- LINE's authorization options are forwarded when set: `prompt`, `max_age`,
|
|
45
|
+
`ui_locales`, `bot_prompt`, `initial_amr_display`, `switch_amr`,
|
|
46
|
+
`disable_auto_login`, `disable_ios_auto_login`. A `nonce` option is
|
|
47
|
+
ignored; the strategy generates one per sign-in.
|
|
48
|
+
- The callback URL override is the `redirect_uri` option. `callback_url`,
|
|
49
|
+
the name `omniauth-line` 0.1.0 used, is still accepted.
|
|
50
|
+
- A profile response that is not JSON, or names no user, fails the sign-in
|
|
51
|
+
with `invalid_credentials`.
|
|
52
|
+
- Requires Ruby 3.1 or newer, `omniauth` 2.3.1 or newer and
|
|
53
|
+
`omniauth-oauth2` 1.10 or newer. The `json` gem is no longer a declared
|
|
54
|
+
dependency.
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2015 kazasiki and the omniauth-line contributors
|
|
4
|
+
Copyright (c) 2026 the omniauth-line-oidc contributors
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in
|
|
14
|
+
all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
22
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# omniauth-line-oidc
|
|
2
|
+
|
|
3
|
+
[](https://github.com/momiji-rs/omniauth-line-oidc/actions/workflows/ci.yml)
|
|
4
|
+
|
|
5
|
+
LINE Login v2.1 (OAuth 2.0 / OpenID Connect) strategy for OmniAuth.
|
|
6
|
+
|
|
7
|
+
See the LINE Login docs for details:
|
|
8
|
+
https://developers.line.biz/en/docs/line-login/integrate-line-login/
|
|
9
|
+
|
|
10
|
+
This gem continues the `omniauth-line` strategy, which has had no release
|
|
11
|
+
since 2021. It registers the same provider name, `:line`.
|
|
12
|
+
|
|
13
|
+
## Using This Strategy
|
|
14
|
+
|
|
15
|
+
First start by adding this gem to your Gemfile:
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
gem 'omniauth-line-oidc'
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Switching from `omniauth-line`
|
|
22
|
+
|
|
23
|
+
Replace the gem in your Gemfile; the provider name, the callback path
|
|
24
|
+
(`/auth/line/callback`) and the auth hash keys are the same. Things to
|
|
25
|
+
check:
|
|
26
|
+
|
|
27
|
+
- PKCE is on by default. It needs nothing from you unless your callback is
|
|
28
|
+
handled without the session that started the sign-in; see
|
|
29
|
+
[Callbacks handled by another session](#callbacks-handled-by-another-session).
|
|
30
|
+
- A sign-in started before the deploy and finished after it has no PKCE
|
|
31
|
+
verifier or nonce in its session, and may be refused by LINE; the user
|
|
32
|
+
signs in again.
|
|
33
|
+
- A sign-in now fails when LINE rejects the ID token. `omniauth-line`
|
|
34
|
+
ignored the rejection and returned no email.
|
|
35
|
+
- `extra` now also carries `raw_info`, and `id_token_claims` when the ID
|
|
36
|
+
token was verified.
|
|
37
|
+
- Do not keep both gems in one bundle: both define
|
|
38
|
+
`OmniAuth::Strategies::Line`. If you require the gem by name, the file
|
|
39
|
+
is `omniauth-line-oidc`.
|
|
40
|
+
- Requires `omniauth-oauth2` 1.10 or newer.
|
|
41
|
+
|
|
42
|
+
See the [changelog](CHANGELOG.md) for everything that differs from
|
|
43
|
+
`omniauth-line` 0.1.0.
|
|
44
|
+
|
|
45
|
+
Next, tell OmniAuth about this provider. For a Rails app, your
|
|
46
|
+
`config/initializers/omniauth.rb` file should look like this:
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
Rails.application.config.middleware.use OmniAuth::Builder do
|
|
50
|
+
provider :line, ENV['LINE_CHANNEL_ID'], ENV['LINE_CHANNEL_SECRET']
|
|
51
|
+
end
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Register your callback URL (e.g. `https://your.app/auth/line/callback`)
|
|
55
|
+
under "LINE Login > Callback URL" in the
|
|
56
|
+
[LINE Developers console](https://developers.line.biz/console/), or the
|
|
57
|
+
authorization request will fail with "Invalid redirect_uri value".
|
|
58
|
+
|
|
59
|
+
### Scopes and email
|
|
60
|
+
|
|
61
|
+
The default scope is `profile openid`. To receive the user's email
|
|
62
|
+
address, apply for the "OpenID Connect email" permission in the LINE
|
|
63
|
+
Developers console and request the `email` scope:
|
|
64
|
+
|
|
65
|
+
```ruby
|
|
66
|
+
provider :line, ENV['LINE_CHANNEL_ID'], ENV['LINE_CHANNEL_SECRET'],
|
|
67
|
+
scope: 'profile openid email'
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
LINE only exposes the email inside the ID token, never through the
|
|
71
|
+
profile API. When the `email` scope is not granted, `info[:email]` is
|
|
72
|
+
`nil`.
|
|
73
|
+
|
|
74
|
+
### Options
|
|
75
|
+
|
|
76
|
+
Besides the standard OmniAuth options, the strategy forwards these to
|
|
77
|
+
LINE's authorization URL when you set them: `prompt`, `max_age`,
|
|
78
|
+
`ui_locales`, `bot_prompt`, `initial_amr_display`, `switch_amr`,
|
|
79
|
+
`disable_auto_login`, `disable_ios_auto_login`. There is no `nonce`
|
|
80
|
+
option: the strategy generates a fresh one for every sign-in.
|
|
81
|
+
|
|
82
|
+
```ruby
|
|
83
|
+
provider :line, ENV['LINE_CHANNEL_ID'], ENV['LINE_CHANNEL_SECRET'],
|
|
84
|
+
bot_prompt: 'normal', ui_locales: 'ja'
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
| Option | Default | Meaning |
|
|
88
|
+
|---|---|---|
|
|
89
|
+
| `scope` | `'profile openid'` | Add `email` once the channel has the permission |
|
|
90
|
+
| `pkce` | `true` | Send a PKCE challenge (`S256`, the only method LINE accepts) |
|
|
91
|
+
| `redirect_uri` | built from the request | The callback URL registered with LINE. `callback_url`, the name `omniauth-line` used, is accepted too |
|
|
92
|
+
| `verify_options` | `{ open_timeout: 5, read_timeout: 10 }` | Timeouts for the ID token verify call, in seconds |
|
|
93
|
+
|
|
94
|
+
`response_mode` is not supported: the strategy reads the callback from
|
|
95
|
+
the query string, which is LINE's default.
|
|
96
|
+
|
|
97
|
+
## How the ID token is used
|
|
98
|
+
|
|
99
|
+
The user is identified by LINE's profile API, called with the access
|
|
100
|
+
token the sign-in was just issued. The ID token adds what the profile
|
|
101
|
+
API does not have, the email above all.
|
|
102
|
+
|
|
103
|
+
- The strategy sends a `nonce` with every authorization request that
|
|
104
|
+
asks for an ID token, and keeps it in the session under the request's
|
|
105
|
+
`state`.
|
|
106
|
+
- On the callback it sends the ID token, the channel ID and that nonce
|
|
107
|
+
to LINE's [verify endpoint](https://developers.line.biz/en/reference/line-login/#verify-id-token).
|
|
108
|
+
LINE checks the signature, issuer, audience, expiry and nonce.
|
|
109
|
+
- If LINE rejects the token (HTTP 400, which is also its answer to a
|
|
110
|
+
wrong nonce), the sign-in fails with `invalid_credentials`.
|
|
111
|
+
- If the verified token names a different user than the profile, the
|
|
112
|
+
sign-in fails with `invalid_credentials`.
|
|
113
|
+
- If LINE cannot be asked (the call times out, the connection fails, or
|
|
114
|
+
LINE answers with a server error), the sign-in continues without the
|
|
115
|
+
claims: `info[:email]` is `nil` and `extra[:id_token_claims]` is
|
|
116
|
+
absent. A warning is logged. Handle a `nil` email as you already must
|
|
117
|
+
for a user who declines the `email` scope.
|
|
118
|
+
|
|
119
|
+
The raw `extra[:id_token]` is passed through as LINE issued it. Do not
|
|
120
|
+
read claims out of it yourself; use `extra[:id_token_claims]`, which is
|
|
121
|
+
only present after verification.
|
|
122
|
+
|
|
123
|
+
The strategy keeps one nonce for each sign-in a browser session has
|
|
124
|
+
under way, up to five, next to what `omniauth-oauth2` stores for state
|
|
125
|
+
and PKCE. Five unfinished sign-ins come to about 2.5 KB of session data,
|
|
126
|
+
which matters if your sessions live in a 4 KB cookie that is already
|
|
127
|
+
well filled.
|
|
128
|
+
|
|
129
|
+
### Callbacks handled by another session
|
|
130
|
+
|
|
131
|
+
The nonce and the PKCE verifier live in the session that started the
|
|
132
|
+
sign-in. If your callback arrives in a different session (for example a
|
|
133
|
+
front end on another domain relays it to your API), the strategy cannot
|
|
134
|
+
find them:
|
|
135
|
+
|
|
136
|
+
- Set `pkce: false`, or the token exchange is sent without a verifier
|
|
137
|
+
and LINE rejects it.
|
|
138
|
+
- The ID token is then verified without a nonce. Everything else about
|
|
139
|
+
it is still checked.
|
|
140
|
+
- You will also need `provider_ignores_state: true`, which gives up the
|
|
141
|
+
CSRF protection `state` provides. Prefer carrying the session across
|
|
142
|
+
if you can.
|
|
143
|
+
|
|
144
|
+
## Authentication Hash
|
|
145
|
+
|
|
146
|
+
An example auth hash available in `request.env['omniauth.auth']`:
|
|
147
|
+
|
|
148
|
+
```ruby
|
|
149
|
+
{
|
|
150
|
+
provider: "line",
|
|
151
|
+
uid: "U1234567890abcdef1234567890abcdef",
|
|
152
|
+
info: {
|
|
153
|
+
name: "yamada tarou",
|
|
154
|
+
image: "https://profile.line-scdn.net/xxxxx",
|
|
155
|
+
description: nil, # the user's status message, may be nil
|
|
156
|
+
email: "user@example.com" # nil unless the email scope is granted
|
|
157
|
+
},
|
|
158
|
+
credentials: {
|
|
159
|
+
token: "a1b2c3d4...", # OAuth 2.0 access token
|
|
160
|
+
refresh_token: "e5f6g7h8...",
|
|
161
|
+
expires_at: 1789266388,
|
|
162
|
+
expires: true
|
|
163
|
+
},
|
|
164
|
+
extra: {
|
|
165
|
+
raw_info: { # the profile API response
|
|
166
|
+
"userId" => "U1234567890abcdef1234567890abcdef",
|
|
167
|
+
"displayName" => "yamada tarou",
|
|
168
|
+
"pictureUrl" => "https://profile.line-scdn.net/xxxxx"
|
|
169
|
+
},
|
|
170
|
+
id_token: "eyJ0eXAiOiJKV1Qi...", # as issued; present with the openid scope
|
|
171
|
+
id_token_claims: { # present only when LINE verified the token
|
|
172
|
+
"iss" => "https://access.line.me",
|
|
173
|
+
"sub" => "U1234567890abcdef1234567890abcdef",
|
|
174
|
+
"aud" => "1234567890",
|
|
175
|
+
"exp" => 1789266388,
|
|
176
|
+
"iat" => 1789262788,
|
|
177
|
+
"nonce" => "0f3c...",
|
|
178
|
+
"amr" => ["linesso"], # how the user authenticated
|
|
179
|
+
"name" => "yamada tarou",
|
|
180
|
+
"email" => "user@example.com"
|
|
181
|
+
},
|
|
182
|
+
friendship_status_changed: true # present only when bot_prompt was requested
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Development
|
|
188
|
+
|
|
189
|
+
```sh
|
|
190
|
+
bundle install
|
|
191
|
+
bundle exec rake # runs the specs; every line of lib/ must be covered
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
The test suite stubs all HTTP with WebMock; no LINE credentials are
|
|
195
|
+
needed. CI runs the suite on Ruby 3.1 through 4.0 and on ruby-head
|
|
196
|
+
(which may fail without failing the build), and once more against the
|
|
197
|
+
oldest dependency versions the gemspec admits:
|
|
198
|
+
|
|
199
|
+
```sh
|
|
200
|
+
BUNDLE_GEMFILE=gemfiles/minimum.gemfile bundle install
|
|
201
|
+
BUNDLE_GEMFILE=gemfiles/minimum.gemfile bundle exec rake
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Releasing
|
|
205
|
+
|
|
206
|
+
Releases are published by the `Release` workflow through RubyGems
|
|
207
|
+
[trusted publishing](https://guides.rubygems.org/trusted-publishing/);
|
|
208
|
+
no API key is stored in the repository. Only 1.0.0, which created the
|
|
209
|
+
gem on RubyGems.org, was pushed by hand.
|
|
210
|
+
|
|
211
|
+
1. Set the version in `lib/omniauth/line_oidc/version.rb` and move the
|
|
212
|
+
changelog's "Unreleased" entries under that version. Merge to `master`.
|
|
213
|
+
2. Tag that commit `v<version>` and push the tag.
|
|
214
|
+
3. The workflow checks that the tag matches the gem's version and that
|
|
215
|
+
the commit is on `master`, runs the specs, then builds and pushes the
|
|
216
|
+
gem.
|
|
217
|
+
|
|
218
|
+
One-time setup on RubyGems.org, by an owner of the gem: a trusted
|
|
219
|
+
publisher for repository `momiji-rs/omniauth-line-oidc`, workflow
|
|
220
|
+
`release.yml`, environment `release`, added on the gem's "Trusted
|
|
221
|
+
publishers" page.
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'omniauth-oauth2'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'net/http'
|
|
6
|
+
require 'openssl'
|
|
7
|
+
require 'securerandom'
|
|
8
|
+
|
|
9
|
+
module OmniAuth
|
|
10
|
+
module Strategies
|
|
11
|
+
# LINE Login v2.1.
|
|
12
|
+
#
|
|
13
|
+
# The user is identified by the profile API, called with the access token
|
|
14
|
+
# this sign-in was just issued. The ID token adds claims the profile API
|
|
15
|
+
# does not have (the email above all). Its claims are used only after
|
|
16
|
+
# LINE's verify endpoint has accepted the token, together with the nonce
|
|
17
|
+
# this strategy sent. A token LINE rejects fails the sign-in; a verify
|
|
18
|
+
# endpoint that cannot be reached leaves the sign-in working without the
|
|
19
|
+
# claims.
|
|
20
|
+
class Line < OmniAuth::Strategies::OAuth2
|
|
21
|
+
VERIFY_URL = 'https://api.line.me/oauth2/v2.1/verify'
|
|
22
|
+
NONCE_SESSION_KEY = 'omniauth.line.nonces'
|
|
23
|
+
# Sign-ins a browser session can have under way at once, each with its
|
|
24
|
+
# own nonce; the oldest is dropped beyond this.
|
|
25
|
+
MAX_STORED_NONCES = 5
|
|
26
|
+
|
|
27
|
+
# Failures that say nothing about the token: LINE was not reached, or
|
|
28
|
+
# did not answer.
|
|
29
|
+
VERIFY_UNREACHABLE = [
|
|
30
|
+
::Timeout::Error, ::SystemCallError, ::SocketError, ::IOError, ::OpenSSL::SSL::SSLError
|
|
31
|
+
].freeze
|
|
32
|
+
|
|
33
|
+
option :name, 'line'
|
|
34
|
+
option :scope, 'profile openid'
|
|
35
|
+
|
|
36
|
+
# LINE supports PKCE with S256 only, which is what omniauth-oauth2 sends.
|
|
37
|
+
# Set `pkce: false` when the callback is handled without the session
|
|
38
|
+
# that started the sign-in, since the verifier lives in that session.
|
|
39
|
+
option :pkce, true
|
|
40
|
+
|
|
41
|
+
# omniauth-oauth2 only forwards options named here to the authorize
|
|
42
|
+
# URL; anything else set on the strategy is silently dropped. `nonce` is
|
|
43
|
+
# absent on purpose: a nonce configured once would be the same for every
|
|
44
|
+
# sign-in, so the strategy always generates its own.
|
|
45
|
+
option :authorize_options, %i[
|
|
46
|
+
scope state prompt max_age ui_locales bot_prompt
|
|
47
|
+
initial_amr_display switch_amr disable_auto_login
|
|
48
|
+
disable_ios_auto_login
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
# The verify call runs inside the user-facing callback request, so a
|
|
52
|
+
# slow LINE API must fail fast rather than hold the login hostage.
|
|
53
|
+
DEFAULT_VERIFY_TIMEOUTS = { open_timeout: 5, read_timeout: 10 }.freeze
|
|
54
|
+
option :verify_options, DEFAULT_VERIFY_TIMEOUTS.dup
|
|
55
|
+
|
|
56
|
+
# Authorization lives on access.line.me while every API call
|
|
57
|
+
# (token exchange, profile, verify) lives on api.line.me.
|
|
58
|
+
option :client_options, {
|
|
59
|
+
site: 'https://api.line.me',
|
|
60
|
+
authorize_url: 'https://access.line.me/oauth2/v2.1/authorize',
|
|
61
|
+
token_url: '/oauth2/v2.1/token',
|
|
62
|
+
auth_scheme: :request_body
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
uid do
|
|
66
|
+
ensure_id_token_is_for_this_user!
|
|
67
|
+
raw_info['userId']
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
info do
|
|
71
|
+
{
|
|
72
|
+
name: raw_info['displayName'],
|
|
73
|
+
image: raw_info['pictureUrl'],
|
|
74
|
+
description: raw_info['statusMessage'],
|
|
75
|
+
email: email
|
|
76
|
+
}
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
extra do
|
|
80
|
+
hash = { raw_info: raw_info }
|
|
81
|
+
hash[:id_token] = id_token if id_token
|
|
82
|
+
hash[:id_token_claims] = id_token_claims unless id_token_claims.empty?
|
|
83
|
+
hash[:friendship_status_changed] = friendship_status_changed unless friendship_status_changed.nil?
|
|
84
|
+
hash
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# A nonce ties the ID token to this sign-in: it is sent with the
|
|
88
|
+
# authorization request, LINE copies it into the ID token, and the
|
|
89
|
+
# verify call later states which nonce the token must carry.
|
|
90
|
+
def authorize_params
|
|
91
|
+
super.tap do |params|
|
|
92
|
+
next unless openid_scope?(params[:scope])
|
|
93
|
+
|
|
94
|
+
params[:nonce] = SecureRandom.hex(16)
|
|
95
|
+
remember_nonce(params[:state], params[:nonce])
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# The nonce is taken before anything can fail, so a callback that is
|
|
100
|
+
# refused or carries an error leaves none behind.
|
|
101
|
+
def callback_phase
|
|
102
|
+
@nonce = take_nonce
|
|
103
|
+
super
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# LINE returns the email claim only inside the ID token (email scope),
|
|
107
|
+
# never from the profile endpoint.
|
|
108
|
+
def email
|
|
109
|
+
id_token_claims['email']
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# The ID token's claims as LINE's verify endpoint returned them, or an
|
|
113
|
+
# empty hash when there is no ID token or LINE could not be asked.
|
|
114
|
+
def id_token_claims
|
|
115
|
+
@id_token_claims ||= id_token ? verified_claims : {}
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Require: Access token with PROFILE permission issued.
|
|
119
|
+
def raw_info
|
|
120
|
+
@raw_info ||= fetch_profile
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# `callback_url` is the option name omniauth-line 0.1.0 used.
|
|
124
|
+
def callback_url
|
|
125
|
+
options[:redirect_uri] || options[:callback_url] || (full_host + script_name + callback_path)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
private
|
|
129
|
+
|
|
130
|
+
def id_token
|
|
131
|
+
access_token['id_token']
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Set by LINE on the callback only when `bot_prompt` was requested.
|
|
135
|
+
def friendship_status_changed
|
|
136
|
+
value = request.params['friendship_status_changed']
|
|
137
|
+
value.nil? ? nil : value.to_s == 'true'
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def openid_scope?(scope)
|
|
141
|
+
scope.to_s.split(/[\s,]+/).include?('openid')
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def fetch_profile
|
|
145
|
+
profile = parse_json(access_token.get('v2/profile').body)
|
|
146
|
+
return profile if profile.is_a?(Hash) && profile['userId']
|
|
147
|
+
|
|
148
|
+
raise CallbackError.new(:invalid_credentials, 'the profile response names no user')
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# The profile comes from the access token and the claims from the ID
|
|
152
|
+
# token; both were issued by the same token response, so they name the
|
|
153
|
+
# same user unless something is badly wrong.
|
|
154
|
+
def ensure_id_token_is_for_this_user!
|
|
155
|
+
subject = id_token_claims['sub']
|
|
156
|
+
return if subject.nil? || subject == raw_info['userId']
|
|
157
|
+
|
|
158
|
+
raise CallbackError.new(:invalid_credentials, 'the ID token and the profile name different users')
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def verified_claims
|
|
162
|
+
response = verify_id_token
|
|
163
|
+
payload = parse_json(response.body)
|
|
164
|
+
|
|
165
|
+
if response.is_a?(Net::HTTPSuccess)
|
|
166
|
+
return payload if payload.is_a?(Hash) && payload['sub']
|
|
167
|
+
|
|
168
|
+
log :warn, 'ID token verify returned no claims'
|
|
169
|
+
elsif response.is_a?(Net::HTTPBadRequest)
|
|
170
|
+
# LINE's answer for a token that fails a check, the nonce included.
|
|
171
|
+
description = payload['error_description'] || payload['error'] if payload.is_a?(Hash)
|
|
172
|
+
raise CallbackError.new(:invalid_credentials, "LINE rejected the ID token: #{description}")
|
|
173
|
+
else
|
|
174
|
+
log :warn, "ID token verify unavailable (HTTP #{response.code})"
|
|
175
|
+
end
|
|
176
|
+
{}
|
|
177
|
+
rescue *VERIFY_UNREACHABLE => e
|
|
178
|
+
log :warn, "ID token verify could not be reached (#{e.class})"
|
|
179
|
+
{}
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def verify_id_token
|
|
183
|
+
uri = URI(VERIFY_URL)
|
|
184
|
+
request = Net::HTTP::Post.new(uri)
|
|
185
|
+
form = { id_token: id_token, client_id: client.id }
|
|
186
|
+
form[:nonce] = @nonce if @nonce
|
|
187
|
+
request.set_form_data(form)
|
|
188
|
+
timeouts = options.verify_options || {}
|
|
189
|
+
Net::HTTP.start(uri.host, uri.port,
|
|
190
|
+
use_ssl: true,
|
|
191
|
+
open_timeout: timeouts[:open_timeout] || DEFAULT_VERIFY_TIMEOUTS[:open_timeout],
|
|
192
|
+
read_timeout: timeouts[:read_timeout] || DEFAULT_VERIFY_TIMEOUTS[:read_timeout]) do |http|
|
|
193
|
+
http.request(request)
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def parse_json(body)
|
|
198
|
+
JSON.parse(body.to_s)
|
|
199
|
+
rescue JSON::ParserError
|
|
200
|
+
nil
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Nonces are kept per `state`, so two sign-ins started in one browser
|
|
204
|
+
# session each verify against their own.
|
|
205
|
+
def remember_nonce(state, nonce)
|
|
206
|
+
nonces = stored_nonces
|
|
207
|
+
nonces.delete(state.to_s)
|
|
208
|
+
nonces[state.to_s] = nonce.to_s
|
|
209
|
+
session[NONCE_SESSION_KEY] = nonces.to_a.last(MAX_STORED_NONCES).to_h
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# nil when this callback's session did not start the sign-in; the
|
|
213
|
+
# verify call then checks everything about the token except the nonce.
|
|
214
|
+
def take_nonce
|
|
215
|
+
nonces = stored_nonces
|
|
216
|
+
nonce = nonces.delete(request.params['state'].to_s)
|
|
217
|
+
session[NONCE_SESSION_KEY] = nonces if nonce
|
|
218
|
+
nonce
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def stored_nonces
|
|
222
|
+
nonces = session[NONCE_SESSION_KEY]
|
|
223
|
+
nonces.is_a?(Hash) ? nonces.dup : {}
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: omniauth-line-oidc
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- kazasiki
|
|
8
|
+
- omniauth-line-oidc contributors
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: omniauth
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '2.3'
|
|
20
|
+
- - ">="
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: 2.3.1
|
|
23
|
+
type: :runtime
|
|
24
|
+
prerelease: false
|
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
26
|
+
requirements:
|
|
27
|
+
- - "~>"
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: '2.3'
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: 2.3.1
|
|
33
|
+
- !ruby/object:Gem::Dependency
|
|
34
|
+
name: omniauth-oauth2
|
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '1.10'
|
|
40
|
+
type: :runtime
|
|
41
|
+
prerelease: false
|
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '1.10'
|
|
47
|
+
description: 'Sign in with LINE through OmniAuth: LINE Login v2.1 with OpenID Connect,
|
|
48
|
+
including the email claim and LINE-specific authorization options.'
|
|
49
|
+
executables: []
|
|
50
|
+
extensions: []
|
|
51
|
+
extra_rdoc_files: []
|
|
52
|
+
files:
|
|
53
|
+
- CHANGELOG.md
|
|
54
|
+
- LICENSE.txt
|
|
55
|
+
- README.md
|
|
56
|
+
- lib/omniauth-line-oidc.rb
|
|
57
|
+
- lib/omniauth/line_oidc/version.rb
|
|
58
|
+
- lib/omniauth/strategies/line.rb
|
|
59
|
+
homepage: https://github.com/momiji-rs/omniauth-line-oidc
|
|
60
|
+
licenses:
|
|
61
|
+
- MIT
|
|
62
|
+
metadata:
|
|
63
|
+
source_code_uri: https://github.com/momiji-rs/omniauth-line-oidc/tree/master
|
|
64
|
+
changelog_uri: https://github.com/momiji-rs/omniauth-line-oidc/blob/master/CHANGELOG.md
|
|
65
|
+
bug_tracker_uri: https://github.com/momiji-rs/omniauth-line-oidc/issues
|
|
66
|
+
rubygems_mfa_required: 'true'
|
|
67
|
+
rdoc_options: []
|
|
68
|
+
require_paths:
|
|
69
|
+
- lib
|
|
70
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '3.1'
|
|
75
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
76
|
+
requirements:
|
|
77
|
+
- - ">="
|
|
78
|
+
- !ruby/object:Gem::Version
|
|
79
|
+
version: '0'
|
|
80
|
+
requirements: []
|
|
81
|
+
rubygems_version: 3.6.9
|
|
82
|
+
specification_version: 4
|
|
83
|
+
summary: OmniAuth strategy for LINE Login v2.1 (OpenID Connect)
|
|
84
|
+
test_files: []
|