Skip to content

Commit

Permalink
Implement prompt for re-login/consent
Browse files Browse the repository at this point in the history
  • Loading branch information
mohssenfathi committed Jul 2, 2024
1 parent 735c180 commit cf70a70
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 40 deletions.
6 changes: 4 additions & 2 deletions Sources/UberAuth/AuthProviding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ extension AuthProviding where Self == AuthorizationCodeAuthProvider {

public static func authorizationCode(presentationAnchor: ASPresentationAnchor = .init(),
scopes: [String] = AuthorizationCodeAuthProvider.defaultScopes,
shouldExchangeAuthCode: Bool = true) -> Self {
shouldExchangeAuthCode: Bool = true,
prompt: Prompt? = nil) -> Self {
AuthorizationCodeAuthProvider(
presentationAnchor: presentationAnchor,
scopes: scopes,
shouldExchangeAuthCode: shouldExchangeAuthCode
shouldExchangeAuthCode: shouldExchangeAuthCode,
prompt: prompt
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,14 @@ public final class AuthorizationCodeAuthProvider: AuthProviding {

private let scopes: [String]

private let prompt: Prompt?

// MARK: Initializers

public init(presentationAnchor: ASPresentationAnchor = .init(),
scopes: [String] = AuthorizationCodeAuthProvider.defaultScopes,
shouldExchangeAuthCode: Bool = false) {
shouldExchangeAuthCode: Bool = false,
prompt: Prompt? = nil) {
self.configurationProvider = DefaultConfigurationProvider()

guard let clientID: String = configurationProvider.clientID else {
Expand All @@ -73,11 +76,13 @@ public final class AuthorizationCodeAuthProvider: AuthProviding {
self.networkProvider = NetworkProvider(baseUrl: Constants.baseUrl)
self.tokenManager = TokenManager()
self.scopes = scopes
self.prompt = prompt
}

init(presentationAnchor: ASPresentationAnchor = .init(),
authenticationSessionBuilder: AuthenticationSessionBuilder? = nil,
scopes: [String] = AuthorizationCodeAuthProvider.defaultScopes,
prompt: Prompt? = nil,
shouldExchangeAuthCode: Bool = false,
configurationProvider: ConfigurationProviding = DefaultConfigurationProvider(),
applicationLauncher: ApplicationLaunching = UIApplication.shared,
Expand All @@ -104,6 +109,7 @@ public final class AuthorizationCodeAuthProvider: AuthProviding {
self.networkProvider = networkProvider
self.tokenManager = tokenManager
self.scopes = scopes
self.prompt = prompt
}

// MARK: AuthProviding
Expand Down Expand Up @@ -195,6 +201,7 @@ public final class AuthorizationCodeAuthProvider: AuthProviding {
app: nil,
clientID: clientID,
codeChallenge: shouldExchangeAuthCode ? pkce.codeChallenge : nil,
prompt: prompt,
redirectURI: redirectURI,
requestURI: requestURI,
scopes: scopes
Expand Down
4 changes: 4 additions & 0 deletions Sources/UberAuth/Authorize/AuthorizeRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ struct AuthorizeRequest: NetworkRequest {
private let app: UberApp?
private let codeChallenge: String?
private let clientID: String
private let prompt: Prompt?
private let redirectURI: String
private let requestURI: String?
private let scopes: [String]
Expand All @@ -26,12 +27,14 @@ struct AuthorizeRequest: NetworkRequest {
init(app: UberApp?,
clientID: String,
codeChallenge: String?,
prompt: Prompt? = nil,
redirectURI: String,
requestURI: String?,
scopes: [String] = []) {
self.app = app
self.clientID = clientID
self.codeChallenge = codeChallenge
self.prompt = prompt
self.redirectURI = redirectURI
self.requestURI = requestURI
self.scopes = scopes
Expand All @@ -47,6 +50,7 @@ struct AuthorizeRequest: NetworkRequest {
"client_id": clientID,
"code_challenge": codeChallenge,
"code_challenge_method": codeChallenge != nil ? "S256" : nil,
"prompt": prompt?.stringValue,
"redirect_uri": redirectURI,
"request_uri": requestURI,
"scope": scopes.joined(separator: " ")
Expand Down
38 changes: 38 additions & 0 deletions Sources/UberAuth/Authorize/Prompt.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//
// Copyright © Uber Technologies, Inc. All rights reserved.
//


import Foundation

///
/// A type defining values that specify whether the Authorization Server prompts the End-User for reauthentication and consent.
/// Current supported values are `login` and `consent`

/// See OpedID standards for more information.
/// https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
///
public struct Prompt: OptionSet {

public let rawValue: Int

public init(rawValue: Int) {
self.rawValue = rawValue
}

/// The Authorization Server SHOULD prompt the End-User for reauthentication.
/// If it cannot reauthenticate the End-User, it MUST return an error, typically `login_required`.
public static let login = Prompt(rawValue: 1 << 0)

/// The Authorization Server SHOULD prompt the End-User for consent before returning information to the Client.
/// If it cannot obtain consent, it MUST return an error, typically `consent_required`.
public static let consent = Prompt(rawValue: 1 << 1)

/// Creates a space seperated string containing the values in the option set
var stringValue: String {
var values: [String] = []
if contains(.login) { values.append("login") }
if contains(.consent) { values.append("consent") }
return values.joined(separator: " ")
}
}
73 changes: 36 additions & 37 deletions examples/UberSDK/UberSDK/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,21 @@ final class Content {
var type: LoginType? = .authorizationCode
var destination: LoginDestination? = .inApp
var isTokenExchangeEnabled: Bool = true
var shouldForceLogin: Bool = false
var shouldForceConsent: Bool = false
var isPrefillExpanded: Bool = false
var response: String?
var prefillBuilder = PrefillBuilder()

func login() {

var promt: Prompt = []
if shouldForceLogin { promt.insert(.login) }
if shouldForceConsent { promt.insert(.consent) }

let authProvider: AuthProviding = .authorizationCode(
shouldExchangeAuthCode: isTokenExchangeEnabled
shouldExchangeAuthCode: isTokenExchangeEnabled,
prompt: promt
)

let authDestination: AuthDestination = {
Expand Down Expand Up @@ -72,6 +79,8 @@ final class Content {
case type = "Auth Type"
case destination = "Destination"
case tokenExchange = "Exchange Auth Code for Token"
case forceLogin = "Always ask for Login"
case forceConsent = "Always ask for Consent"
case prefill = "Prefill Values"
case firstName = "First Name"
case lastName = "Last Name"
Expand Down Expand Up @@ -169,41 +178,12 @@ struct ContentView: View {
@ViewBuilder
private var loginSection: some View {

row(
item: .type,
content: {
Text(content.type?.description ?? "")
.foregroundStyle(.gray)
},
tapHandler: { content.selection = .type }
)

row(
item: .destination,
content: {
Text(content.destination?.description ?? "")
.foregroundStyle(.gray)
},
tapHandler: { content.selection = .destination }
)

row(
item: .tokenExchange,
content: {
Toggle(isOn: $content.isTokenExchangeEnabled, label: { EmptyView() })
},
showDisclosureIndicator: false,
tapHandler: nil
)

row(
item: .prefill,
content: {
Toggle(isOn: $content.isPrefillExpanded, label: { EmptyView() })
},
showDisclosureIndicator: false,
tapHandler: nil
)
textRow(.type, value: content.type?.description)
textRow(.destination, value: content.destination?.description)
toggleRow(.tokenExchange, value: $content.isTokenExchangeEnabled)
toggleRow(.forceLogin, value: $content.shouldForceLogin)
toggleRow(.forceConsent, value: $content.shouldForceConsent)
toggleRow(.prefill, value: $content.isPrefillExpanded)

if content.isPrefillExpanded {
row(
Expand Down Expand Up @@ -263,15 +243,34 @@ struct ContentView: View {
label: {
HStack(spacing: 0) {
if let item { Text(item.rawValue) }
Spacer()
content()
.frame(maxWidth: .infinity, alignment: .trailing)
if showDisclosureIndicator { emptyNavigationLink }
}
}
)
.tint(.black)
}

private func textRow(_ item: Content.Item, value: String?) -> some View {
row(
item: item,
content: { Text(value ?? "").foregroundStyle(.gray) },
tapHandler: { content.selection = item }
)
}

private func toggleRow(_ item: Content.Item, value: Binding<Bool>) -> some View {
row(
item: item,
content: {
Toggle(isOn: value, label: { EmptyView() })
},
showDisclosureIndicator: false,
tapHandler: nil
)
}

private let emptyNavigationLink: some View = NavigationLink.empty
.frame(width: 17, height: 0)
.frame(alignment: .leading)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,43 @@ final class AuthorizationCodeAuthProviderTests: XCTestCase {

XCTAssertTrue(hasCalledAuthenticationSessionBuilder)
}

func test_executeInAppLogin_prompt_includedInAuthorizeRequest() {

configurationProvider.isInstalledHandler = { _, _ in
true
}

let applicationLauncher = ApplicationLaunchingMock()
applicationLauncher.openHandler = { _, _, completion in
completion?(true)
}

var hasCalledAuthenticationSessionBuilder: Bool = false
let prompt: Prompt = [.login, .consent]
let promptString = prompt.stringValue.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!

let authenticationSessionBuilder: AuthorizationCodeAuthProvider.AuthenticationSessionBuilder = { _, _, url, _ in
XCTAssertTrue(url.query()!.contains("prompt=\(promptString)"))
hasCalledAuthenticationSessionBuilder = true
return AuthenticationSessioningMock()
}

let provider = AuthorizationCodeAuthProvider(
authenticationSessionBuilder: authenticationSessionBuilder,
prompt: [.login, .consent],
shouldExchangeAuthCode: false,
configurationProvider: configurationProvider,
applicationLauncher: applicationLauncher
)

provider.execute(
authDestination: .inApp,
completion: { result in }
)

XCTAssertTrue(hasCalledAuthenticationSessionBuilder)
}

func test_execute_existingSession_returnsExistingAuthSessionError() {
let provider = AuthorizationCodeAuthProvider(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,14 @@ final class AuthorizeRequestTests: XCTestCase {

func test_generatedUrl() {

let prompt: Prompt = [.consent, .login]
let promptString = prompt.stringValue.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed)!

let request = AuthorizeRequest(
app: nil,
clientID: "test_client_id",
codeChallenge: "code_challenge",
prompt: prompt,
redirectURI: "redirect_uri",
requestURI: "request_url"
)
Expand All @@ -30,6 +34,7 @@ final class AuthorizeRequestTests: XCTestCase {
XCTAssertTrue(url.query()!.contains("request_uri=request_url"))
XCTAssertTrue(url.query()!.contains("code_challenge_method=S256"))
XCTAssertTrue(url.query()!.contains("redirect_uri=redirect_uri"))
XCTAssertTrue(url.query()!.contains("prompt=\(promptString)"))
}

func test_appSpecific_generatedUrls() {
Expand Down

0 comments on commit cf70a70

Please sign in to comment.