User management in Microsoft Access 2013 databases - mysql

I am building a system using Microsoft Access 2013 that uses MySQL as a backend. I am trying to figure out a way to do some kind of basic user management, ideally in such a way that users would have to "log in" when they launch the database, and then their username would be easily accessible by the system while they are using it. I've tried searching for solutions, but most of them just tell me to use Office 365 or sharepoint, which are not options at the moment. Does anyone have an idea of how to accomplish this? Thanks in advance!

I recommend building your own user storage and login system. You'll basically need to create your own users table (in MySQL in your case), make forms to manage users, make a Login form, and write code to control the login process.
Logging in usually consists of checking some kind of credentials they type in against existing data in your users table. You can usually do this in Access with DLookup or DCount statements but I usually end up using a DAO or ADO recordset instead since I like to pull out more than one value from the User's table and I also like to write things back to it right away, like the LastLogin datetime, LastLogin computername, etc.
I actually wrote an example database which you can download here. It needs a rewrite. I've changed quite a few of my practices since Jan, 2011. But give me another year and it would need another rewrite.
I usually program the login form so that the user enters their initials and then a password. If you go this route you need to have a unique index setup on the Initials field to prevent duplicates. If you're going to have a lot of users you need to use a Username instead, which could still theoretically be the users initials.
Here's what my code would look like to authenticate a user. Be aware that this is far from truly secure. This assumes that passwords are stored in plain text. Users could theoretically try to do SQL Inject from here because I'm not use a parametrized query or stripping out special characters from their input such as # or ;.
Private Function AuthenticateUser() As Boolean
Dim sInitials As String
Dim sPassword As String
sInitials = Trim(Nz(Me.txtInitials, ""))
sPassword = Trim(Nz(Me.txtPassword, ""))
If sInitials = "" Or sPassword = "" Then
'Logging in with blank passwords is not allowed
AuthenticateUser = False
Exit Function
End If
If DCount("EmployeeID", "tblEmployees", "[Initials] = '" & Replace(sInitials, "'", "''") & "' AND Password = '" & Replace(sPassword, "'", "''") & "'", True) = 0 Then
MsgBox "Invalid Credentials."
AuthenticateUser = False
Exit Function
Else
Dim rs As New DAO.Recordset
Dim sSQL As String
sSQL = "SELECT * FROM tblEmployees WHERE initials = '" & Replace(sInitials, "'", "''") & "'"
Set rs = CurrentDb.OpenRecordset(sSQL)
If Not (rs.EOF And rs.BOF) Then
'Config is an instance of a User Defined Type. It could also be a class object.
Config.UserInitials = rs("Initials")
Config.UserFullName = rs("EmployeeName")
Config.UserID = rs("EmployeeID")
rs.Edit
rs("LastLoginDateTime") = Now()
rs("LastLoginComputer") = "Function Required to Get Computer Name"
rs("ProgVersion") = "Your Program Version Number"
rs("CurrentDbPath") = Left(CurrentProject.path & "\" & CurrentProject.Name, 254)
rs.Update
End If
rs.Close
Set rs = Nothing
AuthenticateUser = True
End If
End Function
In my applications I use a global object, in this case an instance of a User Defined Type, which I call Config. I store any kind of application runtime related settings in there for the duration of the runtime of the application. Of course this object gets destroyed when the user closes out of the application or when a code reset happens (which cannot happen in Access runtime, but does happen frequently during development). You could use a class object instead of a User Defined Type. Or you could use individual global variables for everything, which I don't recommend (that's what I used to do). A User Defined Type simply allows you to group global variables together and gives you an easy way to refer to them in your code during design time by typing in Config., which then brings up every option using Intellisense (assuming you have it enabled).
If you want your settings to survive a code reset, you need to use TempVars. TempVars became available with Access 2007. I do not use them now (contrary to my example database) because they are not strongly typed. There's no Intellisense to help you get the correct TempVar and you can technically refer to a TempVar that doesn't even exist and Access won't throw an error. I think TempVars is really just a Dictionary object with all of it's shortcomings, and the single benefit of surviving a code reset. I can imagine storing a Connection String in there, but I wonder if it's worth using TempVars for anything at all. If a code reset happens, my entire application needs to be reloaded anyway since I setup a lot of global objects and variables when the application first opens and the user first logs in.
FYI, in previous versions of Access there was user security built in. I think Microsoft discontinued that starting in 2007. I never really used it so I didn't miss it when it got discontinued.

