Use the header Authorization: Bearer <access_token> to perform authenticated requests. You can receive a bearer token by combining the following two requests.

> Here is an amazing introduction to OAuth: OAuth 2.0 and OpenID Connect (in plain English)

GET /api/method/frappe.integrations.oauth2.authorize

Get an authorization code from the user to access ERPNext on his behalf.

Params (in query):

  • client_id (string)

    ID of your OAuth2 application

  • state (string)

    Arbitrary value used by your client application to maintain state between the request and callback. The authorization server includes this value when redirecting the user-agent back to the client. The parameter should be used for preventing cross-site request forgery.

  • response_type (string)

    "code"

  • scope (string)

    The scope of access that should be granted to your application.

  • redirect_uri (string)

    Callback URI that the user will be redirected to, after the application is authorized. The authorization code can then be extracted from the params.

  • codechallengemethod (string)

    (OPTIONAL) Can be one from s256 or plain.

  • code_challenge (string)

    (OPTIONAL) Can be base64encode(sha256(random_string)) in case code_challenge_method=s256 or random_string in case code_challenge_method=plain. Refer https://tools.ietf.org/html/rfc7636#appendix-A

Example:

curl -X POST https://{your frappe instance}/api/method/frappe.integrations.oauth2.authorize \
     --data-urlencode 'client_id=511cb2ac2d' \
     --data-urlencode 'state=444' \
     # base64encode(sha256('420')) => 21XaP8MJjpxCMRxgEzBP82sZ73PRLqkyBUta1R309J0
     # --data-urlencode 'code_verifier=21XaP8MJjpxCMRxgEzBP82sZ73PRLqkyBUta1R309J0' \
     --data-urlencode 'response_type=code'
     --data-urlencode 'scope=openid%20all' \
     --data-urlencode 'redirect_uri=https://app.getpostman.com/oauth2/callback'

Returns:

  • HTTP Code: 200
  • text/html

    This will open the authorize page which then redirects you to the redirect_uri.

If the user clicks 'Allow', the redirect URI will be called with an authorization code in the query parameters:

https://{redirect uri}?code=plkj2mqDLwaLJAgDBAkyR1W8Co08Ud&state=444

If user clicks 'Deny' you will receive an error:

https://{redirect uri}?error=access_denied

Token Exchange for Authorization Code Grant with ID Token

POST /api/method/frappe.integrations.oauth2.get_token
Header Content-Type: application/x-www-form-urlencoded

> Note: This endpoint can also be used to get a refreshed access token. Just send the refresh_token in the request body.

Trade the authorization code (obtained above) for an access token.

Params (in body):

  • grant_type (string)

    "authorization_code"

  • code (string)

    Authorization code received in redirect URI.

  • client_id (string)

    ID of your OAuth2 application

  • redirect_uri (string)

    Registered redirect URI of client app

Example:

curl -X POST https://{your frappe instance}/api/method/frappe.integrations.oauth2.get_token \
     -H 'Content-Type: application/x-www-form-urlencoded' \
     -H 'Accept: application/json' \
     -d 'grant_type=authorization_code&code=wa1YuQMff2ZXEAu2ZBHLpJRQXcGZdr
         &redirect_uri=https%3A%2F%2Fapp.getpostman.com%2Foauth2%2Fcallback&client_id=af615c2d3a'

For testing purposes you can also pass the parameters in the URL like this (and open it in the browser):

https://{your frappe instance}/api/method/frappe.integrations.oauth2.get_token?grant_type=authorization_code&code=A1KBRoYAN1uxrLAcdGLmvPKsRQLvzj&client_id=511cb2ac2d&redirect_uri=https%3A%2F%2Fapp.getpostman.com%2Foauth2%2Fcallback

Returns:

    {
        "access_token": "pNO2DpTMHTcFHYUXwzs74k6idQBmnI",
        "token_type": "Bearer",
        "expires_in": 3600,
        "refresh_token": "cp74cxbbDgaxFuUZ8Usc7egYlhKbH1",
        "scope": "openid all",
        "id_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.XbPfbIHMI6arZ3Y922BhjWgQzWXcXNrz0ogtVhfEd2o"
    }

Token Exchange for Authorization Code Grant with ID Token (PKCE)

POST /api/method/frappe.integrations.oauth2.get_token
Header Content-Type: application/x-www-form-urlencoded

Trade the authorization code (obtained above) for an access token.

Params (in body):

  • grant_type (string)

    "authorization_code"

  • code (string)

    Authorization code received in redirect URI.

  • client_id (string)

    ID of your OAuth2 application

  • redirect_uri (string)

    Registered redirect URI of client app

  • code_verifier (string)

    random_string used during Authorization Request with code_challenge_method and code_challenge.

Content-Type: application/x-www-form-urlencoded

Example:

