I Made a Password Generator in Streamlit That’s Really Secure (Maybe Too Secure!)

4.3/5 - (7 votes)
I Made a Password Generator in Streamlit That’s Really Secure (Maybe Too Secure!)

Project Description

Both in my day job and personal life, I notice every day how important online security has become. Almost every part of our everyday lives are connected somehow to the Internet. And everyone of those connections requires (or should need) a password at the least.

The problem with that is that passwords are often difficult to remember if you want to make them secure. Furthermore, it is difficult to randomize them when we create them.

People think in patterns, and I am no different.

To solve this, I use a self-hosted password manager, but this is not an easy thing to set up. To bridge the gap between how most people look at passwords these days and the ideal way of keeping them, I developed a simple web application.

It uses my favorite framework Streamlit, and should not take you more than half an hour to create. Let’s jump in!

Feel free to check out the coding project on the Finxter Academy to get your Streamlit certificate and prove your newly-acquired skills to your future employers:

Install dependencies

The first thing I’ll do every time is installing all the needed dependencies.

For this project, we will need the Streamlit library, as well as Random_Word and requests.

As with my other tutorials, I will explain why we need these as we encounter them in our code.

pip install streamlit
pip install Random_Word
pip install requests

Step 1: Set up the directory structure

I use VS Code as my editor of choice, and setting up a new project folder there is a breeze.

Right-click in the Explorer menu on the left side and click New Folder. We only need one folder, .streamlit, to hold our Streamlit config.toml and secrets.toml files.

We will write all our code in a single app.py file.

├── .streamlit
│   ├── config.toml
│   └── secrets.toml
|── app.py

Step 2: Getting the API Ninja API key

For the passphrase aspect of our application, we’ll need an API key to get us random words. I used the API Ninja Random Word API, which you can find here. Signing up for their service is quick, easy and, most importantly, free!

Navigate to their registration page and follow the sign-up procedure. 

Afterward, you should be able to create your API key and get started right away. Your screen should look something like this, except for the API calls. Yours will be zero when you start this for the first time.

Once you’ve grabbed your API key, navigate back to your folder structure and create a variable in the secrets.toml file. Paste your key on the right side of the = sign, and you’re ready to start coding!

Tip: Don’t forget the quotes 🙂

Step 3: Import dependencies and Streamlit basic set-up

At the top of our file, we first import all the dependencies for our app to function. These are the three packages we installed earlier, as well as the secrets and string modules that come built-in to Python.

These I will use to generate an, as close to possible, random password.

#---PIP PACKAGES----#
import streamlit as st
from random_word import ApiNinjas
import requests


#---BUILT IN PYTHON PACKAGES----#
import secrets
import string

When building Streamlit applications, I find it easiest to first get their initial configuration done quickly, so I don’t have to worry about it later.

The first thing to do is define a couple of parameters we will need to initialize our application. That way, we can change them later if we are so inclined.

I do this in the  #---STREAMLIT SETTINGS---# block. 

#---STREAMLIT SETTINGS---#
page_title = "PW & PW-Sentence Generator"
page_icon = ":building_construction:"
layout = "centered"

The first function you will always need to call to get your Streamlit app to function is st.set_page_config(). If you forget this, Streamlit will get annoyed and start throwing errors.

#---PAGE CONFIG---#
st.set_page_config(page_title=page_title, page_icon=page_icon, layout=layout)


"#"
st.title(f"{page_icon} {page_title}")
"#"

The last block, #---STREAMLIT CONFIG HIDE---#, is optional but hides the “Made with Streamlit” banner at the bottom and the hamburger menu at the top if you want to.

#---STREAMLIT CONFIG HIDE---#
hide_st_style = """<style>
                #MainMenu {visibility : hidden;}
                footer {visibility : hidden;}
                header {visibility : hidden;}
                </style>
                """
st.markdown(hide_st_style, unsafe_allow_html=True)

When you’ve inserted all the code above and then called the command below, a browser window should open. 

👉 Tip: Make sure you run the command in the root of your application folder!


streamlit run app.py

After a few seconds and your view should resemble the one below

At this point, I usually let the application run in the background. This allows me to see all my changes to the code in semi-real-time on the browser window. 

Step 3: Defining our password generator function

The idea for the app is to have the ability for a user to choose if he wants a secure password or a secure passphrase. For this to work, we need functions that can generate those passwords or passphrases.

The current length for a secure password is between 14 and 16 randomized characters. I use a length of 14 for my function but you can change this very easily. 

