OAuthlib support for Python-Requests!

Overview

Requests-OAuthlib build-status coverage-status Documentation Status

This project provides first-class OAuth library support for Requests.

The OAuth 1 workflow

OAuth 1 can seem overly complicated and it sure has its quirks. Luckily, requests_oauthlib hides most of these and let you focus at the task at hand.

Accessing protected resources using requests_oauthlib is as simple as:

>>> from requests_oauthlib import OAuth1Session
>>> twitter = OAuth1Session('client_key',
                            client_secret='client_secret',
                            resource_owner_key='resource_owner_key',
                            resource_owner_secret='resource_owner_secret')
>>> url = 'https://api.twitter.com/1/account/settings.json'
>>> r = twitter.get(url)

Before accessing resources you will need to obtain a few credentials from your provider (e.g. Twitter) and authorization from the user for whom you wish to retrieve resources for. You can read all about this in the full OAuth 1 workflow guide on RTD.

The OAuth 2 workflow

OAuth 2 is generally simpler than OAuth 1 but comes in more flavours. The most common being the Authorization Code Grant, also known as the WebApplication flow.

Fetching a protected resource after obtaining an access token can be extremely simple. However, before accessing resources you will need to obtain a few credentials from your provider (e.g. Google) and authorization from the user for whom you wish to retrieve resources for. You can read all about this in the full OAuth 2 workflow guide on RTD.

Installation

To install requests and requests_oauthlib you can use pip:

