Try ERPNext Try Frappe Cloud Buy Support Partners Foundation

Best way to connect a Angular App with ERPNext

Hello guys,

I was wondering what the best way is to implement Authentification from an Angular App with User Credentials (Username + PW) AND OR an ID (like an RFID chip [currently implemented with the username instead of an ID]).

Attempt 1:
My first attempt was to create an API_Key+Secret by my own (but here I am still using the Username as an ID):

@frappe.whitelist(allow_guest=True)
def get_login_data(username):
try:
    user_details = frappe.get_doc("User", username)
except:
    frappe.throw(frappe._(""), frappe.DoesNotExistError)

api_secret = frappe.generate_hash(length=15)
# if api key is not set generate api key
if not user_details.api_key:
    api_key = frappe.generate_hash(length=15)
    user_details.api_key = api_key

user_details.api_secret = api_secret
user_details.save(ignore_permissions=True)

return {"api_key": user_details.api_key, "api_secret": api_secret}

This is working pretty fine the only problem I have here is that the User needs to generate the first key- secret pair manually in the UI.
Is there a way arround this problem?

Attempt 2:
So the second thing i tried was to create a Session with a Cookie:

@frappe.whitelist(allow_guest=True)
def generateCookie(username, resume=False):
    user = frappe.get_doc("User", username)
    full_name = " ".join(filter(None, [user.first_name, user.last_name]))
    make_session(user, False, full_name)
    set_user_info(user, full_name, False)

def make_session(user, full_name, resume):
    frappe.local.session_obj = Session(user=user, resume=resume, full_name=full_name, user_type=user.user_type)
    print(frappe.local.session_obj.data)
    frappe.local.session = frappe.local.session_obj.data

def set_user_info(user, full_name, resume=False):
    frappe.local.cookie_manager.init_cookies()

    if user.user_type=="Website User":
        frappe.local.cookie_manager.set_cookie("system_user", "no")
        if not resume:
            frappe.local.response["message"] = "No App"
    else:
        frappe.local.cookie_manager.set_cookie("system_user", "yes")
        if not resume:
            frappe.local.response['message'] = 'Logged In'
            frappe.local.response["home_page"] = "/desk"
            frappe.local.response["cookie"] = frappe.local.session.get('sid')

    if not resume:
        frappe.response["full_name"] = full_name

    frappe.local.cookie_manager.set_cookie("full_name", full_name)

So the thing is I want a user to be able to log in here, but when a guest calls this method the SessionManager recognizes him as a Guest.
That means that he will return a sid with “guest” inside. But I want a correct SID instead.

Would it be possible to create a SID for a given user and to return it to the Angular App?

These were my attempts to make a custom Login.
I am not very happy with them, what do you guys think?
Are there better solutions?

Help would be really appreciated!

check this if it helps. Add Single Page Application to website portal page

This app is served as a www page from frappe framework. That makes it part of same domain served as static files so there is no issue with cors and requests can be made with cookie already there in browser for rest of the site.

Thank you for your fast reply! @revant_one

This sounds like a nice solution but in my particular case I need to use it as a standalone App.
Do you have any suggestions how to implement it like this?

Check this out what i have used for one usecase.

@frappe.whitelist(allow_guest=True)
def login(usr,pwd):
    try:
        login_manager = frappe.auth.LoginManager()
        login_manager.authenticate(user=usr,pwd=pwd)
        login_manager.post_login()
    except frappe.exceptions.AuthenticationError:
        frappe.clear_messages()
        frappe.local.response["message"] =  {
            "success_key":0,
            "message":"Authentication Failed"
            }
        return

    api_generate=generate_keys(frappe.session.user)
    user = frappe.get_doc('User',frappe.session.user)
 
    frappe.response["message"] =	{
            "success_key": 1,
            "message":"Authentication Success"
            "sid": frappe.session.sid,
            "api_key":user.api_key,
            "api secret": api_generate
            }
    return

def generate_keys(user):
    user_details = frappe.get_doc('User', user)
    api_secret = frappe.generate_hash(length=15)
    # if api key is not set, generate api key
    if not user_details.api_key:
        api_key = frappe.generate_hash(length=15)
        user_details.api_key = api_key
    user_details.api_secret = api_secret
    user_details.save()
    return api_secret

You can generate api key and api secret everytime someone logs in.

and you don’t need to generate the first key-secret pair manually. it works for me using the above for the login. further requests work with api key and api secret.

Will this change the api_secret if same user logs in from 2 devices?

Actually yes. Only the last logged in device works. In my case, the other devices gets logged out automatically and redirects back to the login page.

thx @hashir that helps a lot

but i still have the usecase where i need to login a User only with an ID (RFID or in my current case his email address)

do you have any advice how I can solve this?

the ‘usr’ parameter in my above example can be username, email address or mobile number (from user doctype) provided that the following are enabled in the system settings

If you want seamless single sign on in your custom app, create OAuth Client and use OAuth 2.
That will make sure multiple devices can login and get separate bearer token for each device.

Still CORS needs to be configured if it is browser only app.

This is Angular/Ionic sample mobile app, it uses OAuth 2 code grant with refresh token.
It is using native http calls from mobile platform to workaround CORS.

you can make it into a PWA to be served from browser if you enable CORS for domain from where app will be served.

@revant_one i have my users registering themselves from the app as new users. Can OAuth be applied in this scenario?

As long as user is able to login using standard login page, it’ll work. It will also work if user selects social login option.

1 Like