I'm trying to set up an external IdP using OpenID Connect, with my own login form.
It works quite well, except for the last step. The authorization code I received in the callback does not seem to be useful as an exchange for an access token.
Setup
- Identity Provider: ory.sh (Ory Hydra)
- FusionAuth version: 1.30.1
Configuration
Settings, the Identity Provider:
- Scopes: email name openid offline offline_access
- Redirect URIs: https://xxx.fusionauth.io/oauth2/callback
- Supported OAuth2 flows: client_credentials, authorization_code, refresh_token
- Response types: id_token, token, code
- Authentication method: client_secret_post
Identity Provider in FusionAuth (Generic OpenID Connect):
- Client Id, Client Secret - as provided by IdP
- Client authentication: client_secret_post
- Authorization endpoint: https://xxx.projects.oryapis.com/oauth2/auth
- Token endpoint: https://xxx.projects.oryapis.com/oauth2/token
- Userinfo endpoint: https://xxx.projects.oryapis.com/userinfo
- Scope: name email openid
- Others: No reconcile lambda, debug enabled
- Enabled to one application with enabled "Create registration"
Steps to reproduce
- Request to
https://xxx.fusionauth.io/oauth2/authorize
with query parameters:
- client_id= <application ID enabled in the IdP config>
- redirect_uri= <URL encoded, own callback>
- response_type=code
- tenantId=<tenant ID that has the application enabled in the IdP config>
- scope= URL encoded "openid offline_access"
- idp_hint=<ID of the Identity Provider configuration>
-
Have been redirected to Ory, got the authorization page, confirmed authorization for scope: email, name, openid.
-
Got redirection to the callback provided in point 1 as
redirect_uri
, with query parameters:
- code=*** 43-character long code, looks like Base64 plus
-
and_
- userState=Authenticated
- locale=en_GB
Confirmed in Ory web console that FusionAuth is properly listed as an authorized OAuth2 application with correct scope.
All looks good so far.
- As the next step, I tried to exchange the 43-character long code for FusionAuth's access token (JWT):
Request POST to https://xxx.fusionauth.io/api/identity-provider/login
with Content-Type: application/json
and body:
{
"applicationId": "<FusionAuth's application ID>",
"data": {
"code": "43-character long code ",
"redirect_uri": "same as redirect_uri from point 1"
},
"identityProviderId": "FusonAuth's ID of the IdP configuration"
}
- Response: HTTP 401:
{"generalErrors":[
{"code":"[ExternalAuthenticationException]OpenIDConnectToken","message":"A request to the OpenID Connect Token API has failed. Unable to complete this login request."}
]}
- Event Log:
The request to the [https://xxx.projects.oryapis.com/oauth2/token] endpoint failed. Status code [400].
Error response is
{
"error" : "invalid_grant",
"error_description" : "The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client. not_found"
}
Investigation, findings
-
I verified the correctness of the Identity Provider by implementing a simple OIDC flow with the same client configuration in Ory, without FusionAuth, and it worked as expected. Conclusion: problems are not caused by Ory.
-
Created my own public endpoint where I could see request details, let's call it
/middleman
When replaced Ory's/oauth2/token
with/middleman
I discovered that FusionAuth does one extra and unexpected call to/oauth2/token
between point 2 and point 3 above, i.e., after receiving successful redirection from Ory (with code apparently) and before sending the result to my callback.
This alone is not a problem yet. I guess FusionAuth keeps communication with Ory as an internal state referred to by its own token. Fine.
But then, when I callhttp://xxx.fusionauth.io/api/identity-provider/login
withdata.code
equal to the 43-character code I receive to the callback, FusionAuth seems to send that exact code to Ory. Obviously, Ory does not know what it is and returns an error.
Conclusion: Ory returns the correctauthorization_code
, but I have no access to it, moreover, Fusionauth "consumes" this code for a mysterious extra call, without any visible result of it, like access/refresh tokens kept in the internal state.
Questions
-
Why FusionAuth calls IdP's
/oauth2/token
before it calls my callback if it does not keep access/refresh tokens? -
What is this 43-character code FusionAuth sends to the callback if I can't use it for anything? Most importantly, I can't use it for
fusionauth.io/api/identity-provider/login
.