How to setup Authentik for OAuth2 Password Grant?
Describe your question/ We are trying to use Authentik as our own identity provider inside a Microservice architecture to login users through OAuth2 Password Grant. Basically, we want to implement this flow: https://auth0.com/docs/get-started/authentication-and-authorization-flow/resource-owner-password-flow.
We found in the documentation that it's not listed as grant type https://goauthentik.io/docs/providers/oauth2/ but inside https://goauthentik.io/docs/providers/oauth2/client_credentials there is a note about password grant saying:
Note that authentik does treat a grant type of password the same as client_credentials to support applications
which rely on a password grant.
Further, checking the OpenID endpoint /application/o/{appName}/.well-known/openid-configuration we see that the password grant is supported:
{
"grant_types_supported": [
"authorization_code",
"refresh_token",
"implicit",
"client_credentials",
"password",
"urn:ietf:params:oauth:grant-type:device_code"
],
}
According to that, we've tried to send requests with many alternatives, including scope, response_type, etc, without success:
curl --request POST \
--url 'https://{domain}/application/o/token/' \
--header 'content-type: application/x-www-form-urlencoded' \
--data grant_type=password \
--data 'username={username}' \
--data 'password={password}' \
--data 'client_id={clientId}' \
--data 'client_secret={clientSecret}'
It always fails saying:
{
"error": "invalid_grant",
"error_description": "The provided authorization grant or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client"
}
We appreciate it any advice about this topic or any confirmation that the password grant is not supported. Thanks!
Version and Deployment (please complete the following information):
- authentik version: 2023.5.3
- Deployment: helm
the password grant (since it is handled internally like the client_credentials) grant only works with Tokens of the kind of APP_PASSWORD
@BeryJu does that mean we cannot authenticate on behave of a user?
From what we tried, the client_credentials work properly with the service account + token but we are not able to access to protected endpoint where a user resource is required, e.g. /core/users/me, and that's expected as it's a machine-to-machine flow.
Thanks for your quick answer.
@BeryJu @rissson What we are trying to accomplish is that a user authenticates using only username and password, as specified in the RFC 6749 as Resource Owner Password Credentials Grant it looks like this:
I took that from Auth0 because it was the simplest one I could find but in the end, the Resource Owner Password Flow is always the same:
Is this something that could be done using Authentik?
I'm interested in hearing more about this too. I've been reading documentation about this kind of use-case and I've been having difficulty implementing this.
Currently also having issues with this, working on dms and can't get grant password working with app passwords.
hello, same error for me, is there any workaround to make the password flow authentication work with a curl request on goauthentik ?
same here for Jamf Connect
@fheisler Maybe this is related to #6139 ? In my case Jamf Connect tries to validate username/password of user by sending it to the token endpoint with "password" as grant_type. Unfortunately authentik only reponds with HTTP 400.
so, basically, we need this to function:
curl --request POST \
--url 'https://authentik.url.local/application/o/token/' \
--header 'content-type: application/x-www-form-urlencoded' \
--data grant_type=password \
--data username=user-login \
--data password=user-pw \
--data 'client_id=client-id' \
--data 'client_secret=client-secret' \
--data 'scope=openid profile read:sample'
Agree. Im having real difficulties understanding how to solve it other ways… is it possible to create a flow for this?
--data password=user-pw \ this should be an app password instead of the user's password (see https://github.com/goauthentik/authentik/issues/5860#issuecomment-1577081591)
Yeah, but with esp Jamf Connect the user password gets transferred, as the user types in his password. This is the one which needs to be verified, not an app password. Like the others also mentioned after the comment #5860
This is really not clear in the documentation what an app_password is (i.e. how it differs from a token), if there's a way for a client to request one except through the web interface, or what its purpose is altogether. I'm trying to set up a similar setup to many here:
User gets invited to create an account, creates it, and creates an API Key. This API key is used to authenticate with authentik which issues JWTs for the user.
There doesn't seem to be a clear distinction between tokens and app passwords. The web interface and API call them both tokens. All I've found is that the app passwords work with the /application/o/token/ endpoint, and the tokens don't.
EDIT: Just figured this out for anyone else confused. The admin GUI and terraform docs shed some light: Tokens are just for API access and app passwords are for flow authentication.
I figured out how to fix this problem. The main reason why we have this problem is because someone merged GRANT_TYPE_CLIENT_CREDENTIALS and GRANT_TYPE_PASSWORD a long time ago.
https://github.com/goauthentik/authentik/blob/b301048a272aa76756d9d0d13d0b8b8fedeb0cfe/authentik/providers/oauth2/views/token.py#L163-L167
https://github.com/goauthentik/authentik/blob/b301048a272aa76756d9d0d13d0b8b8fedeb0cfe/authentik/providers/oauth2/views/token.py#L531-L533
Digging deeper I found out. The error occurs only because the user and his APP_PASSWORD are checked. Logically, a regular user does not have this password.
https://github.com/goauthentik/authentik/blob/b301048a272aa76756d9d0d13d0b8b8fedeb0cfe/authentik/providers/oauth2/views/token.py#L311-L339
But we can add a simple check and everything will start working like clockwork. If the usertype is a SERVICE_ACCOUNT, check his APP_PASSWORD, if the user is regular, check his password in the database. But this is still not the correct behavior.
def __post_init_client_credentials_creds(
self, request: HttpRequest, username: str, password: str
):
# Authenticate user based on credentials
user = User.objects.filter(username=username).first()
if not user:
raise TokenError("invalid_grant")
if user.type == UserTypes.SERVICE_ACCOUNT:
token: Token = Token.filter_not_expired(
key=password, intent=TokenIntents.INTENT_APP_PASSWORD
).first()
if not token or token.user.uid != user.uid:
raise TokenError("invalid_grant")
else:
if not user.check_password(password):
raise TokenError("invalid_grant")
self.user = user
# Authorize user access
app = Application.objects.filter(provider=self.provider).first()
if not app or not app.provider:
raise TokenError("invalid_grant")
self.__check_policy_access(app, request)
Event.new(
action=EventAction.LOGIN,
**{
PLAN_CONTEXT_METHOD: "token",
PLAN_CONTEXT_METHOD_ARGS: {
"identifier": token.identifier if token else {},
},
PLAN_CONTEXT_APPLICATION: app,
},
).from_http(request, user=user)
How it works now, grant_type "password" and "client_credentials" works the same!!!:
OK
curl --location 'https://auth.example.com/application/o/token/' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'client_id=your_client_id' \
--data-urlencode 'username=your_service_account' \
--data-urlencode 'password=your_service_account_APP_PASSWORD'
OK
curl --location 'https://auth.example.com/application/o/token/' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'client_id=your_client_id' \
--data-urlencode 'client_secret=your_client_secret'
OK
curl --location 'https://auth.example.com/application/o/token/' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'client_id=your_client_id' \
--data-urlencode 'client_secret=your_client_secret'
OK
curl --location 'https://auth.example.com/application/o/token/' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'client_id=your_client_id' \
--data-urlencode 'username=your_service_account' \
--data-urlencode 'password=your_service_account_APP_PASSWORD'
Ideally, these two flows should be separated. When code validate client_id and client_secret it returns the client_credentials token. If client_secret is invalid or empty it tries to check service account and APP_PASSWORD.
After my patch, both flows still don't work correctly. It turns out that we can log out a user without client_secret, regardless of grant_type.
Case 1:
curl --location 'https://auth.example.com/application/o/token/' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=password or client_credentials' \
--data-urlencode 'client_id=your_client_id' \
--data-urlencode 'client_secret=your_client_secret'
// will be ignored
--data-urlencode 'username=username' \
--data-urlencode 'password=password'
Case 2:
curl --location 'https://auth.example.com/application/o/token/' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=password or client_credentials' \
--data-urlencode 'client_id=your_client_id' \
--data-urlencode 'username=username' \
--data-urlencode 'password=password'
Is this fixed? I'm trying to get token with:
curl --request POST \
--url 'https://{domain}/application/o/token/' \
--header 'content-type: application/x-www-form-urlencoded' \
--data grant_type=client_credentials \
--data 'username={username}' \
--data 'password={password}' \
--data 'client_id={clientId}' \
and i'm getting:
{
"error": "invalid_grant",
"error_description": "The provided authorization grant or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client"
}
Gives me the same error as well: { "error": "invalid_grant", "error_description": "The provided authorization grant or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client" }
Gives me the same error as well: { "error": "invalid_grant", "error_description": "The provided authorization grant or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client" }
same here
any ETA?
same issue here, no idea on how to get the access token. keep getting error.
{ "error": "invalid_grant", "error_description": "The provided authorization grant or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client" }
Found way to get an access token for a service user that has an app password. The trick is to use the "Copy Token" button on the "Tokens" (if/admin/#/core/tokens) page, even though the "Intent" is set to "App Password" and then use this token value for the "password" field in the POST request
curl --location 'https://authentik.domain.tld/application/o/token/'
--header 'Content-Type: application/x-www-form-urlencoded'
--data-urlencode 'grant_type=client_credentials'
--data-urlencode 'client_id=my-app'
--data-urlencode 'password=xxxxxxxxxxx'
--data-urlencode 'scope=openid'
--data-urlencode 'username=yyyy'
this returns:
{
"access_token": "eyJhbGciOiJSUzI1NiIsImtp....",
"token_type": "Bearer",
"expires_in": 3600,
"id_token": "eyJhbGciOiJSUzI1NiIsIm....."
}
The documentation here https://docs.goauthentik.io/docs/add-secure-apps/providers/oauth2/client_credentials#static-authentication mentions this, however it needs some clarification and an example to make it clearer how to setup and use this
Hi guys,
in my case its failing too.
I came across that issue as well, while investigating for the support OAuth2-proxy feature request https://github.com/oauth2-proxy/oauth2-proxy/issues/2966.
Then I saw #6139, and this one.
As @BeryJu mentions, the docs are a bit unclear about where to find the Token (in the Directory) and how to set it up properly. The Client_ID is also unclear from the context, but can be found in the "Providers" Tab. Also, whether to specify API Token or App Password type is unclear, but in my case both failed.
For testing the client_credentials grant request on application/o/token/ endpoint, I crafted the following python script
import requests
proto = "http"
host = "authentik_server"
port = 9000
#host_ip = socket.gethostbyname(host) # grabs IP of hostname from resolution table - only needed in my docker env
endpoint = "application/o/token/"
#fullurl = "{}://{}:{}/{}".format(proto, host_ip, port, endpoint) # changed as only required in my docker env
fullurl = "{}://{}:{}/{}".format(proto, host, port, endpoint)
# creds
username = "[email protected]" # m2m service account name
password = "xxxxx" # token
client_id = "OxxxH" # AUD/SUB/client_id
data={
"grant_type": "client_credentials",
"client_id": "{}".format(client_id),
"password": "{}".format(password),
"scope": "openid", #"profile" # can be profile or openid
"username": "{}".format(username),
}
headers = {
#"content-type": "application/x-www-form-urlencoded",
}
print(data)
r1 = requests.post (fullurl, data=data, headers=headers)
print(r1.json())
Now when I am doing the request it returns the following error:
{'error': 'invalid_grant', 'error_description': 'The provided authorization grant or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client'}
Does not matter whether App Password Token or API Token is selected for the m2m machine account token in the Directory.
Post Request data:
{'grant_type': 'client_credentials', 'client_id': 'OgHxxxxxxl676H', 'password': 'TNbWxxxxx3f2tjfc', 'scope': 'openid', 'username': '[email protected]'}
I tried everything for username; username, email etc. ... With or without urlenocde in headers... Changing the position of "username" element in the dict as per @mwullink's reply nope... Always the same error message.
Instead, I would expect the JWT Bearer access_token as in @mwullink 's reply.
I have the same issue.
It does work if i add a App Password and use that but i need to change things if i can't let the user login with there normal user password via password grant as he's only supposed to communicate with the my backend without knowing about the authentik server
@BeryJu any updates?
Much of the discussion so far is around acquiring the token. In 2025.2.4, I can get the token without issue. When introspected, I see it has the preferred_username set to the service account used to acquire it. What I can't do is use this token with a proxy provider. Even though I have the OAuth provider selected as a Federated OIDC Provider, I invariably get "Token does not exist". Has anyone actually gotten this flow to work for m2m communication? With Password Grant being deprecated in OAuth 2.1, is there another way to achieve this?
@joshuarestivo This looks broken on 2025.2.4 - it was working before. I too can get a token but i am unable to use it. haven't checked for reported bugs - but we probably need to create one.
@emouawad I've created https://github.com/goauthentik/authentik/issues/14993 for this. Please add any additional info that you think is relevant.