$ pip install requests requests_oauthlib
Comments
  • The URL should always be native.

    The URL should always be native.

    This should resolve the issue that @michaelhelmick had in kennethreitz/requests#1366. In short, the keys for doing Transport Adapter lookups are native strings, but requests_oauthlib turns the URL into bytes. Not helpful. This prevents that behaviour while avoiding regressing kennethreitz/requests#1252.

    opened by Lukasa 50
  • Refresh Tokens + Web App Example?

    Refresh Tokens + Web App Example?

    I've been trying to wrap my head around refresh tokens and the OAuth2 web example (https://requests-oauthlib.readthedocs.org/en/latest/examples/real_world_example.html). Would it be possible to provide an example usage of refresh tokens in that context? Where in the code it would go, etc?

    Just a suggestion! :grin:

    docs 
    opened by bryanveloso 24
  • Fix dependencies

    Fix dependencies

    Each time the oauthlib library is changed, I have to change my code because the behaviour changes. So, can you fix the version of the libraries that you use? At least you should fix the mayor number of the dependencies since when this number changes, the API often changes.

    opened by aitormagan 23
  • AttributeError: 'PreparedRequest' object has no attribute 'data'

    AttributeError: 'PreparedRequest' object has no attribute 'data'

    @Lukasa asked me to raise this error in this repo as well:

    Hey guys, just wanted to bring this issue forward. With requests 1.0.0 So the error came from when I went to test Twython with the new requests release.

    And because it bothered me, I removed self from all variables that used self like self.callback_url Also, I striped the code out of the actual function get_authentication_tokens (so that's why you'll see ref to that function in the traceback)

    import requests
    from requests_oauthlib import OAuth1
    
    app_key = u'SUPERDUPERSECRETKEY'
    app_secret = u'SUPERDUPERSECRETSECRET'
    
    callback_url = 'http://example.com'
    headers = {'User-Agent': 'Twython v2.5.5'}
    auth = OAuth1(app_key, app_secret,
                                   signature_type='auth_header')
    
    request_args['oauth_callback'] = callback_url
    response = requests.get('https://api.twitter.com/oauth/request_token', params=request_args, headers=headers, auth=auth)
    
    if response.status_code != 200:
        raise TwythonAuthError("Seems something couldn't be verified with your OAuth junk. Error: %s, Message: %s" % (response.status_code, response.content))
    
    request_tokens = dict(parse_qsl(response.content))
    if not request_tokens:
        raise TwythonError('Unable to decode request tokens.')
    
    oauth_callback_confirmed = request_tokens.get('oauth_callback_confirmed') == 'true'
    
    auth_url_params = {
        'oauth_token': request_tokens['oauth_token'],
    }
    
    # Use old-style callback argument if server didn't accept new-style
    if callback_url and not oauth_callback_confirmed:
        auth_url_params['oauth_callback'] = callback_url
    
    request_tokens['auth_url'] = 'https://api.twitter.com/oauth/authenticate?' + urllib.urlencode(auth_url_params)
    
    return request_tokens
    

    Anyways, when I try to run this code I get the error:

    Traceback (most recent call last):
      File "twython.py", line 585, in <module>
        auth_props = t.get_authentication_tokens()
      File "twython.py", line 271, in get_authentication_tokens
        response = requests.get('https://api.twitter.com/oauth/request_token', params=request_args, headers=headers, auth=self.auth)
      File "/Users/mikehelmick/.virtualenv/twython/lib/python2.7/site-packages/requests/api.py", line 49, in get
        return request('get', url, **kwargs)
      File "/Users/mikehelmick/.virtualenv/twython/lib/python2.7/site-packages/requests/api.py", line 38, in request
        return session.request(method=method, url=url, **kwargs)
      File "/Users/mikehelmick/.virtualenv/twython/lib/python2.7/site-packages/requests/sessions.py", line 253, in request
        prep = req.prepare()
      File "/Users/mikehelmick/.virtualenv/twython/lib/python2.7/site-packages/requests/models.py", line 200, in prepare
        p.prepare_auth(self.auth)
      File "/Users/mikehelmick/.virtualenv/twython/lib/python2.7/site-packages/requests/models.py", line 336, in prepare_auth
        r = auth(self)
      File "/Users/mikehelmick/.virtualenv/twython/lib/python2.7/site-packages/requests_oauthlib/core.py", line 41, in __call__
        decoded_body = extract_params(r.data)
    AttributeError: 'PreparedRequest' object has no attribute 'data'
    
    opened by michaelhelmick 20
  • Sort out the documentation

    Sort out the documentation

    We've finally got some stuff up on ReadTheDocs, but it's not great. This issue is an umbrella tracking issue, so I can tick stuff off as we go. Here are some things we need to do: feel free to suggest more in the comments.

    • [ ] Sort out the README (partly in #46).
    • [x] Sort out Intersphinx (see #47).
    • [ ] Remove lengthy worked examples from the API docs.
    • [ ] Put lengthy worked examples somewhere else (see #46).
    • [ ] Improve clarity of API docs.
    • [ ] Improve landing page.
    • [ ] Tutorials. Lots of tutorials. A whole tutorials section (see #49).
    enhancement Contributor Friendly docs 
    opened by Lukasa 19
  • Forcing HTTPBasicAuth in fetch_token results in invalid_request from Google

    Forcing HTTPBasicAuth in fetch_token results in invalid_request from Google

    Using Flask_OAuth2_Login (for example):

      def login(self):
        sess = self.session()
    
        # Get token
        try:
          sess.fetch_token(
            self.token_url,
            code=request.args["code"],
            client_secret=self.client_secret,
          )
    

    results in:

      File "/lib/python2.7/site-packages/flask_oauth2_login/base.py", line 56, in login
        client_secret=self.client_secret,
      File "/lib/python2.7/site-packages/requests_oauthlib/oauth2_session.py", line 232, in fetch_token
        self._client.parse_request_body_response(r.text, scope=self.scope)
      File "/lib/python2.7/site-packages/oauthlib/oauth2/rfc6749/clients/base.py", line 409, in parse_request_body_response
        self.token = parse_token_response(body, scope=scope)
      File "/lib/python2.7/site-packages/oauthlib/oauth2/rfc6749/parameters.py", line 376, in parse_token_response
        validate_token_parameters(params)
      File "/lib/python2.7/site-packages/oauthlib/oauth2/rfc6749/parameters.py", line 383, in validate_token_parameters
        raise_from_error(params.get('error'), params)
      File "/lib/python2.7/site-packages/oauthlib/oauth2/rfc6749/errors.py", line 271, in raise_from_error
        raise cls(**kwargs)
    

    This is due to a change introduced in 0.6.0 in oauth2_session.py/fetch_token:

    auth = auth or requests.auth.HTTPBasicAuth(username, password)
    

    whereas previously auth was allowed to remain empty. Google responds with:

    {
      "error" : "invalid_request"
    }
    

    and everything falls down from there. Commenting out the line allows the request to complete normally.

    opened by butlertron 17
  • Only pass bytes to urllib3.

    Only pass bytes to urllib3.

    This should resolve requests-oauthlib's problems with uploading binary data, as demonstrated in kennethreitz/requests#1252.

    @sigmavirus24, can I get code review on this before I merge into master?

    opened by Lukasa 17
  • OAuth2Session(client_id=client_id, client=client) return 403 error in production environment

    OAuth2Session(client_id=client_id, client=client) return 403 error in production environment

    OAuth2Session(client_id=client_id, client=client) return 403 error in production environment. Works good in localhost

    In localhost and production environment I disabled OAUTHLIB_INSECURE_TRANSPORT os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1'

    opened by ilGuccio 14
  • Fix spaces encoding in parameters

    Fix spaces encoding in parameters

    OAuth requires spaces to be encoded as %20 instead of + in order to generate a valid signature.

    Explained in http://troy.yort.com/2-common-problems-with-oauth-client-libraries/

    Before this patch, I was unable to use Yahoo!'s BOSS Placefinder API, which now seems to be responding correctly to me, instead of what it used to say:

    <?xml version='1.0' encoding='UTF-8'?>
    <yahoo:error xmlns:yahoo='http://yahooapis.com/v1/base.rng'
      xml:lang='en-US'>
      <yahoo:description>Please provide valid credentials. OAuth oauth_problem="signature_invalid", realm="yahooapis.com"</yahoo:description>
    </yahoo:error>
    
    opened by Xowap 14
  • Tidying up a bit for requests 1.0

    Tidying up a bit for requests 1.0

    I've added some initial tests and cleaned up the extension code a bit.

    Unfortunately this code depends heavily on an update to requests.models.py in which lines 200 & 201 are swapped, i.e.

        p.prepare_auth(self.auth)
        p.prepare_body(self.data, self.files)
    

    becomes

        p.prepare_body(self.data, self.files)
        p.prepare_auth(self.auth)
    

    Why? Because we have no idea in auth whether body will soon be filled with files data or not and consequently it would be foolish to assume form encoded on empty body.

    I've not had time to look into what implications this might have for requests. Will send a PR when I have. @kennethreitz

    opened by ib-lundgren 14
  • Prepare 0.4.0 release

    Prepare 0.4.0 release

    The current 0.3.0 release is pretty old and we've fixed a few bugs, as well as added a massive amount of functionality. I want to get this library into a shape where we can pass it to Kenneth all he has to do is tag it and go. @ib-lundgren, @sigmavirus24, is there anything we need to do?

    opened by Lukasa 13
  • Scope changes with Microsoft services & `offline_access`

    Scope changes with Microsoft services & `offline_access`

    I'm trying to set up OAuth2 for unattended access to Microsoft IMAP servers - the refresh_token is important here.

    When providing a request scope set as follows:

    • offline_access
    • https://outlook.office.com/User.Read
    • https://outlook.office.com/IMAP.AccessAsUser.All

    The service responds with the following (i.e: offline_access is removed):

    • https://outlook.office.com/User.Read
    • https://outlook.office.com/IMAP.AccessAsUser.All

    This results in a warning being raised.

    Traceback
    Traceback (most recent call last):
      File "./oauth2-test.py", line 51, in <module>
        token = oauth.fetch_token(token_url, client_secret=client_secret, authorization_response=redirect_response)
      File "/usr/lib/python3.8/site-packages/requests_oauthlib/oauth2_session.py", line 366, in fetch_token
        self._client.parse_request_body_response(r.text, scope=self.scope)
      File "/usr/lib/python3.8/site-packages/oauthlib/oauth2/rfc6749/clients/base.py", line 427, in parse_request_body_response
        self.token = parse_token_response(body, scope=scope)
      File "/usr/lib/python3.8/site-packages/oauthlib/oauth2/rfc6749/parameters.py", line 441, in parse_token_response
        validate_token_parameters(params)
      File "/usr/lib/python3.8/site-packages/oauthlib/oauth2/rfc6749/parameters.py", line 471, in validate_token_parameters
        raise w
    Warning: Scope has changed from "https://outlook.office.com/User.Read https://outlook.office.com/IMAP.AccessAsUser.All offline_access" to "https://outlook.office.com/User.Read https://outlook.office.com/IMAP.AccessAsUser.All".
    

    Apparently the offline_access scope should never be returned by Microsoft services, as it's not actually a useful scope for accessing resources (ref).


    My current approach (which isn't ideal), is as follows:

    oauth = OAuth2Session(client_id, redirect_uri=redirect_uri, scope=scope)
    authorization_url, state = oauth.authorization_url(authorize_url)
    
    # remove the `offline_access` scope directly / by hand
    oauth.scope.remove('offline_access')
    
    # ... submit the request to authorization_url, and retrieve the token
    redirect_response = ...
    token = oauth.fetch_token(token_url, client_secret=client_secret, authorization_response=redirect_response)
    

    I'm aware of OAUTHLIB_RELAX_TOKEN_SCOPE (link), but that seems perhaps a little over-permissive.

    Perhaps one of the following would be a good idea?

    • A more generic mechanism to permit accepting scope changes
    • A way to supply the expected response set of scopes
    • A list of "I don't mind if these aren't grated" scopes
    opened by attie-argentum 0
  • Requirements out of date

    Requirements out of date

    The version of requests pinned by this library is 2.26.0.

    requests==2.26.0

    Currently, requests is at version 2.28.1. Please consider updating the requirements or leaving the requests version unpinned, as it is in requirements.in.

    Thanks!

    opened by MooseV2 1
  • Add refresh token exception hook to list of compliance hooks?

    Add refresh token exception hook to list of compliance hooks?

    I have a use case were sometimes my stored users' tokens expire and thus they need to reauthenticate into whatever app I'm trying to request data from.

    In these cases I get an error 400 from the api I'm requesting from and simply ask my user to reauthenticate using my usual oAuth2 flow.

    Since I don't know exactly when a token will expire my current approach is to wrap every request I make to these apis in a try catch and then handle the exception from there.

    I noticed that there are some compliance hooks and was wondering if it would be appropriate to add something like a refresh_token_exception hook to be called if the library runs into an error code while refreshing the token as opposed to just calling the usual refresh_token_response with the raw response.

    If this makes sense I would be happy to submit a pull request for it

    https://github.com/requests/requests-oauthlib/blob/3a2a852e33c691c7e793300ce366a01b6e4b3848/requests_oauthlib/oauth2_session.py#L94

    opened by ramennoodles20 0
  • `oauth2_session.OAuth2Session.refresh_token` creates infinite loop with Exchange Online when token expires

    `oauth2_session.OAuth2Session.refresh_token` creates infinite loop with Exchange Online when token expires

    Using an expired access token with Microsoft Exchange Online for sending Outlook e-mails under exchangelib runs into an infinite loop during the token refresh just because the app creds are bundled into the body structure instead of being provided as a (client_id, client_secret) pair given the auth object. (context)

    Is it possible to automatically create and use an auth object (if it isn't provided explicitly by the caller) based on these refresh kwargs (if they contain the client creds) right inside of oauth2_session.OAuth2Session.refresh_token so the /token POST will benefit from this implicit auth object and make the server auth working with Exchange?

    opened by cmin764 0
  • WIP: Add PKCE support with oauthlib 3.2.0

    WIP: Add PKCE support with oauthlib 3.2.0

    Since oauthlib 3.2.0 now supports PKCE for Clients (https://github.com/oauthlib/oauthlib/releases/tag/v3.2.0), this PR proposes a first implementation . Any feedbacks are welcome, I'm not sure it is production ready yet.

    Change from: session = OAuth2Session(client_id) to session = OAuth2Session(app.client_id, pkce="S256")

    And be sure to reuse the same session for fetch_token, as it will need to remember code_verifier. It is not really practical beyond PoC, so any suggestions are welcome.

    opened by JonathanHuot 0
Releases(v1.3.1)
  • v1.3.1(Jan 29, 2022)

    What's Changed

    • Add Support for OAuth Mutual TLS (draft-ietf-oauth-mtls) by @danielfett in https://github.com/requests/requests-oauthlib/pull/389
    • Linkedin compliance removal & LinkedIn Example update/fix by @jtroussard in https://github.com/requests/requests-oauthlib/pull/397
    • docs: Fix typos in token refresh section of oauth2 worflow by @momobel in https://github.com/requests/requests-oauthlib/pull/413
    • Add eBay compliance fix by @craiga in https://github.com/requests/requests-oauthlib/pull/456
    • Fix Docs generation - Improve Pipeline by @JonathanHuot in https://github.com/requests/requests-oauthlib/pull/459
    • Fix Sphinx error for oauth1 fetch_token documentation by @JonathanHuot in https://github.com/requests/requests-oauthlib/pull/462
    • Move from Travis to GitHub Actions by @JonathanHuot in https://github.com/requests/requests-oauthlib/pull/470
    • Add Spotify OAuth 2 Tutorial by @odysseusmax in https://github.com/requests/requests-oauthlib/pull/471
    • Update documentation for Python 3 by @gschizas in https://github.com/requests/requests-oauthlib/pull/435
    • docs: add the link to the Application Registration Portal by @Abdelkrim in https://github.com/requests/requests-oauthlib/pull/425
    • docs: rearrange and link spotify tutorial by @odysseusmax in https://github.com/requests/requests-oauthlib/pull/472
    • Update google.rst by @mrwangjianhui in https://github.com/requests/requests-oauthlib/pull/454
    • Add Python 3.8 & 3.9 as supported versions by @kaxil in https://github.com/requests/requests-oauthlib/pull/442
    • Update build badge for GitHub Actions by @hugovk in https://github.com/requests/requests-oauthlib/pull/475

    New Contributors

    • @danielfett made their first contribution in https://github.com/requests/requests-oauthlib/pull/389
    • @jtroussard made their first contribution in https://github.com/requests/requests-oauthlib/pull/397
    • @momobel made their first contribution in https://github.com/requests/requests-oauthlib/pull/413
    • @craiga made their first contribution in https://github.com/requests/requests-oauthlib/pull/456
    • @JonathanHuot made their first contribution in https://github.com/requests/requests-oauthlib/pull/459
    • @odysseusmax made their first contribution in https://github.com/requests/requests-oauthlib/pull/471
    • @gschizas made their first contribution in https://github.com/requests/requests-oauthlib/pull/435
    • @Abdelkrim made their first contribution in https://github.com/requests/requests-oauthlib/pull/425
    • @mrwangjianhui made their first contribution in https://github.com/requests/requests-oauthlib/pull/454
    • @kaxil made their first contribution in https://github.com/requests/requests-oauthlib/pull/442
    • @hugovk made their first contribution in https://github.com/requests/requests-oauthlib/pull/475

    Full Changelog: https://github.com/requests/requests-oauthlib/compare/v1.3.0...v1.3.1

    Source code(tar.gz)
    Source code(zip)
  • v1.3.0(Nov 2, 2021)

  • v1.2.0(Nov 2, 2021)

    • This project now depends on OAuthlib 3.0.0 and above. It does not support versions of OAuthlib before 3.0.0.
    • Updated oauth2 tests to use 'sess' for an OAuth2Session instance instead of auth because OAuth2Session objects and methods acceept an auth paramether which is typically an instance of requests.auth.HTTPBasicAuth
    • OAuth2Session.fetch_token previously tried to guess how and where to provide "client" and "user" credentials incorrectly. This was incompatible with some OAuth servers and incompatible with breaking changes in oauthlib that seek to correctly provide the client_id. The older implementation also did not raise the correct exceptions when username and password are not present on Legacy clients.
    • Avoid automatic netrc authentication for OAuth2Session.
    Source code(tar.gz)
    Source code(zip)
  • v1.1.0(Jan 9, 2019)

    • Adjusted version specifier for oauthlib dependency: this project is not yet compatible with oauthlib 3.0.0.
    • Dropped dependency on nose.
    • Minor changes to clean up the code and make it more readable/maintainable.
    Source code(tar.gz)
    Source code(zip)
  • v1.0.0(Jun 4, 2018)

    • Removed support for Python 2.6 and Python 3.3. This project now supports Python 2.7, and Python 3.4 and above.
    • Added several examples to the documentation.
    • Added plentymarkets compliance fix.
    • Added a token property to OAuth1Session, to match the corresponding token property on OAuth2Session.
    Source code(tar.gz)
    Source code(zip)
JSON Web Token implementation in Python

PyJWT A Python implementation of RFC 7519. Original implementation was written by @progrium. Sponsor If you want to quickly add secure token-based aut

José Padilla 4.5k Jan 09, 2023
Python's simple login system concept - Advanced level

Simple login system with Python - For beginners Creating a simple login system using python for beginners this repository aims to provide a simple ove

Low_Scarlet 1 Dec 13, 2021
REST implementation of Django authentication system.

djoser REST implementation of Django authentication system. djoser library provides a set of Django Rest Framework views to handle basic actions such

Sunscrapers 2.2k Jan 01, 2023
A Python library for OAuth 1.0/a, 2.0, and Ofly.

Rauth A simple Python OAuth 1.0/a, OAuth 2.0, and Ofly consumer library built on top of Requests. Features Supports OAuth 1.0/a, 2.0 and Ofly Service

litl 1.6k Dec 08, 2022
MikroTik Authentication POCs

Proofs of concept which successfully authenticate with MikroTik Winbox and MAC Telnet servers running on RouterOS version 6.45.1+

Margin Research 56 Dec 08, 2022
Implementation of Supervised Contrastive Learning with AMP, EMA, SWA, and many other tricks

SupCon-Framework The repo is an implementation of Supervised Contrastive Learning. It's based on another implementation, but with several differencies

Ivan Panshin 132 Dec 14, 2022
Auth for use with FastAPI

FastAPI Auth Pluggable auth for use with FastAPI Supports OAuth2 Password Flow Uses JWT access and refresh tokens 100% mypy and test coverage Supports

David Montague 95 Jan 02, 2023
Authentication testing framework

What is this This is a framework designed to test authentication for web applications. While web proxies like ZAProxy and Burpsuite allow authenticate

DigeeX 140 Jul 06, 2022
Cack facebook tidak login

Cack facebook tidak login

Angga Kurniawan 5 Dec 12, 2021
A wagtail plugin to replace the login by an OAuth2.0 Authorization Server

Wagtail OAuth2.0 Login Plugin to replace Wagtail default login by an OAuth2.0 Authorization Server. What is wagtail-oauth2 OAuth2.0 is an authorizatio

Gandi 7 Oct 07, 2022
OpenConnect auth creditials collector.

OCSERV AUTH CREDS COLLECTOR V1.0 Зачем Изначально было написано чтобы мониторить какие данные вводятся в интерфейс ханипота в виде OpenConnect server.

0 Sep 23, 2022
A Python tool to generate and refresh Amazon access tokens.

amazon_auth A Python tool to generate and refresh Amazon access tokens. Description This tool generates and outputs Amazon access and refresh tokens f

15 Nov 21, 2022
蓝鲸用户管理是蓝鲸智云提供的企业组织架构和用户管理解决方案,为企业统一登录提供认证源服务。

蓝鲸用户管理 简体中文 | English 蓝鲸用户管理是蓝鲸智云提供的企业组织架构和用户管理解决方案,为企业统一登录提供认证源服务。 总览 架构设计 代码目录 功能 支持多层级的组织架构管理 支持通过多种方式同步数据:OpenLDAP、Microsoft Active Directory(MAD)

腾讯蓝鲸 35 Dec 14, 2022
python-social-auth and oauth2 support for django-rest-framework

Django REST Framework Social OAuth2 This module provides OAuth2 social authentication support for applications in Django REST Framework. The aim of th

1k Dec 22, 2022
Library - Recent and favorite documents

Thingy Thingy is used to quickly access recent and favorite documents. It's an XApp so it can work in any distribution and many desktop environments (

Linux Mint 23 Sep 11, 2022
Kube OpenID Connect is an application that can be used to easily enable authentication flows via OIDC for a kubernetes cluster

Kube OpenID Connect is an application that can be used to easily enable authentication flows via OIDC for a kubernetes cluster. Kubernetes supports OpenID Connect Tokens as a way to identify users wh

7 Nov 20, 2022
CheckList-Api - Created with django rest framework and JWT(Json Web Tokens for Authentication)

CheckList Api created with django rest framework and JWT(Json Web Tokens for Aut

shantanu nimkar 1 Jan 24, 2022
Ready to use and customizable Authentications and Authorisation management for FastAPI ⚡

AuthenticationX 💫 Ready-to-use and customizable Authentications and Oauth2 management for FastAPI ⚡

Yasser Tahiri 408 Jan 05, 2023
Spotify User Token Generator Template

Spotify User Token Generator Template Quick Start $ pip3 install -r requirements

Arda Soyer 1 Feb 01, 2022
Todo app with authentication system.

todo list web app with authentication system. User can register, login, logout. User can login and create, delete, update task Home Page here you will

Anurag verma 3 Aug 18, 2022