There seems to be no easy way to set up server to server authorization in the existing Frappe oauth2 integration using client_credentials grant_type. In such a case redirection to a login page is not an option.
e.g. How to use OAuth2 grant type Client Credential?
Based on test_login_using_implicit_token
from frappe/frappe/tests/test_oauth20.py
This approach maybe used in such a situation.
-
Create an OAuth Client that will provide the client_id, client_secret, scope etc
-
Create a service account user that will be the User for generating the access_token
import frappe
import requests
import json
from urllib.parse import parse_qs, urljoin, urlparse, unquote
from werkzeug.wrappers import Response # type: ignore
from frappe.utils.password import get_decrypted_password
from frappe.integrations.oauth2 import encode_params
def get_full_url(endpoint):
"""Turn '/endpoint' into 'http://127.0.0.1:8000/endpoint'."""
return urljoin(frappe.utils.get_url(), endpoint)
def login(session):
# The usr, pwd which will be the login. Can be from a Settings DocType
usr = frappe.conf.oauth_usr
pwd = frappe.conf.oauth_pwd
session.post(get_full_url("/api/method/login"), data={"usr": usr, "pwd": pwd})
return unquote(session.cookies.get("user_id", "")) == usr
@frappe.whitelist(allow_guest=True)
def token(**kwargs):
"""return access_token for grant_type client_credentials, without redirect to login"""
if not kwargs.get("grant_type", "") == "client_credentials":
return Response(
json.dumps(
{
"error": "unsupported_grant_type",
"error_description": "The grant_type should be client_credentials",
}
),
status=400,
)
client_id = kwargs.get("client_id")
client_secret = kwargs.get("client_secret")
oauth_client = frappe.get_value(
"OAuth Client", {"client_id": client_id, "client_secret": client_secret}
)
if not oauth_client:
return Response(
json.dumps(
{
"error": "invalid_client",
"error_description": "The client_credentials are invalid.",
}
),
status=401,
)
oauth_client = frappe.get_doc("OAuth Client", oauth_client)
session = requests.Session()
data = login(session)
if not login(session):
return Response(
json.dumps(
{
"error": "invalid_grant",
"error_description": "The API Service User credential is invalid.",
}
),
status=401,
)
# Go to Authorize url.
# The redirect url will have the access_token when OAuth Client is setup with Implicit and Token
try:
out = session.get(
get_full_url("/api/method/frappe.integrations.oauth2.authorize"),
params=encode_params(
{
"client_id": client_id,
"scope": "openid all",
"response_type": "token",
"redirect_uri": oauth_client.default_redirect_uri,
}
),
)
redirect_destination = out.url
except requests.exceptions.ConnectionError as ex:
redirect_destination = ex.request.url
response_dict = parse_qs(urlparse(redirect_destination).fragment)
data = {}
for key, val in response_dict.items():
data[key] = val[0]
return Response(
json.dumps(data), status=200, content_type="application/json; charset=utf-8"
)