Try ERPNext Try Frappe Cloud Buy Support Partners Foundation

Token Based Authentication Not working

Hi, I am trying to make use of erpnext token based authentication, but instead of generating the api key & api secret from web console. I was using the RPC api call to generate api secret.


But the issue which I am facing is that when I retrieve the api_secret using the RPC api call, that api_secret is not working and does not authenticate me. On the other hand if I get the api_secret using the web UI that is working.

I am unable to figure out what is going wrong. I am using erpnext v13.

How do you authenticate to the API when you call the generate_keys method?

I make use of the token based authorization while calling the generate_keys method. The token which I am using is the api key & api secret of the administrator.

Header which I am using for generate_keys method call using administrator keys:

Authorization: token qfb47df2cbcf1k4:ce41561b657gg38

The only thing I can think of is that you might be inadvertently using the secret as the key and the key as the secret or maybe the administrators key and the user’s secret.

The number of times I have done that kind of thing makes me hate myself and wonder if I would not be better getting a job flipping hamburgers.

I have verified it multiple times, I am using the correct api key and the corresponding api secret returned for that user.

The thing I find weird is that api secret generated from the web is working whereas the api secret returned from the api is not, for the same user. My assumption is that it is not updating the database when I make an api call, but as the tabuser table has ********** for api secret, i an unable to check. Is there any way to check that or is it saved somewhere else?

I seem to remember screwing with that many months ago, and decided it was simply defective.

Maybe you can find the Python code and stick in a few logging statements to see if the API and web calls are treated differently.

Yeah this is a good suggestion, I will try that and see if there is any difference. Thanks

hi @sanket,
How does your exploration go?

I tried the same RPC requests but always return 404 DoesNotExistError (the user not found).
Also tried the command line also the same, user not found.

I use Administrator key:secret to run the method. And also tried other user (with full permissions) key:secret. All the same 404 error.

Is there any other way to generate token for a user?

I use version 12

Is this a HTTP GET request? Try using POST request.

If your method changes the state of the database, use POST . After a successful POST request, the framework will automatically call frappe.db.commit() to commit the changes to the database.
Read more:

GET and POST are the same.
404 Not Found

What should be filled in the user_name ?
I tried email, name, fullname, username nothing succeeded.

I’ll detail what works for me.

To start with, change the following to suit your current set up and then paste into your Unix command line:

export ERP_HOST="";
export OTHER_USER="";
export TOKEN="72d03ddb0c9e010:0261aee60ce2190";
export ADMIN_PWD="sEcr3t";

With that done, all the rest of the commands below ought to just work as is, when pasted into the command line.

curl --location --request \
    POST "https://${ERP_HOST}/api/method/frappe.core.doctype.user.user.generate_keys?user=${OTHER_USER}" \
--header "Authorization: token ${TOKEN}";

The key and secret are of the administrator. I always use the UI to get the first token (see note below). However there is another way that I explain below.