curl -X POST https://{your frappe instance}/api/method/frappe.integrations.oauth2.get_token \
     -H 'Content-Type: application/x-www-form-urlencoded' \
     -H 'Accept: application/json' \
     -d 'grant_type=authorization_code&code=wa1YuQMff2ZXEAu2ZBHLpJRQXcGZdr
         &redirect_uri=https%3A%2F%2Fapp.getpostman.com%2Foauth2%2Fcallback&client_id=af615c2d3a&code_verifier=420'

For testing purposes you can also pass the parameters in the URL like this (and open it in the browser):

https://{your frappe instance}/api/method/frappe.integrations.oauth2.get_token?grant_type=authorization_code&code=A1KBRoYAN1uxrLAcdGLmvPKsRQLvzj&client_id=511cb2ac2d&redirect_uri=https%3A%2F%2Fapp.getpostman.com%2Foauth2%2Fcallback&code_verifier=420

Returns:

{
    "access_token": "pNO2DpTMHTcFHYUXwzs74k6idQBmnI",
    "token_type": "Bearer",
    "expires_in": 3600,
    "refresh_token": "cp74cxbbDgaxFuUZ8Usc7egYlhKbH1",
    "scope": "openid all",
    "id_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.XbPfbIHMI6arZ3Y922BhjWgQzWXcXNrz0ogtVhfEd2o"
}

Revoke Token Endpoint

POST /api/method/frappe.integrations.oauth2.revoke_token
Header: Content-Type: application/x-www-form-urlencoded

Revoke token endpoint.

Params:

  • token

    Access token to be revoked.

Returns:

Always returns empty response with HTTP status code 200.

    {}

Open ID Connect id_token

ID Token is a JWT.

  • aud claim has registered client_id.
  • iss claim has frappe server url.
  • sub claim has Frappe User's userid.
  • roles claim has user roles.
  • exp claim has expiration time.
  • iat claim has issued at time.

Example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkouIERvZSIsImVtYWlsIjoiakBkb2UuY29tIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyNDIwMjIsImF1ZCI6ImNsaWVudF9pZCJ9.ZEdnrHjLbArahVTN19b4zoRFoBO5a2BakRkR82O1VU8

Verify and extract it with PyJWT.

import jwt

id_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkouIERvZSIsImVtYWlsIjoiakBkb2UuY29tIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyNDIwMjIsImF1ZCI6ImNsaWVudF9pZCIsInJvbGVzIjpbIlN5c3RlbSBNYW5hZ2VyIiwiU2FsZXMgTWFuYWdlciJdLCJub25jZSI6Ijc4OTEyMyIsImlzcyI6Imh0dHBzOi8vZXJwLmV4YW1wbGUuY29tIn0.F8Wbh5dtD1loZPltJLj_sqF9DZNeBvEbo-ITtf3UPqk"

client_id = 'client_id'
client_secret = 'client_secret'

payload = jwt.decode(
    id_token,
    audience=client_id,
    key=client_secret,
    algorithm="HS256",
    options={"verify_exp": False}, # Enabled by default to verify expiration time
)

print(payload)
# Output

{'sub': '1234567890', 'name': 'J. Doe', 'email': 'j@doe.com', 'iat': 1516239022, 'exp': 1516242022, 'aud': 'client_id', 'roles': ['System Manager', 'Sales Manager'], 'nonce': '789123', 'iss': 'https://erp.example.com'}

OpenID User Info Endpoint

Request

GET /api/method/frappe.integrations.oauth2.openid_profile
Header: Authorization: Bearer valid_access_token

Response

{
    "sub": "1234567890",
    "name": "J. Doe",
    "given_name": "J",
    "family_name": "Doe",
    "iss": "https://erp.example.com",
    "picture": "https://erp.example.com/files/jdoe.jpg",
    "email": "j@doe.com",
    "iat": 1516239022,
    "exp": 1516242022,
    "aud": "client_id",
    "roles": ["System Manager", "Sales Manager"]
}

Introspect Token Endpoint

POST /api/method/frappe.integrations.oauth2.introspect_token
Header: Content-Type: application/x-www-form-urlencoded

Revoke token endpoint.

Params:

  • tokentypehint

    access_token or refresh_token, defaults to access_token if nothing is provided

  • token

    Access token or Refresh Token to be introspected. Depends on tokentypehint

Returns:

{
    "client_id": "511cb2ac2d",
    "trusted_client": 1,
    "active": true,
    "exp": 1619523326,
    "scope": "openid all",
    "sub": "1234567890",
    "name": "J. Doe",
    "given_name": "J",
    "family_name": "Doe",
    "iss": "https://erp.example.com",
    "picture": "https://erp.example.com/files/jdoe.jpg",
    "email": "j@doe.com",
    "iat": 1516239022,
    "exp": 1516242022,
    "aud": "511cb2ac2d",
    "roles": ["System Manager", "Sales Manager"]
}

OR

{
    "active": false,
    "_server_messages": "..."
}

Further Reading

Please check Guides / Integration / How To Set Up Oauth to see how to create a new OAuth 2 client.

Authors:

- Raffael Meyer (raffael@alyf.de)
- Revant Nandgaonkar (revant@castlecraft.in)

</access_token>