goauthentik / authentik

The authentication glue you need.
https://goauthentik.io
Other
8.12k stars 634 forks source link

OIDC authorization through API #3926

Open ariep opened 1 year ago

ariep commented 1 year ago

Describe your question/ I'd like to log in to an OIDC client (app) from an automated system. The app has an API endpoint to enter the OIDC code and get a token for further use of the app, so I'd like to obtain the OIDC code from Authentik.

I'm trying to go through the Authentik API to do so. I've successfully gone through the default-authentication-flow already, submitting username and password, resulting in a successful login. I'm storing the Authentik session cookies so subsequent requests to Authentik I think can use that session.

However, I fail so far to perform the OIDC authorization. I've tried the default-provider-authorization-explicit-consent flow, but I get stuck in CSRF errors (according to the Authentik server logs). This could be due to me not putting the right things in the query parameter, but I'm not exactly sure what to put there, I'm mostly following what I see in a browser session but that's bound to differ at some point, because I don't want to be redirected to the app, I want to obtain the code token to submit that to the app's API myself. I don't really understand why this particular flow gives these CSRF errors, while the authentication flow works fine using the same approach.

Is there maybe some documentation on the query parameter and/or the default-provider-authorization-explicit-consent flow and/or the required CSRF behaviour (Referer headers?)?

Or maybe there's a better way to achieve my goal of obtaining an OIDC code token from a script?

Logs

{"event": "PermissionDenied('CSRF Failed: CSRF token missing.')", "flow_slug": "default-provider-authorization-explicit-consent", "host": "***", "level": "warning", "logger": "authentik.flows.vi
ews.executor", "pid": 3782, "request_id": "***", "timestamp": "2022-11-01T***"}

There is in fact an authentik_csrf cookie in the script's session storage, which works for the other flow as mentioned.

Version and Deployment (please complete the following information):

Rapatas commented 1 year ago

I got stuck in the same step too. Turns out you need to provide the CSRF token both as cookie and as header. Like so:

curl "http://localhost:9000/api/v3/flows/executor/default-provider-authorization-explicit-consent/" \
    --cookie "authentik_session=$session; authentik_csrf=$csrf" \
    -H "X-Authentik-Id: $id" \
    -H "X-authentik-CSRF: $csrf" \
    -H "Content-Type: application/json" \
    -d '{"component":"ak-stage-consent","token":"'"$token"'"}' -vv
ariep commented 1 year ago

Thanks @Rapatas , that helps against the CSRF error indeed! I'm not sure how to continue from there though; it would be splendid if you could provide some more guidance. Specifically:

  1. Context for reference: at the point I'm stuck, I have already successfully gone through the authentication flow (I think): I've submitted username, then password, which finally results in a 200 response {'type': 'redirect', 'component': 'xak-flow-redirect', 'to': '/'}, which I take to mean that authentication has succeeded. After that, I try to go through the consent flow to obtain a code for submitting to the actual application (OIDC client).
  2. Your curl example is a GET request, is that what you mean? The browser does a GET first and then a POST to the same url, but I'm not sure how far I should follow the browser as I want to obtain a code token and not a browser redirect to the app.
  3. What's the $token in your example? I see a token in the output of the GET to my default-provider-authorization-explicit-consent; I'm assuming for now that one should set that same token when POSTing back to the same url.
  4. The farthest I get is by doing this subsequent GET and POST to default-provider-authorization-explicit-consent with your suggestion of giving the CSRF token in two forms, and POSTing the token that the GET gives. In that case the POST results in {"type": "redirect", "component": "xak-flow-redirect", "to": "/"}, which seems to indicate success, except I don't get the code token which I need to interact with my application (OIDC client). What am I missing here? I did add response_type=code to the consent GET and POST, but I get the feeling the response is still meant for a browser-based flow.

Thanks for any further pointers!

Rapatas commented 1 year ago
  1. When you add -d on curl, it assumes that you want a POST request. My example is the POST request after the GET in the same url.
  2. That is correct. The token is the same as the one from the GET request.
  3. At this point I gave up reverse engineering authentik. I decided it was simpler to use the Authorization Code Flow with PKCE in the browser instead.
BeryJu commented 1 year ago

For api-based logins, it is recommended to either use app-password based client_credentials flow (see https://goauthentik.io/docs/providers/oauth2/client_credentials), or using a device code flow (see https://goauthentik.io/docs/providers/oauth2/device_code). Otherwise, have a look at the LDAP Flow executor, which runs a flow via the API: https://github.com/goauthentik/authentik/blob/main/internal/outpost/flow/executor.go

prostoprojekt commented 10 months ago

Hi, is this topic alive? Look’s like a have more or less same situation

ariep commented 10 months ago

It is alive in the sense that I have not solved this yet. It has remained dormant though because this is a side project for me that I can't spend a lot of time on.

Replying to @BeryJu : the client_credentials flow seems to use service accounts, so I assume that's not compatible with my goal of doing an OIDC login on behalf of a regular (human user) account. The device code flow seems to require interaction by the human user, and not just once because the device code expires after some time. Perhaps this could be an option for me if I can configure that lifetime to be long (say one year).

prostoprojekt commented 10 months ago

Dear @ariep if it will be not very hard for you, can you please give more details of point "I'm trying to go through the Authentik API to do so. I've successfully gone through the default-authentication-flow already, submitting username and password, resulting in a successful login. I'm storing the Authentik session cookies so subsequent requests to Authentik I think can use that session."

I have problem with sending password.

Initially I was able to make GET request to default-authentication-flow, than POST request sending uid_field and in return I have 200 reply:

{"type":"native","flow_info":{"title":"Welcome to authentik!","background":"\/static\/dist\/assets\/images\/flow_background.jpg","cancel_url":"\/flows\/-\/cancel\/","layout":"stacked"},"component":"ak-stage-password","pending_user":"571***4","pending_user_avatar":"data:image\/svg+xml;base64,PHN...4="}

I try to send another POST to default-authentication-flow my request looks like this:

Array
(
    [timeout] => 10
    [sslverify] => 1
    [redirection] => 5
    [blocking] => 1
    [cookies] => Array
        (
        )

    [headers] => Array
        (
            [Authorization] => Bearer EKAs***NS
            [Content-Type] => application/json
            [cookies] => (
                    [name] => authentik_session
                    [value] => eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzaWQiOiJrZmMxYnRvM3pjZHY3eXdzcm9wMWExMHg3YmVxbTN0YSIsImlzcyI6ImF1dGhlbnRpayIsInN1YiI6Ijc0YmJlY2FhYjkzZWNmOWViMjkwNzJlOTY2NDFhZTE4ZjZkZDA1M2Q4NzllYTA4ZDZmNTc4YTA4YzhjZjRmNTIiLCJhdXRoZW50aWNhdGVkIjp0cnVlLCJhY3IiOiJnb2F1dGhlbnRpay5pby9jb3JlL2RlZmF1bHQifQ.sJOngmcFH0U3v4YcwQb9zZOQm_jLnFX8U3UfDVH7x5I
                    [expires] => 
                    [path] => /
                    [domain] => auth.me
                    [port] => 
                    [host_only] => 1
                )
        )

    [body] => Array
        (
            [component] => ak-stage-password
            [password] => 123
        )

    [method] => POST
)

BUT response looks like this: {"type":"native","component":"ak-stage-flow-error","request_id":"e1e***55"}

Where in request I go wrong according to what you send for ak-stage-password?

BeryJu commented 3 months ago

@prostoprojekt there should be some additional context in the authentik server container logs