The return result is:

    "message": {
        "api_secret": "bd18c496424153a"

Clearly that is only the secret. To get the key, run:

curl --location \
    "https://${ERP_HOST}/api/resource/User/${OTHER_USER}" \
--header "Authorization: token ${TOKEN}";

The response for that will be:

    "data": {
        "name": "${OTHER_USER}",

        "api_key": "63e2ac4283fede7",
        "api_secret": "***************",

        "doctype": "User",
        "stuff": "lots of stuff",
        "etc": "blabbity blabbity blaaa"  

If you find that overwhelming you can install the wonderful jq utility and run:

curl --silent --location \
   "https://${ERP_HOST}/api/resource/User/${OTHER_USER}" \
   --header "Authorization: token ${TOKEN}" \
   | jq -r '.data.api_key';

Which will get you:


Pay attention to the --location switch that means, “Automatically follow HTTP redirects”. It could be the reason for your "File not found: errors.

I’m guessing you would like to obviate the UI entirely. To do that you still have to log in, but there is an alternate way to it:

Here it is:

curl --location --cookie-jar oreo.txt \
     --request POST "https://${ERP_HOST}/api/method/login" \
     --header "Content-Type: application/json" \
     --data-raw "{
         \"usr\": \"Administrator\",
         \"pwd\": \"${ADMIN_PWD}\"

This returns, first a bit of meta information in standard out …

    "message": "Logged In",
    "home_page": "/desk",
    "full_name": "Administrator"

… and second, a cookie file …

# Netscape HTTP Cookie File
# This file was generated by libcurl! Edit at your own risk.

#HttpOnly_    FALSE   /   TRUE    1616357367   sid         937415344dd12179acxxxxxxxxxa4fb3ddb446ab7843a529e84d8aaa    FALSE   /   TRUE             0   system_user yes    FALSE   /   TRUE             0   full_name   Administrator    FALSE   /   TRUE             0   user_id     Administrator    FALSE   /   TRUE             0   user_image  

The only cookie that matters is the first: sid, but there’s no reason not to keep using the file as is. Now, it is merely necessary to include --cookie oreo.txt in all future calls:

curl --cookie oreo.txt \
   --request POST \

    "message": {
        "api_secret": "fadde32b362577b"
curl --silent --cookie oreo.txt \
    "https://${ERP_HOST}/api/resource/User/${OTHER_USER}" \
    | jq -r '.data.api_key';


As an aside, as soon as I have completed the wizard after creating an installation, I log in as Administrator, generate a key/pair, do a few other things, save the pair in a private/files file, and then do a full database backup so that, no matter what happens, I don’t have run that horrible wizard again.


export KEYS="63e2ac4283fede7:fadde32b362577b";
1 Like

I still can’t get pass this.
Always return html and <title>Not Permitted</title>

Second related question:
What if I don’t have UI access to the site to generate user token for authorization?

What are you using to substitute those three values?

Performing the substitutions as indicated…

export ERP_HOST="";
export OTHER_USER="";
export TOKEN="72d03ddb0c9e010:0261aee60ce2190";
export ADMIN_PWD="sEcr3t";

… into this line …

curl --location \
 “https://${ERP_HOST}/api/resource/User/${OTHER_USER}” \
     --header “Authorization: token ${TOKEN}”;

… should produce a line like this …

curl --location \
   "” \
      --header “Authorization: token 72d03ddb0c9e010:0261aee60ce2190”;

Please execute this line:

echo -e “Server: ${ERP_HOST}. User: ${OTHER_USER}. Token ${TOKEN}”;

If you get …

“Server: User: Token 72d03ddb0c9e010:0261aee60ce2190”

… then your variable substitutions are working correctly.

If you get …

“Server: . User: . Token ”

… then you need to be sure you understand how to do variable substitution for your system.

Are you running a Windows machine?

yes I get this:

“Server: User: Token 09287xxxxxxxxxxx:56b39zzzzzzzzzz”

I’m also able to get this:

{ “message”: { “api_secret”: “657a978yyyyyyyyy” } }

I’m on CentOS

So, it’s working! Yes?

No :slight_smile:

I get the Not Permitted when runnning the .../api/resource/... (to get the key)

Looking at the response header:

Server: nginx/1.14.1
Date: Sat, 20 Mar 2021 15:12:15 GMT
Content-Type: text/html; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Vary: Accept-Encoding
X-Page-Name: message
X-From-Cache: False
Link: </assets/frappe/js/lib/jquery/jquery.min.js>; rel=preload; as=script,</assets/js/frappe-web.min.js>; rel=preload; as=script,</assets/js/bootstrap-4-web.min.js>; rel=preload; as=script,</website_script.js>; rel=preload; as=script,</assets/js/erpnext-web.min.js>; rel=preload; as=script,</assets/css/frappe-web-b4.css>; rel=preload; as=style,</assets/css/erpnext-web.css>; rel=preload; as=style,</assets/css/base_theme_web.min.css>; rel=preload; as=style,</assets/css/base_theme_fonts.min.css>; rel=preload; as=style
Set-Cookie: sid=Guest; Expires=Tue, 23-Mar-2021 20:42:14 GMT; Path=/
Set-Cookie: system_user=yes; Path=/
Set-Cookie: full_name=Guest; Path=/
Set-Cookie: user_id=Guest; Path=/
Set-Cookie: user_image=; Path=/
Content-Encoding: gzip

Does the sid=Guest have to do with this error?

Let me clarify:

export ERP_HOST=""; #--> this is target site?
export OTHER_USER=""; #--> this is the user in target site created as api user?
export TOKEN="72d03ddb0c9e010:0261aee60ce2190"; #--> this is the token for that other_user (from User>API Access>Generate Keys)?

What I’m doing is using the Administrator token pair $(TOKEN) to get the secret and key for ${OTHER_USER}

You can only “get” keys and secrets. You cannot “set” them.


# Target Site
export ERP_HOST="";
# The user in target site for whom you are getting a token
export OTHER_USER=""; 
# Token of user who is getting a token for OTHER_USER.
export TOKEN="72d03ddb0c9e010:0261aee60ce2190";

This means that you have successfully obtained a token secret for “”.

Your administrator token is : 09287xxxxxxxxxxx:56b39zzzzzzzzzz

To use the API as you cannot use : 09287xxxxxxxxxxx:56b39zzzzzzzzzz.

The key, 09287xxxxxxxxxxx is only valid for the administrator. No one else.

You must connect a second time with :

curl --location \
    "https://${ERP_HOST}/api/resource/User/${OTHER_USER}" \
--header "Authorization: token ${TOKEN}";

You’ll get :

    "data": {
        "name": "${OTHER_USER}",

        "api_key": "63e2ac4283fede7",
        "api_secret": "***************",

        "doctype": "User",
        "stuff": "lots of stuff",
        "etc": "blabbity blabbity blaaa"  

Search in there for api_key, and put the two together.

Such as :