Related

use login to allow use of dsnless links

i'm using dsnless links in access 2019 to a mysql instance on aws. I have an Access login form, which passes uid & password to a connection string in VBA, and connects and executes simple "select 1".
All along, i assumed the credentials from my access input form were being used to check against my MYSQL users, BUT...it looks to maybe use a cached user/password instead? When i enter a wrong password into my access login form, it still successfully connects and executes the qdf / sql query.
(i can 'watch' the connection string in vba does have the wrong password)
how can i force odbc to authenticate using the actual id & password actually being sent in connection string??
This simple login form has worked, but just realizing now it will pass a wrong password from my access form, but yet still connect and execute the sql...
Function TestLogin(uid As String, pwd As String) As Boolean
On Error GoTo testerror
Dim dbs As DAO.Database
Dim qdf As DAO.QueryDef
Dim RST As DAO.Recordset
Dim strcon As String
Dim errX As DAO.Error
Dim strsql As String
Dim strRW
strcon = "ODBC; Driver=MySQL ODBC 8.0 Unicode Driver;" & _
"SERVER={xxx};DATABASE=xxx;PORT=3306;" & _
"UID=" & uid & "; PWD=" & pwd & ";COLUMN_SIZE_S32=1;DFLT_BIGINT_BIND_STR=1;OPTION=3"
Set dbs = CurrentDb()
dbs.QueryTimeout = 5
Set qdf = dbs.CreateQueryDef("")
qdf.Connect = strcon
qdf.sql = "SELECT current_user"
Set RST = qdf.OpenRecordset(dbOpenSnapshot, dbSQLPassThrough)
TestLogin = True
mysqlUser = uid
floor = DLookup("floor", "tblxx", "user ='" & mysqlUser & "'")
Set qdf = Nothing
Set RST = Nothing
DoCmd.Close
DoCmd.OpenForm "Switchboard"
exit_errorTrap:
Set qdf = Nothing
Set RST = Nothing
Exit Function
Ok, the WAY this works can be confusing.
On application start up we asume you do not by accient (or intention) let any form, any VBA code that runs to touch, or logon or use IN ANY POSSBILE way on startup.
Ok, with above? If you linked the tables correct without UID/Password, if you touch, or even try to open a table from the nav pane? you SHOULD get a ODBC prompt. So, test the above - hold down shift key during startup - NO CODE or NO touch of a linked table is assumed here.
Now, click on a linked table - if the table opens, then you screwed up your table linking, and you DO NOT need the logon, then, right????
Next issue - and READ VERY careful.
If you execute a VALID logon (with your logon code), then ONCE you done this, the correct connection is cached - you can NOT IN ANY POSSBILE way blow out, remove, or re-set that cache.
If AFTER having executed ANY valid logon?
Then ANY ADTIONAL attempted logons (say with your logon code) WILL ALWAYS return true and valid. INCLUDING WRONG logons!!!! (they STILL return true!!!!).
The above information thus means some very significant issue's.
to clear hte logon, you will have to (must) exit Access. You thus can't crete a logout form - you must EXIT applcation.
Worse yet, you BETTER ALWAYS BUT ALWAYS BUT ALWAYS exit access first to clear that cache when developing and testing re-linking of tables. If you at any point in time during development by intention (or accident) open a linked table, and enter uid/password (or do so via code, or do so by JUST clicking on a linked table that DOES and DID save the uid/password?.
Then if you decide to re-link tables - EVEN WITH a incorrect UID/password, they WILL REPORT they linked ok!!!! - but they did not!!!! Even during re-link, if you use the wrong UID/Password, you are screwed, since access will in fact use the cached one (if your uid/password are wrong!!!!).
So, you first have to 100%, if not 200% do the first test above - check, double check/ triple check that linked tables do NOT WORK when clicked on.
Now, run your VBA logon.
Now, cliking on any linked table should work. But as noted, ONCE you touched, or logged on (by any possbile means), the UID/PW is cached, and will remain so for that session until such time you exit access.
What this means is you get ONCE chance to logon. They can fail, but the instant you achieve ONE successful logon, then ALL FURTHER attempts at logons WILL WORK - even if incorrect.
In summary:
You can prompt for a logon.
you can do this without having to re-link tables.
All linked tables, and EVEN pass-though queries will use that cached logon.
You MUST exit access to clear this cache, and thus it is IMPOSSILE to now launch your custom logon form and logon with a different user, since the previous user remains cached.
This also as noted means during deveopment, to link tables without uid/pw, you need to exit Access. launch access (without ANY POSSBILE touch of linked tables/data).
You then execute your logon. You THEN re-link tables without UID/Password. Then you have to exit access, re-launch (shift startup by-pass). Now click on a table - it should fail - prompt for odbc.
But, if you exit, shift- by pass startup. Execute your logon. Now click on a linked table - it should work. If it does not, or the first test (without a logon DID work, then as noted, you have royal screwed up something here.
so, for wrong/incorrect logons? You can continue trying to log on.
But ONCE you have logged on - that's quite much the end of this process. Any further attempts at logons will ALWAYS work - even for totaly wrong logons - you have to exit to clear the PW cache.
you also as a result can thus not use two cached passwords - say some tables linked as read only, and some tables linked as read/write (based on different uid/pw). and the reason is you can't control which logon will be used - and access will thus choose the read/write logon.
So, I suppose the most simple explain?
Once a uid/pw is cached, then you can and will be able to use all linked tables regardless of what uid/logon you attempt to use. And EVEN if at that point in time you did decide to run your re-link code? it will re-link JUST fine, even if you supply a incorrect uid/password. But, be carful!!! - you can in some cases thus re-link, check that the tables worked, but when you exit, and re-enter - you find those linked tables now don't work!!!
So, be VERY careful, if not outright SUPER careful when you re-link, especially if you been messing around with connections - you have to exit access, shift by-pass startup. and THEN re-link your tables. One stray cached uid/password, and you are in heaps of trouble - since things will work, and not make any sense at all.
keeping the above simple concept in mind - all will and should now make sense.
I been using this code:
Function TestLogin(strCon As String) As Boolean
On Error GoTo TestError
Dim dbs As DAO.Database
Dim qdf As DAO.QueryDef
Set dbs = CurrentDb()
Set qdf = dbs.CreateQueryDef("")
qdf.connect = strCon
qdf.ReturnsRecords = False
'Any VALID SQL statement that runs on server will work below.
' this does assume user has enough rights to query built in
' system tables
qdf.sql = "SELECT 1 "
qdf.Execute
TestLogin = True
Exit Function
TestError:
TestLogin = False
Exit Function
End Function
now you used a pass-though query - and I never done/use that before - and don't think it will work. You have to use above format.

Acess support please

Access Docment scanned with Macfee and Microsoft on a server platform One drive so completly safe.
I want a user login page which has different levels so that different users will have access to different objects or functionality.
Please have a look at the coding below I have also attached a link to onedrive for the access 2013 document.
code:
Private Sub Form_Load()
Dim Security As Integer
Me.txtLogin = Environ(“userName”)
Me.txtUser = DLookup(“userName”, “tblUser”, “Username = ‘” & User & “‘”)Then
If IsNull(DLookup(“userSecurity”, “tblUser”, “UserLogin = ‘” & Me.txtLogin & “‘”)) Then
MsgBox (“No User security set up for this user. Please contact the Admin”, vbOKOnly, “Login Info”
Me.NavigationButton13.Enabled = False
Else
Security = DLookup(“userSecurity”, “tblUser”, “UserLogin = ‘” & Me.txtLogin & “‘”)
If Security = 1 Then
Me.NavigationButton15.Enabled = True
Else
Me.NavigationButton15.Enabled = False
End If
End If
End Sub
I am guessing you have a main login page for the entire app, which is the startup form. Keep in mind that a user can hold down the Shift key when opening your app to bypass any startup code, but you can disable that. You can also hide the database container. Users can always use another DB to link your tables, or import objects, so you will also want to lock the entire database with a password.
The form startup code can check your custom security table and then you can use Cancel = True to short-circuit the opening event. It seems like you are trying to accomplish that by disabling navigation buttons, but you may want to disallow the opening of the form.
As HansUp and Tim have suggested, tt would be helpful if you can describe your situation a bit more so that we can respond more concisely. If you have a situation where you need true security that is hard to breech, you may want to consider moving your tables into SQL Server (or SQL Express) and linking them to your Access container. Ultimately, there's nothing you can do in Access that I could not get around.

VB Application Search MySQL Database for Login Credentials

Okay, I posted this question previously but only got half of the answer I require. To put simply I have an application which checks the log-in credentials with those stored in the My SQL Database. The awkward problem I have hear is that I've created it in the way that best suits me, and in a way that's simple for me to understand -- I'm not experienced with programming and self taught. The method I used - though prone to 'injection attacks' and other such dangers is suitable for my application and there's obviously security in my application (CD-Key checks & passwords.)
So, the actual details:
The connection is established successfully and I'm able to manipulate the data from the application.
Connection Code:
Public Sub conecDB()
'This is called upon program start.
Dim connectionString As String = SQLLoginIn.serveriptxt.Text 'This is the server IP/Server name. If server is intalled on your local machine, your IP should be 127.0.0.1 or you may use localhost
Dim strDbase As String = SQLLoginIn.databasenametxt.Text 'Database name
Dim strUser As String = SQLLoginIn.databaseusertxt.Text 'Database user
Dim strPass As String = "CENSORED" 'Database password
If connDB.State <> ConnectionState.Open Then connDB.ConnectionString = "Data Source=" & connectionString.Trim & ";Initial Catalog=" & strDbase.Trim & ";MultipleActiveResultSets=False;User ID=" & strUser.Trim & ";Password=" & strPass
If connDB.State <> ConnectionState.Open Then connDB.Open()
End Sub
That works great, for what I need it to do.
I add the records (of a separate table) to a listview in my program called lvRec
Code:
Public Sub dispRec(ByVal PstrSQL As String)
'This is called once the connection is established.
frmMain.lvRec.Items.Clear()
With comDB
.CommandText = PstrSQL
rdDB = .ExecuteReader
End With
Do While rdDB.Read
Item = frmMain.lvRec.Items.Add(rdDB!Members_ID.ToString)
Item.SubItems.Add(rdDB!Gamer_Tag.ToString.Trim)
'And a bunch of more
Item.SubItems.Add(rdDB!Games_Owned.ToString.Trim)
My.Application.DoEvents()
Loop
rdDB.Close()
End Sub
So now that you've seen how I'm working with SQL you're probably screaming "That's not how you do it!", but as I sayed, it works for me :) The problem occurs when I want to varifiy the user with their own Username & Password stored on a different Table called Logins.
I know I need to/can use the below code to query the users input with the log-in credentials in the Logins table, but I don't know how to return anything use-able like a Boolean result or something similar. I simply want to know if the credentials teh user entered are correct, so a TRUE for it existing would be great.
The SQL Query:
SQL = "Select * from logins " & _
"where Name like '%" & Me.Usernametxt.Text.Trim & _
"%' AND Passkey LIKE '" & Me.Passwordtxt.Text.Trim & "'"
I know this is a lot of detail for such a simple question but I needed people to understand the way I've went around this. Any help would be much appreciated.
Not an answer but to help you get started with authentication in a more robust fashion...
[Please bear in mind that you should never take an SO answer as gospel as all of us make mistakes and security is a very difficult topic to do right even for the professionals...]
The process you want to follow is something like the following (in pseudocode)
UserRecords = ExecuteSql("SELECT UserID, PasswordHash, Salt, IsEnabled FROM Users WHERE Username = '<Blah>'")
If UserRecords.Count <> 1
'Login Failed
End If
Dim UserRecord = UserRecords.First
If Hash(PasswordEnteredByUser & UserRecord.Salt) <> UserRecord.PasswordHash
'Login Failed
End If
If UserRecord.IsEnabled <> True
'Login Failed
End If
' Login was successful, continue as planned
A hashing function works by taking an input eg MyPassword! and converting it to something else eg AB0653AAAF330. Think of it as a sausage maker - you put a pig in one end and get a sausage out the other end. It's impossible to turn a sausage back into a pig. In theory, however, putting the same pig in twice will produce the same sausage. By storing the sausage, not the pig a database breach won't reveal users passwords
The "Salt" comes in to play when your database is breached. Imagine two users picked the same password. You know bob's password is MyPassword! and you can see it's stored as AB0653AAAF330. Anyone else with a password hash of AB0653AAAF330 has the same password - meaning multiple accounts could be breached at once.
If, on the other hand, there is a random string which is user-specific and appended to their password pre-hashing, Bob's password is treated as MyPassword!AAAAAAAAA which (say) hashes to 099DEF3333445 and Jane's (identical) password is treated as MyPassword!BBBBBBBBBwhich (say) hashes to AAAAE1234400.
In short, knowing Bob's password won't tell you that Jane has the same one.
This is still a hideously incomplete example. There are many other things to consider (eg the hashing algorithm. MD5 hashing is considered weak, bcrypt is intentionally computationally expensive to make brute-forcing more difficult, etc) but hopefully it will give you a rough idea to get you started.
You also want to make authentication binary - success or failure. If you tell the user "That username is invalid" (as opposed to a simple "Login Failed") as a help to them, they can use the feature to determine valid usernames by repetition.
I wholeheartedly recommend you read this article on how a company was compromised via SQL injection - once you know how it works in detail, you'll be able to see the pitfalls...
Another feature to consider is a failed login "window" - a sliding period of time in which a certain number of failed logins is allowed before that account becomes locked (possibly temporarily). This means brute-forcing passwords becomes impractical. This is usually handled by recording a FailedLoginAttempts and a FailedLoginTimeout against the user account. If a login fails, set the Timeout to now + 30 mins. If attempts > some threshold and the timeout hasn't expired, lock the account. If the login fails, extend the timeout. You get the idea.
Finally, if you're really security-conscious, when a user logs in successfully, tell them when they last logged in. If someone's been on holiday for a week and sees "Your last login was yesterday" they know instantly that something's wrong.
All the best.
On the general command on how to return a singular results from a SQL command:
Dim sqlConnection1 As New SqlConnection("Your Connection String")
Dim cmd As New SqlCommand
Dim success As Object
cmd.CommandText = "SELECT COUNT(*) FROM logins " & _
"WHERE Name = #Name " & _
"AND Passkey = #Passkey"
cmd.Parameters.AddWithValue("#Name",Me.Usernametxt.Text.Trim)
cmd.Parameters.AddWithValue("#Passkey",Me.Passwordtxt.Text.Trim)
cmd.CommandType = CommandType.Text
cmd.Connection = sqlConnection1
sqlConnection1.Open()
success = cmd.ExecuteScalar()
sqlConnection1.Close()

How to search through VBA code files

I just started a job with a new company where previous developers had created many automated tasks. Of course, there is virtually no documentation and I didn't have a chance to work with the previous developer so now I'm trying to sift through all these processes looking for one that modifies some specific files.
I've scripted all the stored procs in SQL and used a search tool and didn't find what I was looking for, so now I am wondering if the process I need is located in one of many Access databases that are used. With SQL Server, it was easy to write a C# app to script the procs so I could search through them, but with Access it looks like I'm confined to opening each db individually to search through the code files.
Is there any way to programatically search through VBA code files?
If your interest is searching code modules in an Access database file, you can use the VBE object model. This sample searches for a word in all the modules of the ActiveVBProject of the current database. If the database includes more than one VBProject, you can enumerate the VBProjects collection and search the projects one at a time by name:
For Each objComponent In Application.VBE.VBProjects(ProjName).VBComponents
Or if you prefer to reference the project by number rather than name, just be aware the numbering starts with 1 rather than 0.
Public Sub findWordInModules(ByVal pSearchWord As String)
'Dim objComponent As VBComponent
' VBComponent requires reference to Microsoft Visual Basic
' for Applications Extensibility; use late binding instead:
Dim objComponent As Object
Dim strMessage As String
Dim strModuleList As String
strModuleList = vbNullString
For Each objComponent In Application.VBE.ActiveVBProject.VBComponents
If objComponent.CodeModule.Find(pSearchWord, 1, 1, -1, -1) = True Then
strModuleList = strModuleList & "; " & objComponent.Name
End If
Next objComponent
strMessage = "Text '" & pSearchWord & "' found in "
If Len(strModuleList) > 0 Then
strMessage = strMessage & "modules: " & Mid(strModuleList, 3)
Else
strMessage = strMessage & "no modules"
End If
Debug.Print strMessage
End Sub
Review the Access help topic for that Find method; you may prefer different options than I used.
If you want to target multiple db files and search the modules in each, you could automate this using the OpenDatabase method. I'll leave the details of that part up to you.
Another option, not previously mentioned - is to just print the Project Code, from the context menu, in the VBA Editor. The steps below, can be modified, to suit applications available, but the result is the same - searchable text.
From the VBA Editor, right click the project name, and click print, from the context menu that appears. Make sure "Code" is checked, and click "OK". The printer can be changed, from the "Setup..." menu, available in the last dialog.
On a side note - stand alone modules, SQL, tables, etc, are available for printing, from the "Database Documenter", in the "Analyze" group, of the "DATABASE TOOLS" tab.
Very useful, for sorting and outlining underlying SQL, of Access queries.
Best to download the free MZ-Tools for VBA and use their search/replace function.
Edit
MZ-Tools for VBA is no longer available. The paid version works with newer office installations.

Access Database Security Question

I have a database in Access 2003 that I only want certain people to be able to access.
In my database I have a table which lists the people that should be able to access the database. (Tbl_BIRT_Users). The table contains their name, ntlogin and email address. It also has an 'adminstrator' field.
My question has two parts:
1 - On opening the database how can I get it to lookup the ntlogin (environ username) of the person and ensure that that person is authorised to use the database?
2 - I need the database to look at the 'administrator' Yes/No field and grant read only access to non admins and full access to admins.
Thanks!
Use an API call to get the login name - API: Get Login name You can change environment variables while at the command prompt and then, if start Access executing from the command prompt, Access will use the spoofed environment variable.
Also there are ways of easily breaking table driven security such as the user taking the backend database home to a retail copy of Access resides, changing the values in the tables and bringing the database back to the office.
Even if you trust the users not to fiddle with their environment variables, please adopt Tony's suggestion anyway. After you add the module he linked, retrieving the user's account name is a simple call to the fOSUserName() function. It's really no more difficult than getting it from the user's environment.
But I want to add to Tony's point about "easily breaking table driven security". Your plan is to check whether the user is one of your authorized users. My suggestion is to place your back end database file in a location where only your authorized users can get at it. Use Windows file system permissions to keep everyone else out. That way you may decide you don't even need to check your table to determine whether the user is authorized. You could still use the table data to determine whether the user is an Admin or regular user. Or you might decide to keep the authorization check if it gives your managers peace of mind ... even though it doesn't really offer much security.
Could you not just do something like this
Dim rst as Recordset
Dim sql as string
sql = "SELECT * FROM Tbl_BIRT_Users WHERE ntlogin = '" & Environ("UserName") & "'"
set rst = CurrentDb.OpenRecordset(sql)
if (rst.bof and rst.eof) then
/*not a valid user*/
DoCmd.Quit
else
if not rst!Administrator then
/*make read only*/
end if
end if
rst.close
This is the Access security window dressing I use.
Public Function SecurityCode()
'* Purpose: Limits access to program
Dim sUserID As String
Dim sUserName As String
'* Determines user from Windows Login
sUserID = Environ("USERNAME")
'* Lookup on BE table of Allowed Users to verify on the list.
sUserName = DLookup("[UserName]", "tbl_AllowedUsers", "ID = '" & sUserID & "'")
If Len(sUserName) > 0 Then
'Allowed User, opens Main Switchboard
'Set global variable for Admin rights
g_Admin = DLookup("[AdminRights]", "tbl_AllowedUsers", "ID = '" & sUserID & "'")
DoCmd.OpenForm "Switchboard"
DoCmd.SelectObject acForm, "Switchboard", True
DoCmd.RunCommand acCmdWindowHide
Else
'Not on the Allowed Users list, opens to a Password Page
DoCmd.OpenForm "frm_LockPage"
DoCmd.SelectObject acForm, "frm_LockPage", True
DoCmd.RunCommand acCmdWindowHide
End If
End Function
Try something like the below:
Function RealName()
payroll = Environ("Username")
firstname = DLookup("[first name]", "[Payroll Numbers]", "[persno] = " & payroll)
lastname = DLookup("[Last name]", "[Payroll Numbers]", "[persno] = " & payroll)
If IsNull(firstname) = True Then
RealName = payroll
Else
RealName = firstname & " " & lastname
End If
End Function
You can then enter code in the form_load event to make sure it's a verified user.