Я использую клиент Elixir OAuth2 и использую Образец аутентификации Elixir Google Cloud Platform в качестве общей базы.
Код выглядит примерно так:
mix.exs
...
def application do
[
mod: {ExampleApp.Application, []},
extra_applications: [:logger, :runtime_tools, :oauth2]
]
end
...
...
defp deps do
...
{:oauth2, "~> 2.0"}
...
...
Создание файла env
- Создать файл .env: nano .env
- Добавить .env в .gitignore: echo .env >> .gitignore env
#!/bin/bash
export GOOGLE_CLIENT_ID="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com"
export GOOGLE_CLIENT_SECRET="xxxxxxxxxxxxxxxxxxxxxxxx"
export GOOGLE_REDIRECT_URI="http://localhost:4000/auth/callback"
source ./.env
OAuth Модель Google
lib / example_app_web / oauth / google.ex
defmodule Google do
use OAuth2.Strategy
# Public API
def client do
OAuth2.Client.new([
strategy: __MODULE__,
client_id: System.get_env("GOOGLE_CLIENT_ID"),
client_secret: System.get_env("GOOGLE_CLIENT_SECRET"),
redirect_uri: System.get_env("GOOGLE_REDIRECT_URI"),
site: "https://accounts.google.com",
authorize_url: "https://accounts.google.com/o/oauth2/auth",
token_url: "https://oauth2.googleapis.com/token"
])
end
def authorize_url!(params \\ []) do
OAuth2.Client.authorize_url!(client(), params)
end
# you can pass options to the underlying http library via `opts` parameter
def get_token!(params \\ [], headers \\ [], opts \\ []) do
OAuth2.Client.get_token!(client(), params, headers, opts)
end
# Strategy Callbacks
def authorize_url(client, params) do
OAuth2.Strategy.AuthCode.authorize_url(client, params)
end
def get_token(client, params, headers) do
client
|> put_param(:client_secret, client.client_secret)
|> put_header("accept", "application/json")
|> OAuth2.Strategy.AuthCode.get_token(params, headers)
end
end
контроллер авторизации
lib / example_app_web / controllers / auth_controller.ex
defmodule ExampleApp.AuthController do
use ExampleAppWeb, :controller
@doc """
This action is reached via `/auth/callback` is the the callback URL that
Google's OAuth2 provider will redirect the user back to with a `code` that will
be used to request an access token. The access token will then be used to
access protected resources on behalf of the user.
"""
def callback(conn, %{"code" => code}) do
# Exchange an auth code for an access token
client = Google.get_token!(code: code)
# Request the user's data with the access token
scope = "https://www.googleapis.com/plus/v1/people/me/openIdConnect"
%{body: user} = OAuth2.Client.get!(client, scope)
current_user = %{
name: user["name"],
avatar: String.replace_suffix(user["picture"], "?sz=50", "?sz=400")
}
# Store the user in the session under `:current_user` and redirect to /.
# In most cases, we'd probably just store the user's ID that can be used
# to fetch from the database. In this case, since this example app has no
# database, I'm just storing the user map.
#
# If you need to make additional resource requests, you may want to store
# the access token as well.
conn
|> put_session(:current_user, current_user)
|> put_session(:access_token, client.token.access_token)
|> redirect(to: "/")
end
@doc """
This action is reached via `/auth` and redirects to the Google OAuth2 provider.
"""
def index(conn, _params) do
redirect conn, external: Google.authorize_url!(
scope: "https://www.googleapis.com/auth/userinfo.email"
)
end
def delete(conn, _params) do
conn
|> put_flash(:info, "You have been logged out!")
|> configure_session(drop: true)
|> redirect(to: "/")
end
end
router.ex
defmodule ExampleApp.Router do
use ExampleApp.Web, :router
pipeline :browser do
...
plug :assign_current_user
...
end
...
scope "/auth", ExampleApp do
pipe_through :browser
get "/", AuthController, :index
get "/callback", AuthController, :callback
delete "/logout", AuthController, :delete
end
...
# Fetch the current user from the session and add it to `conn.assigns`. This
# will allow you to have access to the current user in your views with
# `@current_user`.
defp assign_current_user(conn, _) do
assign(conn, :current_user, get_session(conn, :current_user))
end
end
Добавить кнопку в шаблон
lib / example_app_web / templates / page / index.html.eex
<section class="phx-hero">
<%= if @current_user do %>
<h2>Welcome, <%= @current_user.name %>!</h2>
<p><img src="<%= @current_user.avatar %>" class="img-circle"/></p>
<p><%= link "Logout", to: Routes.auth_path(@conn, :delete), method: :delete, class: "btn btn-danger" %></p>
<% else %>
<br>
<br>
<a class="btn btn-primary btn-lg" href="<%= Routes.auth_path @conn, :index %>">
<i class="fa fa-google"></i>
Sign in with Google
</a>
<% end %>
</section>
При отправке я получаю сообщение об ошибке "Неверные учетные данные", похожее на это SO
Ошибка в консоли выглядит следующим образом:
[debug] Processing with ExampleApp.AuthController.callback/2
Parameters: %{"authuser" => "0", "code" => "4/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "prompt" => "consent", "scope" => "email https://www.googleapis.com/auth/userinfo.email openid", "session_state" => "920caea4175b647de7feea0fc7cf230fcdb45a22..0f99"}
Pipelines: [:browser]
[info] Sent 500 in 399ms
[error] #PID<0.3262.0> running ExampleAppWeb.Endpoint (connection #PID<0.3261.0>, stream id 1) terminated
Server: localhost:4000 (http)
Request: GET /auth/callback?code=4%2FblahblaheblahblahCWoINhOouSLIvt38H4Hoy5p5343r5B6HC4qVadotDjobRcXKDs&scope=email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+openid&authuser=0&session_state=920caea4175b647de7feea0fc7cf230fcdb45a22..0f99&prompt=consent
** (exit) an exception was raised:
** (OAuth2.Error) Server responded with status: 401
Headers:
vary: X-Origin
www-authenticate: Bearer realm="https://accounts.google.com/", error=invalid_token
content-type: application/json; charset=UTF-8
date: Thu, 19 Sep 2019 06:15:21 GMT
expires: Thu, 19 Sep 2019 06:15:21 GMT
cache-control: private, max-age=0
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
x-xss-protection: 1; mode=block
server: GSE
accept-ranges: none
vary: Origin,Accept-Encoding
transfer-encoding: chunked
alt-svc: quic=":443"; ma=2592000; v="46,43,39"
Body:
"{\n \"error\": {\n \"errors\": [\n {\n \"domain\": \"global\",\n \"reason\": \"authError\",\n \"message\": \"Invalid Credentials\",\n \"locationType\": \"header\",\n \"location\": \"Authorization\"\n }\n ],\n \"code\": 401,\n \"message\": \"Invalid Credentials\"\n }\n}\n"
(oauth2) lib/oauth2/request.ex:65: OAuth2.Request.request!/6
(example_app) lib/example_app_web/controllers/auth_controller.ex:25: ExampleApp.AuthController.callback/2
(example_app) lib/example_app_web/controllers/auth_controller.ex:1: ExampleApp.AuthController.action/2
(example_app) lib/example_app_web/controllers/auth_controller.ex:1: ExampleApp.AuthController.phoenix_controller_pipeline/2
(phoenix) lib/phoenix/router.ex:288: Phoenix.Router.__call__/2
(example_app) lib/example_app_web/endpoint.ex:1: ExampleAppWeb.Endpoint.plug_builder_call/2
(example_app) lib/plug/debugger.ex:122: ExampleAppWeb.Endpoint."call (overridable 3)"/2
(example_app) lib/example_app_web/endpoint.ex:1: ExampleAppWeb.Endpoint.call/2
(phoenix) lib/phoenix/endpoint/cowboy2_handler.ex:42: Phoenix.Endpoint.Cowboy2Handler.init/4
(cowboy) /Users/bigbassroller2017/Sites/gapi-elixir/example_app/deps/cowboy/src/cowboy_handler.erl:41: :cowboy_handler.execute/2
(cowboy) /Users/bigbassroller2017/Sites/gapi-elixir/example_app/deps/cowboy/src/cowboy_stream_h.erl:296: :cowboy_stream_h.execute/3
(cowboy) /Users/bigbassroller2017/Sites/gapi-elixir/example_app/deps/cowboy/src/cowboy_stream_h.erl:274: :cowboy_stream_h.request_process/3
(stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
Вот некоторые аспекты ввода-вывода из контроллера аутентификации
"Scope: ======================"
"https://www.googleapis.com/plus/v1/people/me/openIdConnect"
"======================"
"Client: ======================"
%OAuth2.Client{
authorize_url: "https://accounts.google.com/o/oauth2/auth",
client_id: "xxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com",
client_secret: "xxxxxxxxxxxxxxxxxxxxxxxxx",
headers: [],
params: %{},
redirect_uri: "http://localhost:4000/auth/callback",
ref: nil,
request_opts: [],
serializers: %{},
site: "https://accounts.google.com",
strategy: Google,
token: %OAuth2.AccessToken{
access_token: "{\n \"access_token\": \"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\",\n \"expires_in\": 3600,\n \"scope\": \"https://www.googleapis.com/auth/userinfo.email openid\",\n \"token_type\": \"Bearer\",\n \"id_token\": \"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"\n}",
expires_at: nil,
other_params: %{},
refresh_token: nil,
token_type: "Bearer"
},
token_method: :post,
token_url: "https://oauth2.googleapis.com/token"
}
"======================"
"Code: ======================"
"4/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
"======================"
Params Параметры authuser "0" код "4 / xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" приглашение "область" согласия "электронная почта https://www.googleapis.com/auth/userinfo.email openid" session_state "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
для меня это паравечеров.
Есть идеи, что не так?