Most mobile apps implement some kind of user authentication. Even though part of the authentication and state management logic is performed by the back end service, authentication is such an integral part of most mobile app architectures that understanding its common implementations is important.
You'll usually find that the mobile app uses HTTP as the transport layer. The HTTP protocol itself is stateless, so there must be a way to associate a user's subsequent HTTP requests with that user—otherwise, the user's log in credentials would have to be sent with every request. Also, both the server and client need to keep track of user data (e.g., the user's privileges or role). This can be done in two different ways:
-
With stateful authentication, a unique session id is generated when the user logs in. In subsequent requests, this session ID serves as a reference to the user details stored on the server. The session ID is opaque; it doesn't contain any user data.
-
With stateless authentication, all user-identifying information is stored in a client-side token. The token can be passed to any server or micro service, eliminating the need to maintain session state on the server. Stateless authentication is often factored out to an authorization server, which produces, signs, and optionally encrypts the token upon user login.
Web applications commonly use stateful authentication with a random session ID that is stored in a client-side cookie. Although mobile apps sometimes use stateful sessions in a similar fashion, stateless token-based approaches are becoming popular for a variety of reasons:
- They improve scalability and performance by eliminating the need to store session state on the server.
- Tokens enable developers to decouple authentication from the app. Tokens can be generated by an authentication server, and the authentication scheme can be changed seamlessly.
As a mobile security tester, you should be familiar with both types of authentication.
In this chapter, we discuss authentication and authorization on an OS-independent level. You'll find OS-specific mobile security discussions (local and biometric authentication) in the respective OS-specific chapters.
Authentication and authorization problems are prevalent security vulnerabilities. In fact, they consistently rank second highest in the OWASP Top 10. Before diving into concrete testing instructions, we'll look at some of the most common issues.
Authentication bypass vulnerabilities exist when authentication state is not consistently enforced on the server and when the client can tamper with the state. While the backend service is processing requests from the mobile client, it must consistently enforce authorization checks: verifying that the user is logged in and authorized every time a resource is requested.
Consider the following example from the OWASP Web Testing Guide. In the example, a web resource is accessed through a URL, and the authentication state is passed through a GET parameter:
http://www.site.com/page.asp?authenticated=no
The client can arbitrarily change the GET parameters sent with the request. Nothing prevents the client from simply changing the value of the authenticated
parameter to "yes," effectively bypassing authentication.
Although this is a simplistic example that you probably won't find in the wild, programmers sometimes rely on "hidden" client-side parameters, such as cookies, to maintain authentication state. They assume that these parameters can't be tampered with. Consider, for example, the following classic vulnerability in Nortel Contact Center Manager. The administrative web application of Nortel's appliance relied on the cookie "isAdmin" to determine whether the logged-in user should be granted administrative privileges. Consequently, it was possible to get admin access by simply setting the cookie value as follows:
isAdmin=True
Security experts used to recommend using session-based authentication and maintaining session data on the server only. This prevents any form of client-side tampering with the session state. However, the whole point of using stateless authentication instead of session-based authentication is to not have session state on the server. Instead, state is stored in client-side tokens and transmitted with every request. In this case, seeing client-side parameters such as isAdmin
is perfectly normal.
To prevent tampering cryptographic signatures are added to client-side tokens. Of course, things may go wrong, and popular implementations of stateless authentication have been vulnerable to attacks. For example, the signature verification of some JSON Web Token (JWT) implementations could be deactivated by setting the signature type to "None." We'll discuss this attack in more detail in the "Testing JSON Web Tokens" chapter.
In the following sections, we will list testing methods and best practices for session-based and stateless authentication.
There's no one-size-fits-all approach to authentication. You should first consider whether the authentication method(s) used are appropriate for the app. Authentication can be based on one or more of the following:
- Something the user knows (password, PIN, pattern, etc.)
- Something the user has (SIM card, one-time password generator, or hardware token)
- A biometric property of the user (fingerprint, retina, voice)
The number of authentication procedures implemented by mobile apps depends on the sensitivity of the functions or accessed resources.
Refer to industry best practices when reviewing authentication functions. Username/password authentication (combined with a reasonable password policy) is generally considered sufficient for apps that have a user login and aren't very sensitive. This form of authentication is used by most social media apps.
For sensitive apps, adding a second authentication factor is usually appropriate. This includes apps that provide access to very sensitive information (such as credit card numbers) or allow users to transfer funds. In some industries, these apps must also comply with certain standards. For example, financial apps have to ensure compliance with the Payment Card Industry Data Security Standard (PCI DSS), the Gramm Leech Bliley Act, and the Sarbanes-Oxley Act (SOX). Compliance considerations for the US health care sector include the Health Insurance Portability and Accountability Act (HIPAA)and the Patient Safety Rule.
You can also use the OWASP Mobile AppSec Verification Standard as a guideline. For non-critical apps ("Level 1"), the MASVS lists the following authentication requirements:
- If the app provides users with access to a remote service, an acceptable form of authentication such as username/password authentication is performed at the remote endpoint.
- A password policy exists and is enforced at the remote endpoint.
- The remote endpoint implements an exponential back-off, or temporarily locks the user account, when incorrect authentication credentials are submitted an excessive number of times.
For sensitive apps ("Level 2"), the MASVS adds the following:
- A second factor of authentication exists at the remote endpoint and the 2FA requirement is consistently enforced.
- Step-up authentication is required to enable actions that deal with sensitive data or transactions.
- M4 - Insecure Authentication - https://www.owasp.org/index.php/Mobile_Top_10_2016-M4-Insecure_Authentication
- V4.1: "If the app provides users access to a remote service, some form of authentication, such as username/password authentication, is performed at the remote endpoint."
- CWE-287: Improper Authentication
Two-factor authentication (2FA) is standard for apps that allow users to access sensitive personal data. Common implementations use a password for the first factor and any of the following as the second factor:
- One-Time password via SMS (SMS-OTP)
- One-time Code via phone call
- Hardware or software token
- Push notifications in combination with PKI and local authentication
The secondary authentication can be performed at login or later in the user's session. For example, after logging in to a banking app with a username and PIN, the user is authorized to perform non-sensitive tasks. Once the user attempts to execute a bank transfer, the second factor ("step-up authentication") must be presented.
Transaction signing requires authentication of the user's approval of critical transactions. Asymmetric cryptography is the best way to implement transaction signing. The app will generate a public/private key pair when the user signs up, then registers the public key on the back end. The private key is securely stored in the device keystore. To authorize a transaction, the back end sends the mobile app a push notification containing the transaction data. The user is then asked to confirm or deny the transaction. After confirmation, the user is prompted to unlock the Keychain (by entering the PIN or fingerprint), and the data is signed with user's private key. The signed transaction is then sent to the server, which verifies the signature with the user's public key.
You may supplement an authentication scheme with passive contextual authentication, which can incorporate:
- Geolocation
- IP address
- Time of day
Ideally, the user's context is compared to previously recorded data to identify anomalies that might indicate account abuse or potential fraud. This process is transparent to the user, but can become a powerful deterrent to attackers.
Identify the additional authentication factors the app uses. Locate all endpoints that provide critical functionality. Verify that the additional factors are strictly enforced on all server-side endpoints.
Use the app extensively (going through all UI flows) while using an interception proxy to capture the requests sent to remote endpoints. Next, replay requests to endpoints that require 2FA (e.g., performing a financial transactions) while using a token or session ID that hasn't yet been elevated via 2FA or step-up authentication. If an endpoint is still sending back requested data that should only be available after 2FA or step-up authentication, authentication checks haven't been properly implemented at that endpoint.
Given that many OTPs are just numeric values, an attacker can bypass the second factor by brute-forcing the values within the range at the lifespan of the OTP if the accounts aren't locked after N unsuccessful attempts at this stage. The probability of finding a match for 6-digit values with a 30-second time step within 72 hours is more than 90%.
Attempt a brute-force attack on the OTP verification endpoint using BURP Intruder or a similar tool (see also Testing Excessive Login Attempts).
- M4 - Insecure Authentication - https://www.owasp.org/index.php/Mobile_Top_10_2016-M4-Insecure_Authentication
- V4.9: "A second factor of authentication exists at the remote endpoint, and the 2FA requirement is consistently enforced."
- V4.10: "Sensitive transactions require step-up authentication."
- CWE-287: Improper Authentication
- CWE-308: Use of Single-factor Authentication
Password strength is a key concern when passwords are used for authentication. The password policy defines requirements to which end users should adhere. A password policy typically specifies password length, password complexity, and password topologies. A "strong" password policy makes manual or automated password cracking difficult or impossible.
Password Length
- Minimum password length (10 characters) should be enforced.
- Maximum password length should not be too short because it will prevent users from creating passphrases. The typical maximum length is 128 characters.
Password Complexity
The password must meet at least three out of the following four complexity rules:
- at least one uppercase character (A-Z)
- at least one lowercase character (a-z)
- at least one digit (0-9)
- at least one special character
Verify the existences of a password policy and password complexity requirements. Identify all password-related functions in the source code and make sure that a the verification check is performed in each of them. Review the password verification function and make sure that it rejects passwords that violate the password policy.
Regular Expressions are often used to enforce password rules. For example, the JavaScript implementation by NowSecure uses regular expressions to test the password for various characteristics, such as length and character type. The following is an excerpt of the code:
function(password) {
if (password.length < owasp.configs.minLength) {
return 'The password must be at least ' + owasp.configs.minLength + ' characters long.';
}
},
// forbid repeating characters
function(password) {
if (/(.)\1{2,}/.test(password)) {
return 'The password may not contain sequences of three or more repeated characters.';
}
},
function(password) {
if (!/[a-z]/.test(password)) {
return 'The password must contain at least one lowercase letter.';
}
},
// require at least one uppercase letter
function(password) {
if (!/[A-Z]/.test(password)) {
return 'The password must contain at least one uppercase letter.';
}
},
// require at least one number
function(password) {
if (!/[0-9]/.test(password)) {
return 'The password must contain at least one number.';
}
},
// require at least one special character
function(password) {
if (!/[^A-Za-z0-9]/.test(password)) {
return 'The password must contain at least one special character.';
}
},
For more details, check the OWASP Authentication Cheat Sheet. zxcvbn is a common library that can be used for estimating password strength is. It is available for many programming languages.
Attempt to pass weak passwords to registration and password reset functions, such as:
- Self-registration function for new users, which allows users to set passwords
- "Forgot Password" function, which allows users to set a new password
- "Change Password" function, which allows logged-in users to set a new password
When you're black-box testing, use a dictionary attack to detect weak passwords (this is described in more detail in the next chapter).
- M4 - Insecure Authentication - https://www.owasp.org/index.php/Mobile_Top_10_2016-M4-Insecure_Authentication
- V4.5: "A password policy exists and is enforced at the remote endpoint."
- CWE-521: Weak Password Requirements
Password-guessing attacks are a common and well-known hacking technique. Hackers use a dictionary of common passwords to guess the correct password for user accounts. This kind of attack doesn’t require much technical knowledge of the target: there are ready-made tools that automatically generate the required requests from word lists and smart rule sets.
The common way to block this attack is to lock accounts after a specific number of incorrect login attempts ("login throttling"). Account lockouts may last for a specific amount of time (temporary lockout), such as a few minutes, or until an administrator manually unlocks the account (permanent lockout). The recommended defense, however, is an exponential back-off algorithm: exponentially increasing the time between subsequent login attempts . Such a scheme has a negligible effect on usability while slowing down automated attacks significantly.
Always check whether an account locking mechanism is in place before attempting to perform a brute-force attack on a client’s system. If account locking is in place, refrain from brute-forcing the passwords of legitimate users.
Check the source code for a throttling procedure: a counter for logins attempted in a short period of time with a given user name and a method to prevent login attempts after the maximum number of attempts has been reached. After an authorized login attempt, the error counter should be reset.
Observe the following best practices when implementing anti-brute-force controls:
- After a few unsuccessful login attempts, targeted accounts should be locked (temporarily or permanently), and additional login attempts should be rejected.
- A five-minute account lock is commonly used for temporary account locking.
- The controls must be implemented on the server because client-side controls are easily bypassed.
- Unauthorized login attempts must tallied with respect to the targeted account, not a particular session.
Additional brute force mitigation techniques are described on the OWASP page Blocking Brute Force Attacks.
Automated password guessing attacks can be performed using a number of tools. For HTTP(S) services, using an interception proxy is a viable option. For example, you can use Burp Suite Intruder to perform both wordlist-based and brute-force attacks.
- Start Burp Suite.
- Create a new project (or open an existing one).
- Set up your mobile device to use Burp as the HTTP/HTTPS proxy. Log into the mobile app and intercept the authentication request sent to the backend service.
- Right-click this request on the 'Proxy/HTTP History' tab and select 'Send to Intruder' in the context menu.
- Select the 'Intruder' tab in Burp Suite.
- Make sure all parameters in the 'Target', 'Positions', and 'Options' tabs are appropriately set and select the 'Payload' tab.
- Load or upload the list of passwords you'll try. You're ready to start the attack!
- Click the 'Start attack' button to attack the authentication.
A new window will open. Site requests are sent sequentially, each request corresponding to a password from the list. Information about the response (length, status code, ...) is provided for each request, allowing you to distinguish successful and unsuccessful attempts:
In this example, you can identify the successful attempt by length (password = "P@ssword1").
Tip: Append the correct password of your test account to the end of the password list. The list shouldn't have more than 25 passwords. If you can complete the attack without locking the account, that means the account isn't protected against brute force attacks.
- M4 - Insecure Authentication - https://www.owasp.org/index.php/Mobile_Top_10_2016-M4-Insecure_Authentication
- V4.6: "The remote endpoint implements an exponential back-off or temporarily locks the user account when incorrect authentication credentials are submitted an excessive number of times."
- CWE-307: Improper Restriction of Excessive Authentication Attempts
- Free and Professional Burp Suite editions - https://portswigger.net/burp/ Important precision: The free Burp Suite edition has significant limitations . In the Intruder module, for example, the tool automatically slows down after a few requests, password dictionaries aren't included, and you can't save projects.
- OWASP ZAP - https://www.owasp.org/index.php/OWASP_Zed_Attack_Proxy_Project
Session-based authentication is characterized by authentication records on both the client and server. The authentication flow is as follows:
- The app sends a request with the user's credentials to the backend server.
- The server verifies the credentials If the credentials are valid, the server creates a new session along with a random session ID.
- The server sends to the client a response that includes the session ID.
- The client sends the session ID with all subsequent requests. The server validates the session ID and retrieves the associated session record.
- After the user logs out, the server-side session record is destroyed and the client discards the session ID.
When sessions are improperly managed, they are vulnerable to a variety of attacks that may compromise the session of a legitimate user, allowing the attacker to impersonate the user. This may result in lost data, compromised confidentiality, and illegitimate actions.
Locate any server-side endpoints that provide sensitive information or functions and verify the consistent enforcement of authorization. The backend service must verify the user's session ID or token and make sure that the user has sufficient privileges to access the resource. If the session ID or token is missing or invalid, the request must be rejected.
Make sure that:
- Session IDs are randomly generated on the server side.
- The IDs can't be guessed easily (use proper length and entropy).
- Session IDs are always exchanged over secure connections (e.g. HTTPS).
- The mobile app doesn't save session IDs in permanent storage.
- The server verifies the session whenever a user tries to access privileged application elements, (a session ID must be valid and must correspond to the proper authorization level).
- The session is terminated on the server side and deleted within the mobile app after it times out or the user logs out.
Authentication shouldn't be implemented from scratch but built on top of proven frameworks. Many popular frameworks provide ready-made authentication and session management functionality. If the app uses framework APIs for authentication, check the framework security documentation for best practices. Security guides for common frameworks are available at the following links:
A great resource for testing server-side authentication is the OWASP Web Testing Guide, specifically the Testing Authentication and Testing Session Management chapters.
In most popular frameworks, you can set the session timeout via configuration options. This parameter should be set according to the best practices specified in the framework documentation. The recommended timeout may be between 10 minutes and two hours, depending on the app's sensitivity.
Refer to the framework documentation for examples of session timeout configuration:
First, manually or automatically crawl the application to make sure that all privileged actions and data are secure and to determine whether a valid session ID is required. Record the requests in your proxy.
Then, replay the crawled requests while manipulating the session IDs as follows:
- Invalidate the session ID (for example, append to the session ID, or delete the session ID from the request).
- Log out and log back in to see whether the session ID has changed.
- Try to re-use a session ID after logging out.
To verify session timeout:
- Log into the application.
- Perform a couple of operations that require authentication.
- Leave the session idle until it expires. After session expiry, attempt to use the same session ID to access authenticated functionality.
Consult the OWASP Testing Guide for more session management test cases.
- M4 - Insecure Authentication - https://www.owasp.org/index.php/Mobile_Top_10_2016-M4-Insecure_Authentication
- V4.2: "If stateful session management is used, the remote endpoint uses randomly generated session identifiers to authenticate client requests without sending the user's credentials."
- V4.8: "Sessions and access tokens are invalidated at the remote endpoint after a predefined period of inactivity."
- CWE-613: Insufficient Session Expiration
- OWASP ZAP (Zed Attack Proxy)
- Burp Suite
Token-based authentication is implemented by sending a signed token (verified by the server) with each HTTP request. The most commonly used token format is the JSON Web Token, defined at (https://tools.ietf.org/html/rfc7519). A JWT may encode the complete session state as a JSON object. Therefore, the server doesn't have to store any session data or authentication information.
JWT tokens consist of three Base64-encoded parts separated by dots. The following example shows a Base64-encoded JSON Web Token:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
The header typically consists of two parts: the token type, which is JWT, and the hashing algorithm being used to compute the signature. In the example above, the header decodes as follows:
{"alg":"HS256","typ":"JWT"}
The second part of the token is the payload, which contains so-called claims. Claims are statements about an entity (typically, the user) and additional metadata. For example:
{"sub":"1234567890","name":"John Doe","admin":true}
Signature
The signature is created by applying the algorithm specified in the JWT header to the encoded header, encoded payload, and a secret value. For example, when using the HMAC SHA256 algorithm the signature is created in the following way:
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
Note that the secret is shared between the authentication server and the back end service - the client does not know it. This proves that the token was obtained from a legitimate authentication service. It also prevents the client from tampering with the claims contained in the token.
Identify the JWT library that the server and client use. Find out whether the JWT libraries in use have any known vulnerabilities .
Verify that the implementation adheres to JWT best practices:
- Verify that the HMAC is checked for all incoming requests containing a token;
- Verify the location of the private signing key or HMAC secret key. The key should remain on the server and should never be shared with the client. It should be available for the issuer and verifier only.
- Verify that no sensitive data, such as personal identifiable information, is embedded in the JWT. If, for some reason, the architecture requires transmission of such information in the token, make sure that payload encryption is being applied. See the sample Java implementation on the [OWASP JWT Cheat Sheet](https://www.owasp.org/index.php/JSON_Web_Token_(JWT\)\_Cheat_Sheet_for_Java).
- Make sure that replay attacks are addressed with the
jti
(JWT ID) claim, which gives the JWT a unique identifier. - Verify that tokens are stored securely on the mobile phone, with, for example, KeyChain (iOS) or KeyStore (Android).
An attacker executes this by altering the token and, using the 'none' keyword, changing the hashing algorithm to indicate that the integrity of the token has already been verified. As explained at the link above, some libraries treated tokens signed with the none algorithm as if they were valid tokens with verified signatures, so the application will trust altered token claims.
For example, in Java applications, the expected algorithm should be requested explicitly when creating the verification context:
// HMAC key - Block serialization and storage as String in JVM memory
private transient byte[] keyHMAC = ...;
//Create a verification context for the token requesting explicitly the use of the HMAC-256 hashing algorithm
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(keyHMAC)).build();
//Verify the token; if the verification fails then an exception is thrown
DecodedJWT decodedToken = verifier.verify(token);
Once signed, a stateless authentication token is valid forever unless the signing key changes. A common way to limit token validity is to set an expiration date. Make sure that the tokens include an "exp" expiration claim and the back end doesn't process expired tokens.
A common method of granting tokens combines access tokens and refresh tokens. When the user logs in, the backend service issues a short-lived access token and a long-lived refresh token. The application can then use the refresh token to obtain a new access token, if the access token expires.
For apps that handle sensitive data, make sure that the refresh token expires after a reasonable period of time. The following example code shows a refresh token API that checks the refresh token's issue date. If the token is not older than 14 days, a new access token is issued. Otherwise, access is denied and the user is prompted to login again.
app.post('/refresh_token', function (req, res) {
// verify the existing token
var profile = jwt.verify(req.body.token, secret);
// if more than 14 days old, force login
if (profile.original_iat - new Date() > 14) { // iat == issued at
return res.send(401); // re-login
}
// check if the user still exists or if authorization hasn't been revoked
if (!valid) return res.send(401); // re-logging
// issue a new token
var refreshed_token = jwt.sign(profile, secret, { expiresInMinutes: 60*5 });
res.json({ token: refreshed_token });
});
Investigate the following JWT vulnerabilities while performing dynamic analysis:
- Usage of asymmetric algorithms:
- JWT offers several asymmetric algorithms as RSA or ECDSA. When these algorithms are used, tokens are signed with the private key and the public key is used for verification. If a server is expecting a token to be signed with an asymmetric algorithm and receives a token signed with HMAC, it will treat the public key as an HMAC secret key. The public key can then be misused, employed as an HMAC secret key to sign the tokens.
- Token Storage on the client:
- The token storage location should be verified for mobile apps that use JWT.
- Cracking the signing key:
- Token signatures are created via a private key on the server. After you obtain a JWT, choose a tool for brute forcing the secret key offline. See the tools section for details.
- Information Disclosure:
- Decode the Base64-encoded JWT and find out what kind of data it transmits and whether that data is encrypted.
Also, make sure to check out the OWASP JWT Cheat Sheet](https://www.owasp.org/index.php/JSON_Web_Token_(JWT\)\_Cheat_Sheet_for_Java "OWASP JWT Cheat Sheet").
Modify the alg
attribute in the token header, then delete HS256
, set it to none
, and use an empty signature (e.g., signature = ""). Use this token and replay it in a request. Some libraries treat tokens signed with the none algorithm as a valid token with a verified signature. This allows attackers to create their own "signed" tokens.
- M4 - Insecure Authentication - https://www.owasp.org/index.php/Mobile_Top_10_2016-M4-Insecure_Authentication
- V4.3: "If stateless token-based authentication is used, the server provides a token that has been signed with a secure algorithm."
- CWE-287: Improper Authentication
OAuth 2.0 defines a delegation protocol for conveying authorization decisions across APIs and a network of web-enabled applications. It is used in a variety of applications, including user authentication applications.
Common uses for OAuth2 include:
- Getting permission from the user to access an online service using their account.
- Authenticating to an online service on behalf of the user.
- Handling authentication errors.
According to OAuth 2.0, a mobile client seeking access to a user's resources must first ask the user to authenticate against an authentication server. With the users' approval, the authorization server then issues a token that allows the app to act on behalf of the user. Note that the OAuth2 specification doesn't define any particular kind of authentication or access token format.
OAuth 2.0 defines four roles:
- Resource Owner: the account owner
- Client: the application that wants to access the user's account with the access tokens
- Resource Server: hosts the user accounts
- Authorization Server: verifies user identity and issues access tokens to the application
Note: The API fulfills both the Resource Owner and Authorization Server roles. Therefore, we will refer to both as the API.
Here is a more detailed explanation of the steps in the diagram:
- The application requests user authorization to access service resources.
- If the user authorizes the request, the application receives an authorization grant. The authorization grant may take several forms (explicit, implicit, etc.).
- The application requests an access token from the authorization server (API) by presenting authentication of its own identity along with the authorization grant.
- If the application identity is authenticated and the authorization grant is valid, the authorization server (API) issues an access token to the application, completing the authorization process. The access token may have a companion refresh token.
- The application requests the resource from the resource server (API) and presents the access token for authentication. The access token may be used in several ways (e.g., as a bearer token).
- If the access token is valid, the resource server (API) serves the resource to the application.
Verify that the following best practices are followed:
User agent:
- The user should have a way to visually verify trust (e.g., Transport Layer Security (TLS) confirmation, website mechanisms).
- To prevent man-in-the-middle attacks, the client should validate the server's fully qualified domain name with the public key the server presented when the connection was established.
Type of grant:
- On native apps, code grant should be used instead of implicit grant.
- When using code grant, PKCE (Proof Key for Code Exchange) should be implemented to protect the code grant. Make sure that the server also implements it.
- The auth "code" should be short-lived and used immediately after it is received. Verify that auth codes only reside on transient memory and aren't stored or logged.
Client secrets:
- Shared secrets should not be used to prove the client's identity because the client could be impersonated ("client_id" already serves as proof). If they do use client secrets, be sure that they are stored in secure local storage.
End-User credentials:
- Secure the transmission of end-user credentials with a transport-layer method, such as TLS.
Tokens:
- Keep access tokens in transient memory.
- Access tokens must be transmitted over an encrypted connection.
- Reduce the scope and duration of access tokens when end-to-end confidentiality can't be guaranteed or the token provides access to sensitive information or transactions.
- Remember that an attacker who has stolen tokens can access their scope and all resources associated with them if the app uses access tokens as bearer tokens with no other way to identify the client.
- Store refresh tokens in secure local storage; they are long-term credentials.
OAuth2 authentication can be performed either through an external user agent (e.g. Chrome or Safari) or in the app itself (e.g. through a WebView embedded into the app or an authentication library). None of the two modes is intrinsically "better" - instead, what mode to choose depends on the context.
Using an external user agent is the method of choice for apps that need to interact with social media accounts (Facebook, Twitter, etc.). Advantages of this method include:
-
The user's credentials are never directly exposed to the app. This guarantees that the app cannot obtain the credentials during the login process ("credential phishing").
-
Almost no authentication logic must be added to the add itself, preventing coding errors.
On the negative side, there is no way to control the behavior of the browser (e.g. to activate certificate pinning).
For apps that operate within a closed ecosystem, embedded authentication is the better choice. For example, consider a banking app that uses OAuth2 to retrieve an access token from the bank's authentication server, which is then used to access a number of micro services. In that case, credential phishing is not a viable scenario. It is likely preferable to keep the authentication process in the (hopefully) carefully secured banking app, instead of placing trust on external components.
For additional best practices and detailed information please refer to the following source documents:
- RFC6749 - The OAuth 2.0 Authorization Framework
- DRAFT - OAuth 2.0 for Native Apps
- RFC6819 - OAuth 2.0 Threat Model and Security Considerations
- M4 - Insecure Authentication - https://www.owasp.org/index.php/Mobile_Top_10_2016-M4-Insecure_Authentication
- V4.2: "If stateful session management is used, the remote endpoint uses randomly generated session identifiers to authenticate client requests without sending the user's credentials."
- CWE-613: Insufficient Session Expiration
- OWASP ZAP (Zed Attack Proxy)
- Burp Suite
Minimizing the lifetime of session identifiers and tokens decreases the likelihood of successful account hijacking. The purpose of this test case is verifying logout functionality and determining whether it effectively terminates the session on both client and server and invalidates a stateless token.
Failing to destroy the server-side session is one of the most common logout functionality implementation errors . This error keeps the session or token alive, even after the user logs out of the application. An attacker who gets valid authentication information can continue to use it and hijack a user account.
Many mobile apps don't automatically log users out because it is inconvenient for customers by implementing stateless authentication. The application should still have a logout function, and it should be implemented according to best practices, destroying the access and refresh token on the client and server. Otherwise, authentication can be bypassed when the refresh token is not invalidated.
If server code is available, make sure logout functionality terminates the session is terminated . This verification will depend on the technology. Here are examples session termination for proper server-side logout:
- Spring (Java) - http://docs.spring.io/spring-security/site/docs/current/apidocs/org/springframework/security/web/authentication/logout/SecurityContextLogoutHandler.html
- Ruby on Rails - http://guides.rubyonrails.org/security.html
- PHP - http://php.net/manual/en/function.session-destroy.php
If access and refresh tokens are used with stateless authentication, they should be deleted from the mobile device. The refresh token should be invalidated on the server.
Use an interception proxy for dynamic application analysis. Use the following steps to check whether the logout is implemented properly.
- Log into the application.
- Perform a couple of operations that require authentication inside the application.
- Log out.
- Resend one of the operations from step 2 with an interception proxy (Burp Repeater, for example). . This will send to the server a request with the session ID or token that was invalidated in step 3. If logout is correctly implemented on the server, an error message or redirect to the login page will be sent back to the client. On the other hand, if you receive the same response you got in step 2, the token or session ID is still valid and hasn't been correctly terminated on the server. The OWASP Web Testing Guide (OTG-SESS-006) includes a detailed explanation and more test cases.
- M4 - Insecure Authentication - https://www.owasp.org/index.php/Mobile_Top_10_2016-M4-Insecure_Authentication
- V4.4: "The remote endpoint terminates the existing stateful session or invalidates the stateless session token when the user logs out."
- CWE-613: Insufficient Session Expiration