From c3d6d564348d52c8b51be712b2b0a5aeb690e83a Mon Sep 17 00:00:00 2001 From: 42LinesOfCode <18609393+42LinesOfCode@users.noreply.github.com> Date: Mon, 25 Aug 2025 12:44:15 +0000 Subject: [PATCH] extend scope compatability --- src/oidcc_token.erl | 4 ++ test/oidcc/token_test.exs | 116 +++++++++++++++++++------------------- 2 files changed, 63 insertions(+), 57 deletions(-) diff --git a/src/oidcc_token.erl b/src/oidcc_token.erl index d743c13..dc958f3 100644 --- a/src/oidcc_token.erl +++ b/src/oidcc_token.erl @@ -741,6 +741,10 @@ extract_scope(TokenMap, Opts) -> case maps:get(<<"scope">>, TokenMap, oidcc_scope:scopes_to_bin(Scopes)) of ScopeBinary when is_binary(ScopeBinary) -> {ok, oidcc_scope:parse(ScopeBinary)}; + %% Some providers (e.g. Apple and Twitch) are setting the scope value + %% as list of string. This extends compatibility for those. + ScopeList when is_list(ScopeList) -> + {ok, ScopeList}; ScopeOther -> {error, {invalid_property, {scope, ScopeOther}}} end. diff --git a/test/oidcc/token_test.exs b/test/oidcc/token_test.exs index ec30170..e612065 100644 --- a/test/oidcc/token_test.exs +++ b/test/oidcc/token_test.exs @@ -50,65 +50,67 @@ defmodule Oidcc.TokenTest do end describe inspect(&Token.retrieve/3) do - test_with_mock "works", %{}, :oidcc_http_util, [], - request: fn :post, - {"https://my.provider/token", _headers, ~c"application/x-www-form-urlencoded", - _body}, - _telemetry_opts, - _http_opts -> - {_jws, token} = - @example_jwks - |> JOSE.JWT.sign( - %{"alg" => "RS256"}, - JOSE.JWT.from(%{ - "iss" => "https://my.provider", - "sub" => "sub", - "aud" => "client_id", - "iat" => :erlang.system_time(:second), - "exp" => :erlang.system_time(:second) + 10 - }) - ) - |> JOSE.JWS.compact() + for scope <- ["profile openid", ["profile", "openid"]] do + test_with_mock "works with scope: #{inspect(scope)}", %{}, :oidcc_http_util, [], + request: fn :post, + {"https://my.provider/token", _headers, ~c"application/x-www-form-urlencoded", + _body}, + _telemetry_opts, + _http_opts -> + {_jws, token} = + @example_jwks + |> JOSE.JWT.sign( + %{"alg" => "RS256"}, + JOSE.JWT.from(%{ + "iss" => "https://my.provider", + "sub" => "sub", + "aud" => "client_id", + "iat" => :erlang.system_time(:second), + "exp" => :erlang.system_time(:second) + 10 + }) + ) + |> JOSE.JWS.compact() - {:ok, - {{:json, - %{ - "access_token" => "access_token", - "token_type" => "Bearer", - "id_token" => token, - "scope" => "profile openid", - "refresh_token" => "refresh_token" - }}, []}} - end do - client_context = - ClientContext.from_manual( - @example_metadata, - @example_jwks, - "client_id", - "client_secret" - ) + {:ok, + {{:json, + %{ + "access_token" => "access_token", + "token_type" => "Bearer", + "id_token" => token, + "scope" => unquote(scope), + "refresh_token" => "refresh_token" + }}, []}} + end do + client_context = + ClientContext.from_manual( + @example_metadata, + @example_jwks, + "client_id", + "client_secret" + ) - assert {:ok, - %Token{ - id: %Token.Id{ - token: _token, - claims: %{ - "aud" => "client_id", - "exp" => _exp, - "iat" => _iat, - "iss" => "https://my.provider", - "sub" => "sub" - } - }, - access: %Token.Access{token: "access_token", expires: :undefined}, - refresh: %Token.Refresh{token: "refresh_token"}, - scope: ["profile", "openid"] - }} = - Token.retrieve( - "auth_code", - client_context, - %{redirect_uri: "https://my.server/return"} - ) + assert {:ok, + %Token{ + id: %Token.Id{ + token: _token, + claims: %{ + "aud" => "client_id", + "exp" => _exp, + "iat" => _iat, + "iss" => "https://my.provider", + "sub" => "sub" + } + }, + access: %Token.Access{token: "access_token", expires: :undefined}, + refresh: %Token.Refresh{token: "refresh_token"}, + scope: ["profile", "openid"] + }} = + Token.retrieve( + "auth_code", + client_context, + %{redirect_uri: "https://my.server/return"} + ) + end end end