#---PW GENERATOR FUNCTION--#
def generate_pw()->None:
    """Uses the string module to get the letters and digits that make up the alphabet used to generate the random characters. These characters are
    appended to the pwd string which is then assigned to the session_state variable [pw]"""
    letters = string.ascii_letters
    digits = string.digits  
    alphabet = letters + digits
    pwd_length = 14
    pwd = ''
    for i in range(pwd_length):
        pwd += ''.join(secrets.choice(alphabet))


    st.session_state["pw"] = pwd

This function first creates the letters and digits variables using the methods from the Python string module we imported earlier. These are then concatenated into one long string we call alphabet.

Next, I set the preferred password length at 14 characters. Feel free to change this.

The actual password generation occurs next. We concatenate random characters from this alphabet variable to the initially empty pwd variable.

According to the official documentation of the secrets module, “the secrets module provides access to the most secure source of randomness that your operating system provides”.

In other words, it comes as close to random as possible 🙂

The last thing we need to do is to assign the completed password to the st.session_state["pw"] variable. The st_session_state is, in essence, a dictionary. This dictionary is accessible by the Streamlit application during the entire time it is running. It allows passing variables, states to different parts of your app at different times.

In our case, we will use it to store the generated value of both the password and passphrase.

Step 4: Defining our passphrase generator functions

For generating the passphrase, I found that I needed two functions. It is possible to do it in one function, of course, but I found it a lot tidier to split it up.

It also seems more Pythonic to me, as it adheres to the tenet that every function should only do one thing.

#---PASSPHRASE GENERATOR FUNCTIONS---#


    #---GET RANDOM WORD---#
def get_random_word()->str:
    """Uses the API Ninja API to request a word string. 
       This string is then parsed to extract only the
       word and return it."""
    api_url = 'https://api.api-ninjas.com/v1/randomword'
    response = requests.get(api_url, headers={'X-Api-Key': st.secrets.API_NINJA})
    if response.status_code == requests.codes.ok:
        returned_word = response.text.split(":")
        returned_word = returned_word[1]
        returned_word  = returned_word[2:-2]
        return returned_word
    else:
        return "Error:", response.status_code, response.text

The first function, get_random_word() will use the API Ninja API to request and return a random word. For this, we need to call the API Key we stored earlier.

Streamlit has a secure method for this using the st.secrets method. We just add a . and then the name of the key we defined in the secrets.toml file. That way, we never expose the actual key.

The returned string has some extra characters attached to it that we need to strip off. We only need the actual word for our purpose. If something goes wrong with the request our else statement will trigger. This will return the error code and message.

 #---GENERATING THE PHRASE---#
def generate_ps()->None:
    """Uses the get_random_word function to request five words. These are concatenated into a string with dashes and then
    assigned these to the session_state variable [pw] """
    passphrase = ""
    for x in range(5):
        passphrase += f"{get_random_word()}-"
    passphrase_final = passphrase[:-1]  
    st.session_state["pw"] = passphrase_final

Our second function will create the actual passphrase we need. As with the password, you can choose the length for yourself. I set the length for mine at 5 words, which should be more than secure enough. 

To start, we define an empty string passphrase. Then we will call the get_random_word() function for the length I mentioned above. This will get us the number of words we need.

For each iteration of our loop, I concatenate the received words + a dash to the passphrase string. For that I use my favorite formatting method for Python strings, the f-string. When we’ve added all the words and dashes to the passphrase, we strip off the last dash.

The last thing we need to do here is to also assign the generated string to the st.session_state["pw"] variable.  This is the same thing we did with the password.

Step 5: Creating the Streamlit application

For the UI aspect of our application, we only need a few lines of code, most of them related to the layout.

#---MAIN PAGE---#


if "pw" not in st.session_state:
    st.session_state["pw"] = ''
   
"---"

The first part is ensuring the st.session_state["pw"] gets initialized.

We assign it to an empty string to prevent Streamlit from throwing an error. The 3 dashes between quotes are part of Streamlit’s magic commands. It will insert a horizontal divider in our application without any complicated code.

col1,col2 = st.columns([4,4], gap = "large")


with col1:
    st.caption("Secure password length is set at 14 chars.")
    st.button("Generate secure password", key = "pw_button", on_click = generate_pw)


with col2:
    st.caption("Secure passphrase length is set at 5 words.")
    st.button("Generate secure password sentence", key = "ps_button", on_click = generate_ps)
"#"

As we are allowing the user to choose between creating a password or a passphrase, we’ll need two columns to display these.

When calling st.columns with more than one argument, the width of each column needs to be in a list. That list will then function as the first argument. The gap keyword provides a relative vertical separation between the columns.

Defining the content for each column is easily done. We create a with-statement for each of the columns. All the code inside the statement then becomes part of that column. 

