-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Create TokenResponse to ensure the AppAuth library doesn't leak
- Add README.md - Rename interface and implementation Resolves: DCMAW-7374
- Loading branch information
1 parent
0cdeb50
commit 09384a8
Showing
8 changed files
with
307 additions
and
133 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
# mobile-android-authentication | ||
|
||
Implementation of Authentication package | ||
|
||
## Installation | ||
|
||
To use Authentication in an Android Project: | ||
|
||
1. Add the following to the settings.gradle.kts | ||
|
||
```kotlin | ||
dependencyResolutionManagement { | ||
... | ||
|
||
repositories { | ||
|
||
... | ||
|
||
maven("https://maven.pkg.github.com/govuk-one-login/mobile-android-authentication") { | ||
if (file("${rootProject.projectDir.path}/github.properties").exists()) { | ||
val propsFile = File("${rootProject.projectDir.path}/github.properties") | ||
val props = Properties().also { it.load(FileInputStream(propsFile)) } | ||
val ghUsername = props["ghUsername"] as String? | ||
val ghToken = props["ghToken"] as String? | ||
|
||
credentials { | ||
username = ghUsername | ||
password = ghToken | ||
} | ||
} else { | ||
credentials { | ||
username = System.getenv("USERNAME") | ||
password = System.getenv("TOKEN") | ||
} | ||
} | ||
} | ||
} | ||
} | ||
``` | ||
|
||
2. For local development, ensure you have a `github.properties` in the project's root which includes your username and an access token | ||
3. Add `implementation("uk.gov.android:authentication:_")` for latest version. Check packages for version information | ||
|
||
## Package description | ||
|
||
The Authentication package authenticates a users details and enables them to log into their account securely. This is done by providing them with a login session and token. | ||
|
||
The package integrates [openID](https://openid.net/developers/how-connect-works/) AppAuth and conforms to its standards, documentation can be found here [AppAuth](https://github.com/openid/AppAuth-Android) | ||
|
||
### Types | ||
|
||
#### LoginSessionConfiguration | ||
|
||
Handles creating the `config` found in `LoginSession`. It requires the following to be initialised: | ||
|
||
```kotlin | ||
val authorizeEndpoint: Uri | ||
val clientId: String | ||
val redirectUri: Uri | ||
val scopes: String | ||
val tokenEndpoint: Uri | ||
|
||
// Default values | ||
val locale: String = "en" | ||
val prefersEphemeralWebSession: Boolean = true | ||
val responseType: String = ResponseTypeValues.CODE | ||
val vectorsOfTrust: String = "[\"Cl.Cm.P0\"]" | ||
``` | ||
|
||
#### TokenResponse | ||
|
||
Holds the returned token values | ||
|
||
```kotlin | ||
val tokenType: String | ||
val accessToken: String | ||
val accessTokenExpirationTime: Long | ||
val idToken: String | ||
val refreshToken: String? | ||
val scope: String | ||
``` | ||
|
||
#### AppAuthSession | ||
|
||
A class to handle the login flow with the given auth provider and conforms to the `LoginSession` protocol. | ||
|
||
`present` takes configuration, which comes from `LoginSessionConfiguration`, as a parameter and contains the login information to make the request. It will start an Activity for Result | ||
|
||
`finalise` takes the `Intent` received from the Activity started by `present` and provides the `TokenResponse` via a callback | ||
|
||
## Example Implementation | ||
|
||
### How to use the Authentication package | ||
|
||
Don't forget to call `init` with a context before use! | ||
|
||
```kotlin | ||
import uk.gov.android.authentication.LoginSession | ||
|
||
... | ||
|
||
val loginSession: LoginSession = AppAuthSession() | ||
val configuration = LoginSessionConfiguration( | ||
authorizeEndpoint = uri, | ||
clietId = "clientId", | ||
redirectUri = uri, | ||
scopes = "scopes", | ||
tokenEdnpoint = uri | ||
) | ||
|
||
loginSession | ||
.init(context) | ||
.present(configuration) | ||
|
||
|
||
``` | ||
|
||
Ensure the request code has been registered by the Activity to handle the ActivityResult and call `finalise` | ||
|
||
```kotlin | ||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { | ||
super.onActivityResult(requestCode, resultCode, data) | ||
|
||
if (requestCode == LoginSession.REQUEST_CODE_AUTH) { | ||
try { | ||
loginSession.finalise(intent) { tokens -> | ||
// Do what you like with the tokens! | ||
// ... | ||
} | ||
} catch (e: Error) { | ||
// handle error | ||
} | ||
} | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
117 changes: 117 additions & 0 deletions
117
app/src/main/java/uk/gov/android/authentication/AppAuthSession.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
package uk.gov.android.authentication | ||
|
||
import android.app.Activity | ||
import android.content.Context | ||
import android.content.Intent | ||
import androidx.core.app.ActivityCompat | ||
import java.util.UUID | ||
import net.openid.appauth.AuthorizationException | ||
import net.openid.appauth.AuthorizationRequest | ||
import net.openid.appauth.AuthorizationResponse | ||
import net.openid.appauth.AuthorizationService | ||
import net.openid.appauth.AuthorizationServiceConfiguration | ||
|
||
@Suppress("TooGenericExceptionThrown") | ||
class AppAuthSession : LoginSession { | ||
private var context: Context? = null | ||
private lateinit var authService: AuthorizationService | ||
|
||
override fun init( | ||
context: Context | ||
): LoginSession { | ||
if (this.context == null) { | ||
this.context = context | ||
authService = AuthorizationService(context) | ||
} | ||
return this | ||
} | ||
|
||
override fun present( | ||
configuration: LoginSessionConfiguration | ||
) { | ||
if (context == null) { | ||
throw Error("Context is null, did you call init?") | ||
} | ||
|
||
with(configuration) { | ||
val context = this@AppAuthSession.context!! | ||
val nonce = UUID.randomUUID().toString() | ||
|
||
val serviceConfig = AuthorizationServiceConfiguration( | ||
authorizeEndpoint, | ||
tokenEndpoint | ||
) | ||
|
||
val builder = AuthorizationRequest.Builder( | ||
serviceConfig, | ||
clientId, | ||
responseType, | ||
redirectUri | ||
).also { | ||
it.apply { | ||
setScopes(scopes) | ||
setUiLocales(locale) | ||
setNonce(nonce) | ||
setAdditionalParameters( | ||
mapOf( | ||
"vtr" to vectorsOfTrust | ||
) | ||
) | ||
} | ||
} | ||
|
||
val authRequest = builder.build() | ||
|
||
val authIntent = authService.getAuthorizationRequestIntent(authRequest) | ||
ActivityCompat.startActivityForResult( | ||
context as Activity, | ||
authIntent, | ||
REQUEST_CODE_AUTH, | ||
null | ||
) | ||
} | ||
} | ||
|
||
override fun finalise(intent: Intent, callback: (tokens: TokenResponse) -> Unit) { | ||
val authorizationResponse = AuthorizationResponse.fromIntent(intent) | ||
|
||
if (authorizationResponse == null) { | ||
val exception = AuthorizationException.fromIntent(intent) | ||
|
||
throw Exception(exception?.message) | ||
} | ||
|
||
val exchangeRequest = authorizationResponse.createTokenExchangeRequest() | ||
|
||
authService.performTokenRequest( | ||
exchangeRequest | ||
) { response, exception -> | ||
if (response == null) { | ||
throw Error(exception?.message) | ||
} | ||
|
||
callback(createFromAppAuthResponse(response)) | ||
} | ||
} | ||
|
||
private fun createFromAppAuthResponse( | ||
response: net.openid.appauth.TokenResponse | ||
): TokenResponse { | ||
return TokenResponse( | ||
tokenType = requireNotNull(response.tokenType) { "token type must not be empty" }, | ||
accessToken = | ||
requireNotNull(response.accessToken) { "access token must not be empty" }, | ||
accessTokenExpirationTime = | ||
requireNotNull(response.accessTokenExpirationTime) { | ||
"Token expiry must not be empty" | ||
}, | ||
idToken = requireNotNull(response.idToken) { "id token must not be empty" }, | ||
refreshToken = response.refreshToken, | ||
scope = requireNotNull(response.scope) { "scope must not be empty" } | ||
) | ||
} | ||
|
||
companion object { | ||
const val REQUEST_CODE_AUTH = 418 | ||
} | ||
} |
36 changes: 0 additions & 36 deletions
36
app/src/main/java/uk/gov/android/authentication/ILoginSession.kt
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.