forked from feedhenry/mobile-security-ios-template
-
Notifications
You must be signed in to change notification settings - Fork 0
/
AuthenticationService.swift
186 lines (161 loc) · 6.75 KB
/
AuthenticationService.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
//
// AuthenticationService.swift
// secure-ios-app
//
// Created by Wei Li on 09/11/2017.
// Copyright © 2017 Wei Li. All rights reserved.
//
import Foundation
import AppAuth
import UIKit
import SwiftKeychainWrapper
import Alamofire
protocol AuthenticationService {
func performAuthentication(presentingViewController viewController:UIViewController, onCompleted: @escaping (Identity?, Error?) -> Void)
func resumeAuth(url: URL) -> Bool
func isLoggedIn() -> Bool
func performLogout(onCompleted: @escaping (Error?) -> Void)
func currentIdentity() -> Identity?
}
class AppAuthAuthenticationService: AuthenticationService {
let REDIRECT_URL = URL(string:"com.redhat.secure-ios-app.sso2:/callback")
let KC_AUTH_STATE_KEY = "auth-state"
let authServerConfiguration: AuthServerConfiguration
let keychainWrapper: KeychainWrapper
var currentAuthorisationFlow: OIDAuthorizationFlowSession?
var authState: OIDAuthState?
var identify: Identity?
var onCompleted: ((Identity?, Error?) -> Void)?
init(authServerConfig: AuthServerConfiguration, kcWrapper: KeychainWrapper) {
self.authServerConfiguration = authServerConfig
self.keychainWrapper = kcWrapper
self.authState = loadState()
self.identify = getIdentify(authState: self.authState)
}
// tag::performAuthentication[]
func performAuthentication(presentingViewController viewController:UIViewController, onCompleted: @escaping (Identity?, Error?) -> Void) {
self.onCompleted = onCompleted
let oidServiceConfiguration = OIDServiceConfiguration(authorizationEndpoint: self.authServerConfiguration.authEndpoint, tokenEndpoint: self.authServerConfiguration.tokenEndpoint)
let oidAuthRequest = OIDAuthorizationRequest(configuration: oidServiceConfiguration, clientId: self.authServerConfiguration.clientId, scopes: [OIDScopeOpenID, OIDScopeProfile], redirectURL: REDIRECT_URL!, responseType: OIDResponseTypeCode, additionalParameters: nil)
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.authService = self;
let externalUserAgent = OIDExternalUserAgentIOSCustomBrowser.customBrowserSafari();
//this will automatically exchange the token to get the user info
self.currentAuthorisationFlow = OIDAuthState.authState(byPresenting: oidAuthRequest, externalUserAgent: externalUserAgent) {
authState,error in
if authState != nil && authState?.authorizationError == nil {
self.authSuccess(authState: authState!)
} else {
self.authFailure(authState: authState, error: error)
}
}
}
// end::performAuthentication[]
func resumeAuth(url: URL) -> Bool {
if self.currentAuthorisationFlow!.resumeAuthorizationFlow(with: url) {
self.currentAuthorisationFlow = nil;
return true
}
return false
}
// tag::isLoggedIn[]
func isLoggedIn() -> Bool {
return self.identify != nil
}
// end::isLoggedIn[]
func authSuccess(authState: OIDAuthState) {
Logger.debug("got auth state \(authState.debugDescription)")
self.assignAuthState(authState: authState)
self.authCompleted(identify: self.identify, error: nil)
}
func authFailure(authState: OIDAuthState?, error: Error?) {
let e = authState?.authorizationError ?? error
Logger.error("auth error \(e.debugDescription)")
self.assignAuthState(authState: nil)
self.authCompleted(identify: nil, error: e)
}
func authCompleted(identify: Identity?, error: Error?) {
if (self.onCompleted != nil) {
self.onCompleted!(identify, error)
}
}
// tag::performLogout[]
func performLogout(onCompleted: @escaping (Error?) -> Void) {
if self.isLoggedIn() {
let logoutUrl = self.authServerConfiguration.getLogoutUrl(identityToken: self.authState!.lastTokenResponse!.idToken!)
Alamofire.request(logoutUrl).validate(statusCode: 200..<300).responseData(completionHandler: {response in
switch response.result {
case .success:
self.assignAuthState(authState: nil)
onCompleted(nil)
case .failure(let error):
onCompleted(error)
}
})
}
}
// end::performLogout[]
func currentIdentity() -> Identity? {
return self.identify
}
fileprivate func assignAuthState(authState: OIDAuthState?) {
self.authState = authState
self.identify = self.getIdentify(authState: authState)
self.saveState()
}
fileprivate func saveState() {
if self.authState != nil {
self.keychainWrapper.set(self.authState!, forKey: KC_AUTH_STATE_KEY)
} else {
self.keychainWrapper.removeObject(forKey: KC_AUTH_STATE_KEY)
}
}
fileprivate func loadState() -> OIDAuthState? {
guard let loadedState = self.keychainWrapper.object(forKey: KC_AUTH_STATE_KEY) else {
return nil
}
return loadedState as? OIDAuthState
}
fileprivate func getIdentify(authState: OIDAuthState?) -> Identity? {
if authState == nil {
return nil
}
return Identity(accessToken: authState!.lastTokenResponse?.accessToken)
}
}
func base64urlToBase64(base64url: String) -> String {
var base64 = base64url
.replacingOccurrences(of: "-", with: "+")
.replacingOccurrences(of: "_", with: "/")
if base64.characters.count % 4 != 0 {
base64.append(String(repeating: "=", count: 4 - base64.characters.count % 4))
}
return base64
}
extension Identity {
init?(accessToken: String?) {
if accessToken == nil {
return nil
}
let parts = accessToken?.components(separatedBy: ".")
guard let encodedToken = parts?[1] else {
return nil
}
guard let jsonData = Data(base64Encoded: base64urlToBase64(base64url: encodedToken)) else {
return nil
}
let tokenJson = try? JSONSerialization.jsonObject(with: jsonData, options: []) as! [String: Any]
guard let userName = tokenJson!["name"] as? String,
let fullName = tokenJson!["name"] as? String,
let emailAddress = tokenJson!["email"] as? String,
let realmAccess = tokenJson!["realm_access"] as? [String: [String]],
let realmRoles = realmAccess["roles"]
else {
return nil
}
self.userName = userName
self.fullName = fullName
self.emailAddress = emailAddress
self.reamlRoles = realmRoles
}
}