The st.caption method allows us to provide a hint or extra info. In my case, I use it to tell the user about the current length of the password or passphrase.

The second part of every column is the button used to generate our password/passphrase.

👉 Recommended: Streamlit Button – Ultimate Guide with Video

Streamlit’s st.button() method as a simple on_click keyword argument. You can pass a function to it that gets run when the button gets clicked. 

At the bottom, we use another bit of Streamlit magic. The #will insert a horizontal space/new line to create some separation.

"#"
ocol1, ocol2, ocol3 = st.columns([1,4,1])
with ocol1:
    ''
with ocol2:
    st.caption("Generated secure PW")
    "---"
    st.subheader(st.session_state["pw"])
    "---"
   
with ocol3:
    ''

The very last part of our application is more layout.

I define three columns to center the generated password. We can add the empty string to the first and third columns. These strings are once again part of Streamlit magic. The middle column will hold our generated password/passphrase.

Showing the password is easy. Because we’ve assigned the generated values to the st.session_state["pw"] variable, we can just call it.

Step 6: Putting it all together

If everything went well your completed code will look similar to my code below.

#---PIP PACKAGES----#
import streamlit as st
from random_word import ApiNinjas
import requests


#---BUILT IN PYTHON PACKAGES----#
import secrets
import string


#---STREAMLIT SETTINGS---#
page_title = "PW & PW-Sentence Generator"
page_icon = ":building_construction:"
layout = "centered"


#---PAGE CONFIG---#
st.set_page_config(page_title=page_title, page_icon=page_icon, layout=layout)


"#"
st.title(f"{page_icon} {page_title}")
"#"


#---STREAMLIT CONFIG HIDE---#
hide_st_style = """<style>
                #MainMenu {visibility : hidden;}
                footer {visibility : hidden;}
                header {visibility : hidden;}
                </style>
                """
st.markdown(hide_st_style, unsafe_allow_html=True)


#---PW GENERATOR FUNCTION--#
def generate_pw()->None:
    """Uses the string module to get the letters and digits that make up the alphabet used to generate the random characters. These characters are
    appended to the pwd string which is then assigned to the session_state variable [pw]"""
    letters = string.ascii_letters
    digits = string.digits  
    alphabet = letters + digits
    pwd_length = 14
    pwd = ''
    for i in range(pwd_length):
        pwd += ''.join(secrets.choice(alphabet))


    st.session_state["pw"] = pwd


#---PASSPHRASE GENERATOR FUNCTIONS---#


    #---GET RANDOM WORD---#
def get_random_word()->str:
    """Uses the API Ninja API to request a word string. This string is then parsed to extract only the word and return it."""
    api_url = 'https://api.api-ninjas.com/v1/randomword'
    response = requests.get(api_url, headers={'X-Api-Key': st.secrets.API_NINJA})
    if response.status_code == requests.codes.ok:
        returned_word = response.text.split(":")
        returned_word = returned_word[1]
        returned_word  = returned_word[2:-2]
        return returned_word
    else:
        return "Error:", response.status_code, response.text


    #---GENERATING THE PHRASE---#
def generate_ps()->None:
    """Uses the get_random_word function to request five words. These are concatenated into a string with dashes and then
    assigned these to the session_state variable [pw] """
    passphrase = ""
    for x in range(5):
        passphrase += f"{get_random_word()}-"
    passphrase_final = passphrase[:-1]  
    st.session_state["pw"] = passphrase_final


#---MAIN PAGE---#


if "pw" not in st.session_state:
    st.session_state["pw"] = ''
   
"---"
col1,col2 = st.columns([4,4], gap = "large")


with col1:
    st.caption("Secure password length is set at 14 chars.")
    st.button("Generate secure password", key = "pw_button", on_click = generate_pw)


with col2:
    st.caption("Secure passphrase length is set at 5 words.")
    st.button("Generate secure password sentence", key = "ps_button", on_click = generate_ps)
"#"
"#"
ocol1, ocol2, ocol3 = st.columns([1,4,1])
with ocol1:
    ''
with ocol2:
    st.caption("Generated secure PW")
    "---"
    st.subheader(st.session_state["pw"])
    "---"
   
with ocol3:
    ''

When you check the browser again, your application should be ready to use!

Tip: When pressing the passphrase button, a slight delay might occur as the app contacts the API 🙂

Conclusion

I hope you liked this tutorial. It is very basic in its functionality, but the potential for a lot more is there.

I have some ideas of my own that I would like to implement. Chief among them is the ability to choose the number of characters or words for the password/passphrase.

Another one, but this will be a separate project, is to create a mobile app version of this. Who knows, you might see it here if I succeed. 

Have fun building this!