-
Notifications
You must be signed in to change notification settings - Fork 976
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: support OIDC flows for apps #3216
Conversation
Codecov Report
@@ Coverage Diff @@
## master #3216 +/- ##
==========================================
+ Coverage 77.93% 77.96% +0.03%
==========================================
Files 324 324
Lines 20732 20780 +48
==========================================
+ Hits 16158 16202 +44
+ Misses 3365 3363 -2
- Partials 1209 1215 +6
Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here. |
...istence/sql/migrations/sql/20230405000000000001_create_session_token_exchangers.mysql.up.sql
Outdated
Show resolved
Hide resolved
26ebe49
to
1e5f9ff
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nice & clean, I have a few remarks. One question is whether we should add some tests to the oidc go test suite. It's a bit of duplication because we test the same thing in e2e, but those tests execute faster and are easier to debug / step through if something breaks
persistence/sql/migrations/sql/20230405000000000001_create_session_token_exchanges.mysql.up.sql
Outdated
Show resolved
Hide resolved
persistence/sql/migrations/sql/20230405000000000001_create_session_token_exchanges.up.sql
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nice, a couple of questions and remarks on the code-side. I will look into the flow description next!
init_code = ? AND init_code <> '' AND | ||
return_to_code = ? AND return_to_code <> '' AND |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need the non-empty assertions when the parameters are provided?
Is this not missing an index? Have you checked whether querying this scans excessive rows?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need the non-empty assertions when the parameters are provided?
This is only a precaution that we never fetch from empty credentials.
Is this not missing an index? Have you checked whether querying this scans excessive rows?
The index should be this: CREATE INDEX session_token_exchanges_nid_code_idx ON session_token_exchanges (init_code, nid);. I didn't verify this with EXPLAIN
though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But that's missing return_to_code, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I now verified through EXPLAIN
that everything works as expected:
Fetch by flow ID
explain select * from session_token_exchanges
where nid = '212e6a8b-8a8f-4e30-b1e2-99b8478d773b'
and flow_id = '7cf7bca9-f94e-41a8-8b8c-cb213b8b9cd9'
and init_code <> ''
and return_to_code <> ''
and session_id is not null;
Index Scan using session_token_exchanges_nid_flow_id_idx on session_token_exchanges (cost=0.14..8.17 rows=1 width=372)
Index Cond: ((flow_id = '7cf7bca9-f94e-41a8-8b8c-cb213b8b9cd9'::uuid) AND (nid = '212e6a8b-8a8f-4e30-b1e2-99b8478d773b'::uuid))
Filter: ((session_id IS NOT NULL) AND ((init_code)::text <> ''::text) AND ((return_to_code)::text <> ''::text))
Fetch by code
explain select * from session_token_exchanges
where nid = '212e6a8b-8a8f-4e30-b1e2-99b8478d773b'
and init_code = '5JxAMjfYsOCQNwsEyPoXfbC3u0Dbqb6TyGoB7z9now0BPNaJt4itcqecYr9YSR4t'
and init_code <> ''
and return_to_code = '3bkURN1fYEN2PtfvdE3juvUHC6XAzmcQufvHfbQ8gWSRa85j8kpvnyBh2gvmI9q7'
and return_to_code <> ''
and session_id is not null;
Index Scan using session_token_exchanges_nid_code_idx on session_token_exchanges (cost=0.14..8.17 rows=1 width=372)
Index Cond: (((init_code)::text = '5JxAMjfYsOCQNwsEyPoXfbC3u0Dbqb6TyGoB7z9now0BPNaJt4itcqecYr9YSR4t'::text) AND (nid = '212e6a8b-8a8f-4e30-b1e2-99b8478d773b'::uuid))
Filter: ((session_id IS NOT NULL) AND ((return_to_code)::text <> ''::text) AND ((return_to_code)::text = '3bkURN1fYEN2PtfvdE3juvUHC6XAzmcQufvHfbQ8gWSRa85j8kpvnyBh2gvmI9q7'::text))
In both cases, the index is used first to narrow down the rows. Since (nid, flow_id) and (nid, init_code) both narrow down the row to a single result, the remaining filter is very fast. Adding more fields to the indices would not improve performance.
if ft == flow.TypeAPI && r.URL.Query().Get("return_session_token_exchange_code") == "true" { | ||
e, err := h.d.SessionTokenExchangePersister().CreateSessionTokenExchanger(r.Context(), f.ID) | ||
if err != nil { | ||
return nil, nil, errors.WithStack(herodot.ErrInternalServerError.WithWrap(err)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not just return err
- wrapping it here will probably lose some context?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As the user can't do anything about this we should return a 500 code. Is this always the case when we don't wrap? If so, I can remove the wrapping.
InitCode string `json:"init_code"` | ||
|
||
// The part of the code returned by the return_to URL. | ||
// | ||
// required: true | ||
// in: query | ||
ReturnToCode string `json:"return_to_code"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the future, this endpoint may support other token exchanges as well. Should we somehow prefix / clarify what code you're exchanging here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what other usages you think of here. If there are other types of tokens, wouldn't this be a different end point? It would probably be easier to reason about the security then?
// 404: errorGeneric | ||
// 410: errorGeneric | ||
// default: errorGeneric | ||
func (h *Handler) exchangeCode(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Go tests for this? :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted to execute the tests to check the flow end-to-end, but I was not able to do so, because of:
How can I execute the tests? Additionally I found the code in the react app a bit confusing. I kind of would have expected the code to be more obvious, but it's hiding behind several nested function calls that are relatively deep in the stack trace. This made it difficult to do a final end-to-end review. It would be really helpful if there was a simple Makefile command (or something similar) to get the playwright tests started! |
The entry point to the OIDC handling in the native app is the
I added a make target. You can now run the Playwright tests with RN_UI_PATH="path/to/kratos-selfservice-ui-react-native" make test-e2e-playwright (env var needed until ory/kratos-selfservice-ui-react-native#69 is merged) |
Sorry to show out of the blue, but from client perspective a Given this definition @Headers("Content-Type: application/json")
@POST("self-service/login")
fun getSocialSignInUrl(@Query("flow") flowId: String, @Body body: SocialSignInBody): Call<SocialSignInUrlResponse> It's normally straightforward to get the response body through response.body() But because you are returning a return when (response.code()) {
422 -> {
val errorBody = response.errorBody().string()
RetrofitClientFactory.gson.fromJson(errorBody, SocialSignInUrlResponse::class.java)
}
else -> throw ...
} This won't affect your java client because you parse everything manually, but my two cents as an android developer. I hope this gives you more information and maybe you reconsider using an error code on a successful response. |
Yes that's correct. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code-wise this looks great to me. E2e just straight up fail for me locally. But probably an issue on my machine.
spec/api.json
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is the response code for the registration flow handler missing here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Your comment lost it's link to the source code. What is the issue here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was just wondering whether the (current) 422 response from the registration handler is documented in the open api doc.
Flow for Kratos AppAuth
7a. Upon receiving the URL, the app can retrieve the session token at
/self-service/exchange-code-for-session-token&code=<session token exchange code>
7b. If the app can't receive a deep link (for example, if we are simulating the app on the web), then the app can also poll
/self-service/exchange-code-for-session-token&code=<session token exchange code>
and wait for a 200 status code with the session.Related issue(s)
Docs PR: ory/docs#1379
Checklist
introduces a new feature.
contributing code guidelines.
vulnerability. If this pull request addresses a security vulnerability, I
confirm that I got the approval (please contact
[email protected]) from the maintainers to push
the changes.
works.
Security Considerations
During the authentication process we need to ensure that the following entities are all under the same control:
For example, it should not be possible for an attacker to start a flow, trick a victim into performing the authentication with the OIDC provider, and then be able to fetch the session token in the name of the victim.
We ensure that A1, A2, B1, B2 are under the same control as follows:
initial_session_token_exchange_code
binds A1 and A2 by generating in when the flow starts and requiring it in exchange for the session tokenstate
parameter binds B1 and B2 by deriving thestate
from the flow ID and the hashed code, which guarantees that no attacker can forge thestate
for B2return_to_code
binds B2 and A2 by redirecting the browser to the specified and whitelistedreturn_to
URL and including the code in a query parameter.It then follows that A1, A2, B1, B2 are all under the same control